diff --git a/build.zig b/build.zig index 664adba..330cac5 100644 --- a/build.zig +++ b/build.zig @@ -29,10 +29,11 @@ pub fn build(b: *std.Build) void { const zant_mod = b.createModule(.{ .root_source_file = b.path("src/zant.zig") }); zant_mod.addOptions("build_options", build_options); - //************************************************UNIT TESTS************************************************ const codeGen_mod = b.createModule(.{ .root_source_file = b.path("src/CodeGen/codegen.zig") }); codeGen_mod.addImport("zant", zant_mod); + //************************************************UNIT TESTS************************************************ + // Define unified tests for the project. const unit_tests = b.addTest(.{ .name = "test_lib", @@ -41,6 +42,14 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + // Define test options + const test_options = b.addOptions(); + test_options.addOption(bool, "heavy", b.option(bool, "heavy", "Run heavy tests") orelse false); + unit_tests.root_module.addOptions("test_options", test_options); + + const test_name = b.option([]const u8, "test_name", "specify a test name to run") orelse ""; + test_options.addOption([]const u8, "test_name", test_name); + unit_tests.root_module.addImport("zant", zant_mod); unit_tests.root_module.addImport("codegen", codeGen_mod); @@ -192,7 +201,6 @@ pub fn build(b: *std.Build) void { test_step_generated_lib.dependOn(&run_test_generated_lib.step); // ************************************************ ONEOP CODEGEN ************************************************ - // Setup oneOp codegen const oneop_codegen_exe = b.addExecutable(.{ @@ -212,8 +220,7 @@ pub fn build(b: *std.Build) void { step_test_oneOp_codegen.dependOn(&run_oneop_codegen_exe.step); // ************************************************ - - //Setup test_all_oneOp + // Setup test_all_oneOp const test_all_oneOp = b.addTest(.{ .name = "test_all_oneOp", @@ -238,34 +245,12 @@ pub fn build(b: *std.Build) void { const step_test_oneOp = b.step("test-codegen", "Run generated library tests"); step_test_oneOp.dependOn(&run_test_all_oneOp.step); - // ************************************************ - // Benchmark - - const benchmark = b.addExecutable(.{ - .name = "benchmark", - .root_source_file = b.path("benchmarks/main.zig"), - .target = target, - .optimize = optimize, - }); - - const bench_options = b.addOptions(); - bench_options.addOption(bool, "full", b.option(bool, "full", "Choose whenever run full benchmark or not") orelse false); - - benchmark.root_module.addImport("zant", zant_mod); - benchmark.root_module.addOptions("bench_options", bench_options); - benchmark.linkLibC(); - - const run_benchmark = b.addRunArtifact(benchmark); - const benchmark_step = b.step("benchmark", "Run benchmarks"); - benchmark_step.dependOn(&run_benchmark.step); - // ************************************************ ONNX PARSER TESTS ************************************************ // Add test for generated library const test_onnx_parser = b.addTest(.{ .name = "test_generated_lib", .root_source_file = b.path("tests/Onnx/onnx_loader.zig"), - .target = target, .optimize = optimize, }); @@ -287,7 +272,6 @@ pub fn build(b: *std.Build) void { const main_executable = b.addExecutable(.{ .name = "main_profiling_target", - .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); @@ -304,36 +288,4 @@ pub fn build(b: *std.Build) void { const build_main_step = b.step("build-main", "Build the main executable for profiling"); build_main_step.dependOn(&install_main_exe_step.step); - - // ************************************************ NATIVE GUI ************************************************ - - { - const dvui_dep = b.dependency("dvui", .{ .target = target, .optimize = optimize, .backend = .sdl, .sdl3 = true }); - - const gui_exe = b.addExecutable(.{ - .name = "gui", - .root_source_file = b.path("gui/sdl/sdl-standalone.zig"), - .target = target, - .optimize = optimize, - }); - - // Can either link the backend ourselves: - // const dvui_mod = dvui_dep.module("dvui"); - // const sdl = dvui_dep.module("sdl"); - // @import("dvui").linkBackend(dvui_mod, sdl); - // exe.root_module.addImport("dvui", dvui_mod); - - // Or use a prelinked one: - gui_exe.root_module.addImport("dvui", dvui_dep.module("dvui_sdl")); - - const compile_step = b.step("compile-gui", "Compile gui"); - compile_step.dependOn(&b.addInstallArtifact(gui_exe, .{}).step); - b.getInstallStep().dependOn(compile_step); - - const run_cmd = b.addRunArtifact(gui_exe); - run_cmd.step.dependOn(compile_step); - - const run_step = b.step("gui", "Run gui"); - run_step.dependOn(&run_cmd.step); - } } diff --git a/src/CodeGen/math_handler.zig b/src/CodeGen/math_handler.zig index 645e8b7..15e6239 100644 --- a/src/CodeGen/math_handler.zig +++ b/src/CodeGen/math_handler.zig @@ -92,6 +92,8 @@ pub fn write_math_op(writer: std.fs.File.Writer, node: *ReadyNode) !void { try write_elu(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Flatten")) { try write_flatten(writer, node); + } else if (std.mem.eql(u8, node.nodeProto.op_type, "Floor")) { + try write_floor(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Gather")) { try write_gather(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Gemm")) { @@ -111,7 +113,7 @@ pub fn write_math_op(writer: std.fs.File.Writer, node: *ReadyNode) !void { } else if (std.mem.eql(u8, node.nodeProto.op_type, "Neg")) { try write_neg(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "OneHot")) { - try writer.writeAll("// Handle OneHot\n"); + try write_oneHot(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Pad")) { try write_pads(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "ReduceMean")) { @@ -366,6 +368,97 @@ inline fn write_BatchNormalization(writer: std.fs.File.Writer, node: *ReadyNode) }); } +inline fn write_oneHot(writer: std.fs.File.Writer, node: *ReadyNode) !void { + // https://onnx.ai/onnx/operators/onnx__OneHot.html + // INPUTS: + // - indices (heterogeneous) - T1: Tensor of indices. + // - depth (heterogeneous) - T2: Scalar tensor for depth. + // - values (heterogeneous) - T3: Tensor of shape [off_value, on_value]. + // OUTPUT: + // - output (heterogeneous) - T3: Output tensor with one-hot encoding. + // ATTRIBUTES: + // - axis - INT (default is -1): Axis along which to add the one-hot dimension. + + var axis: i64 = -1; // Default axis per ONNX + for (node.nodeProto.attribute) |attr| { + if (std.mem.eql(u8, attr.name, "axis")) { + if (attr.type != AttributeType.INT) return error.InvalidAxisType; + axis = attr.i; + } + } + + //----create indices string + var indices_string: []u8 = undefined; + defer allocator.free(indices_string); + if (node.inputs.items[0].?.tag == globals.TensorTag.INITIALIZER) { + indices_string = try std.mem.concat(allocator, u8, &[_][]const u8{ + "@constCast(¶m_lib.tensor_", + try utils.getSanitizedName(node.inputs.items[0].?.name), + ")", + }); + } else { + indices_string = try std.mem.concat(allocator, u8, &[_][]const u8{ + "@constCast(&tensor_", + try utils.getSanitizedName(node.inputs.items[0].?.name), + ")", + }); + } + + //----create depth string + var depth_string: []u8 = undefined; + defer allocator.free(depth_string); + if (node.inputs.items[1].?.tag == globals.TensorTag.INITIALIZER) { + depth_string = try std.mem.concat(allocator, u8, &[_][]const u8{ + "@constCast(¶m_lib.tensor_", + try utils.getSanitizedName(node.inputs.items[1].?.name), + ")", + }); + } else { + depth_string = try std.mem.concat(allocator, u8, &[_][]const u8{ + "@constCast(&tensor_", + try utils.getSanitizedName(node.inputs.items[1].?.name), + ")", + }); + } + + //----create values string + var values_string: []u8 = undefined; + defer allocator.free(values_string); + if (node.inputs.items[2].?.tag == globals.TensorTag.INITIALIZER) { + values_string = try std.mem.concat(allocator, u8, &[_][]const u8{ + "@constCast(¶m_lib.tensor_", + try utils.getSanitizedName(node.inputs.items[2].?.name), + ")", + }); + } else { + values_string = try std.mem.concat(allocator, u8, &[_][]const u8{ + "@constCast(&tensor_", + try utils.getSanitizedName(node.inputs.items[2].?.name), + ")", + }); + } + + _ = try writer.print( + \\ + \\ + \\ tensMath.oneHot_lean( + \\ {s}, // T + \\ {s}, // indices + \\ {s}.data[0], // depth (scalare) + \\ {s}, // values + \\ {}, // axis + \\ &tensor_{s}, // output + \\ ) + , .{ + try utils.getTypeString(globals.tensorHashMap.getPtr(node.inputs.items[2].?.name).?.tensorProto.?.data_type), // T + indices_string, // indices + depth_string, // depth + values_string, // values + axis, // axis + try utils.getSanitizedName(node.outputs.items[0].name), // output + }); +} + inline fn write_sub(writer: std.fs.File.Writer, node: *ReadyNode) !void { // https://onnx.ai/onnx/operators/onnx__Sub.html // INPUTS: @@ -2177,6 +2270,37 @@ inline fn write_transpose(writer: std.fs.File.Writer, node: *ReadyNode) !void { }); } +inline fn write_floor(writer: std.fs.File.Writer, node: *ReadyNode) !void { + // https://onnx.ai/onnx/operators/onnx__Floor.html + // INPUTS: + // - X (heterogeneous) - T: Input tensor + // OUTPUTS: + // - Y (heterogeneous) - T: Output tensor with floor of input elements (If x is integral, +0, -0, NaN, or infinite, x itself is returned) + + // Create input tensor string + var input_tensor_string: []u8 = undefined; + defer allocator.free(input_tensor_string); + + if (node.inputs.items[0].?.tag == globals.TensorTag.INITIALIZER) { + input_tensor_string = try std.mem.concat(allocator, u8, &[_][]const u8{ + "@constCast(¶m_lib.tensor_", + try utils.getSanitizedName(node.inputs.items[0].?.name), + ")", + }); + } else { + input_tensor_string = try std.mem.concat(allocator, u8, &[_][]const u8{ "&tensor_", try utils.getSanitizedName(node.inputs.items[0].?.name) }); + } + + _ = try writer.print( + \\ + \\ + \\ tensMath.floor_lean(T, {s}, &tensor_{s}) + , .{ + input_tensor_string, + try utils.getSanitizedName(node.outputs.items[0].name), + }); +} + inline fn write_tanh(writer: std.fs.File.Writer, node: *ReadyNode) !void { // https://onnx.ai/onnx/operators/onnx__Tanh.html // INPUTS: diff --git a/src/CodeGen/shape_handler.zig b/src/CodeGen/shape_handler.zig index d2d25e5..3455a66 100644 --- a/src/CodeGen/shape_handler.zig +++ b/src/CodeGen/shape_handler.zig @@ -80,6 +80,9 @@ pub fn compute_output_shape(readyNode: *ReadyNode) !void { } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Flatten")) { //https://onnx.ai/onnx/operators/onnx__Flatten.html try compute_flatten_output_shape(readyNode); + } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Floor")) { + //https://onnx.ai/onnx/operators/onnx__Floor.html + try compute_floor_output_shape(readyNode); } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Gather")) { try compute_gather_output_shape(readyNode); } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Gemm")) { @@ -100,8 +103,8 @@ pub fn compute_output_shape(readyNode: *ReadyNode) !void { //https://onnx.ai/onnx/operators/onnx__Neg.html try compute_neg_output_shape(readyNode); } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "OneHot")) { - // TODO - return error.OperationWIP; + //https://onnx.ai/onnx/operators/onnx__OneHot.html + try compute_oneHot_output_shape(readyNode); } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Pad")) { //https://onnx.ai/onnx/operators/onnx__Pad.html try compute_pads_output_shape(readyNode); @@ -438,6 +441,93 @@ inline fn compute_gemm_output_shape(readyNode: *ReadyNode) !void { readyNode.outputs.items[0].shape = shape; } +inline fn compute_oneHot_output_shape(readyNode: *ReadyNode) !void { + std.debug.print("\n====== compute_oneHot_output_shape node: {s} ======\n", .{readyNode.nodeProto.name orelse "(unnamed)"}); + + var shape: []const i64 = undefined; + + if (utils.getTensorShape(readyNode.outputs.items[0].name)) |tensorShape| { + shape = tensorShape; + } else { + // Verifica che ci siano esattamente 3 input: indices, depth, values + if (readyNode.inputs.items.len != 3) { + std.debug.print("\n ERROR: OneHot expects exactly 3 inputs, got {d}\n", .{readyNode.inputs.items.len}); + return error.InvalidNumberOfInputs; + } + + const indices = readyNode.inputs.items[0].?; + const depth_tensor = readyNode.inputs.items[1].?; + const values = readyNode.inputs.items[2].?; + + std.debug.print("\n indices_shape: []i64 = {any}", .{indices.shape}); + std.debug.print("\n depth_shape: []i64 = {any}", .{depth_tensor.shape}); + std.debug.print("\n values_shape: []i64 = {any}", .{values.shape}); + + // Verifica che depth sia uno scalare (forma [] o [1]) + const depth_shape_i64 = depth_tensor.shape; + const effective_depth_shape_i64 = if (depth_shape_i64.len == 0) &[_]i64{1} else depth_shape_i64; + if (effective_depth_shape_i64.len > 1 or effective_depth_shape_i64[0] != 1) { + std.debug.print("\n ERROR: depth must be a scalar, got shape {any}\n", .{effective_depth_shape_i64}); + return error.InvalidDepthShape; + } + + // Verifica che values abbia forma [2] + const values_shape_i64 = values.shape; + const effective_values_shape_i64 = if (values_shape_i64.len == 0) &[_]i64{1} else values_shape_i64; + if (effective_values_shape_i64.len != 1 or effective_values_shape_i64[0] != 2) { + std.debug.print("\n ERROR: values must have shape [2], got shape {any}\n", .{effective_values_shape_i64}); + return error.InvalidValuesShape; + } + + // Estrai il valore di depth + var depth: i64 = undefined; + if (depth_tensor.tensorProto != null and depth_tensor.tensorProto.?.int64_data != null) { + depth = depth_tensor.tensorProto.?.int64_data.?[0]; + } else if (depth_tensor.tensorProto != null and depth_tensor.tensorProto.?.raw_data != null) { + const raw = depth_tensor.tensorProto.?.raw_data.?; + if (raw.len < @sizeOf(i64)) { + std.debug.print("\n ERROR: depth raw_data is too small to contain an i64\n", .{}); + return error.InvalidDepthData; + } + depth = std.mem.readInt(i64, raw[0..@sizeOf(i64)], .little); + } else { + std.debug.print("\n ERROR: depth tensorProto is missing valid data\n", .{}); + return error.DepthDataMissing; + } + + // Verifica che depth sia positivo + if (depth <= 0) { + std.debug.print("\n ERROR: depth must be positive, got {d}\n", .{depth}); + return error.InvalidDepthValue; + } + + // Estrai l'attributo axis (default: -1) + var axis: i64 = -1; + for (readyNode.nodeProto.attribute) |attr| { + if (std.mem.eql(u8, attr.name, "axis")) { + if (attr.type != AttributeType.INT) { + std.debug.print("\n ERROR: axis attribute must be INT, got type {any}\n", .{attr.type}); + return error.InvalidAttributeType; + } + axis = attr.i; + break; + } + } + + const indices_shape_i64 = indices.shape; + const indices_shape_usize = try utils.i64SliceToUsizeSlice(indices_shape_i64); + defer allocator.free(indices_shape_usize); + + const output_shape_usize = try tensorMath.get_oneHot_output_shape(indices_shape_usize, depth, axis); + defer allocator.free(output_shape_usize); + + shape = try utils.usizeSliceToI64Slice(output_shape_usize); + } + + readyNode.outputs.items[0].shape = shape; + std.debug.print("\n output_shape: []i64 = {any}", .{readyNode.outputs.items[0].shape}); +} + inline fn compute_mul_output_shape(readyNode: *ReadyNode) !void { Codegen_log.info("\n====== compute_mul_output_shape node: {s} ======\n", .{readyNode.nodeProto.name.?}); @@ -1028,6 +1118,24 @@ inline fn compute_tanh_output_shape(readyNode: *ReadyNode) !void { readyNode.outputs.items[0].shape = shape; } +inline fn compute_floor_output_shape(readyNode: *ReadyNode) !void { + const input = readyNode.inputs.items[0] orelse { + return error.InputTensorIsNull; + }; + + var shape: []const i64 = undefined; + + if (utils.getTensorShape(readyNode.outputs.items[0].name)) |tensorShape| { + shape = tensorShape; + } else { + const input_shape = input.shape; + std.debug.print("\n input_shape: []i64 = {any}", .{input_shape}); + + shape = try utils.usizeSliceToI64Slice(try tensorMath.get_floor_output_shape(try utils.i64SliceToUsizeSlice(input_shape))); + } + readyNode.outputs.items[0].shape = shape; +} + inline fn compute_elu_output_shape(readyNode: *ReadyNode) !void { const input = readyNode.inputs.items[0] orelse { return error.InputTensorIsNull; diff --git a/src/Core/Tensor/TensorMath/lib_elementWise_math/op_floor.zig b/src/Core/Tensor/TensorMath/lib_elementWise_math/op_floor.zig new file mode 100644 index 0000000..e90222e --- /dev/null +++ b/src/Core/Tensor/TensorMath/lib_elementWise_math/op_floor.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const zant = @import("../../../../zant.zig"); +const pkg_allocator = zant.utils.allocator.allocator; + +const Tensor = zant.core.tensor.Tensor; // Import Tensor type + +pub fn get_floor_output_shape(input_shape: []const usize) ![]usize { + // Allocate and copy the input shape + const output_shape = try pkg_allocator.alloc(usize, input_shape.len); + errdefer pkg_allocator.free(output_shape); + + std.mem.copyForwards(usize, output_shape, input_shape); + + return output_shape; +} + +pub fn floor(comptime T: anytype, input: *Tensor(T)) !Tensor(T) { + comptime if (!(std.meta.eql(T, f64) or std.meta.eql(T, f32) or std.meta.eql(T, f16))) { + @compileError("Unsupported type in floor_lean"); + }; + + const output_shape = try get_floor_output_shape(input.shape); + var output = try Tensor(T).fromShape(&pkg_allocator, output_shape); + defer pkg_allocator.free(output_shape); + errdefer output.deinit(); + + try floor_lean(T, input, &output); + return output; +} + +pub fn floor_lean(comptime T: anytype, input: *Tensor(T), output: *Tensor(T)) !void { + // Compute floor(x) for each element of the tensor + for (input.data, output.data) |in_val, *out_val| { + if (std.math.isNan(in_val) or std.math.isInf(in_val) or in_val == 0 or in_val == @trunc(in_val)) { + out_val.* = in_val; + } else { + out_val.* = std.math.floor(in_val); + } + } +} diff --git a/src/Core/Tensor/TensorMath/op_oneHot.zig b/src/Core/Tensor/TensorMath/op_oneHot.zig new file mode 100644 index 0000000..1a8f695 --- /dev/null +++ b/src/Core/Tensor/TensorMath/op_oneHot.zig @@ -0,0 +1,133 @@ +const std = @import("std"); +const zant = @import("../../../zant.zig"); +const Tensor = zant.core.tensor.Tensor; +const TensorError = zant.utils.error_handler.TensorError; +const TensorMathError = zant.utils.error_handler.TensorMathError; +const pkgAllocator = zant.utils.allocator.allocator; + +pub fn get_onehot_output_shape(indices_shape: []const usize, depth: i64, axis: i64) ![]usize { + // Normalizza axis + const rank = @as(i64, @intCast(indices_shape.len)); + const normalized_axis = if (axis < 0) axis + rank + 1 else axis; + if (normalized_axis < 0 or normalized_axis > rank) { + return TensorMathError.InvalidAxes; + } + + // Crea la forma dell'output: rank(indices) + 1 + var output_shape = try pkgAllocator.alloc(usize, indices_shape.len + 1); + errdefer pkgAllocator.free(output_shape); + + // Copia indices_shape e inserisci depth nella posizione axis + for (indices_shape, 0..) |dim, i| { + if (i < normalized_axis) { + output_shape[i] = dim; + } else { + output_shape[i + 1] = dim; + } + } + output_shape[@intCast(normalized_axis)] = @intCast(depth); + + return output_shape; +} + +pub fn onehot(comptime T: type, indices: *const Tensor(i64), depth: *const Tensor(i64), values: *const Tensor(T), axis: i64) !Tensor(T) { + // Controlla i tipi + const allowed_types = [_]type{ + f32, f64, + bool, i8, + i16, i32, + i64, u8, + u16, u32, + u64, + }; + + var valid_type = false; + inline for (allowed_types) |Allowed| { + if (T == Allowed) { + valid_type = true; + break; + } + } + if (!valid_type) { + return TensorMathError.InvalidDataType; + } + + // Controlla depth (scalare o rango 1 con un elemento) + if (depth.shape.len > 1 or (depth.shape.len == 1 and depth.shape[0] != 1)) { + return TensorMathError.InvalidDepthShape; + } + const depth_val = depth.data[0]; + if (depth_val <= 0) { + return TensorMathError.InvalidDepthValue; + } + + // Controlla values (rango 1 con 2 elementi) + if (values.shape.len != 1 or values.shape[0] != 2) { + return TensorMathError.InvalidValuesShape; + } + + // Calcola la forma dell'output + const output_shape = try get_onehot_output_shape(indices.shape, depth_val, axis); + var output = try Tensor(T).fromShape(&pkgAllocator, output_shape); + errdefer output.deinit(); + defer pkgAllocator.free(output_shape); + + // Chiama la versione lean + try onehot_lean(T, indices, depth_val, values, axis, &output); + + return output; +} + +pub fn onehot_lean(comptime T: type, indices: *const Tensor(i64), depth: i64, values: *const Tensor(T), axis: i64, output: *Tensor(T)) !void { + // Inizializza l'output con off_value + for (output.data) |*val| { + val.* = values.data[0]; // off_value + } + + // Normalizza axis + const rank = @as(i64, @intCast(indices.shape.len)); + const normalized_axis = if (axis < 0) axis + rank + 1 else axis; + + // Itera sugli indici + const total_elements = blk: { + var prod: usize = 1; + for (indices.shape) |dim| prod *= dim; + break :blk prod; + }; + + for (0..total_elements) |flat_idx| { + const index_val = indices.data[flat_idx]; + + // Ignora indici fuori range + if (index_val < -depth or index_val >= depth) { + continue; + } + const index = if (index_val < 0) index_val + depth else index_val; + + // Calcola le coordinate multi-dimensionali + var input_coords = try pkgAllocator.alloc(usize, indices.shape.len); + defer pkgAllocator.free(input_coords); + var temp_idx = flat_idx; + for (indices.shape, 0..) |dim, i| { + input_coords[indices.shape.len - 1 - i] = temp_idx % dim; + temp_idx /= dim; + } + + // Calcola le coordinate dell'output + var output_coords = try pkgAllocator.alloc(usize, indices.shape.len + 1); + defer pkgAllocator.free(output_coords); + for (input_coords, 0..) |coord, i| { + if (i < normalized_axis) { + output_coords[i] = coord; + } else { + output_coords[i + 1] = coord; + } + } + + output_coords[@intCast(normalized_axis)] = @intCast(index); + + // Imposta on_value + const output_idx = try output.get_flat_index(output_coords); + output.data[output_idx] = values.data[1]; // on_value + } +} diff --git a/src/Core/Tensor/TensorMath/tensor_math_standard.zig b/src/Core/Tensor/TensorMath/tensor_math_standard.zig index 13ffca6..4cd0254 100644 --- a/src/Core/Tensor/TensorMath/tensor_math_standard.zig +++ b/src/Core/Tensor/TensorMath/tensor_math_standard.zig @@ -44,6 +44,13 @@ const op_clip = @import("lib_elementWise_math/op_clip.zig"); pub const clip = op_clip.clip; pub const clip_lean = op_clip.lean_clip; +//--floor +const op_floor = @import("lib_elementWise_math/op_floor.zig"); + +pub const floor = op_floor.floor; +pub const floor_lean = op_floor.floor_lean; +pub const get_floor_output_shape = op_floor.get_floor_output_shape; + //--unsqueeze const op_unsqueeze = @import("lib_shape_math/op_unsqueeze.zig"); @@ -118,6 +125,13 @@ pub const mean_standard = op_mean.mean_standard; pub const mean_lean = op_mean.mean_lean; pub const get_mean_output_shape = op_mean.get_mean_output_shape; +//----------- importing standard onehot method ---------- +const op_oneHot = @import("op_oneHot.zig"); + +pub const oneHot = op_oneHot.onehot; +pub const oneHot_lean = op_oneHot.onehot_lean; +pub const get_oneHot_output_shape = op_oneHot.get_onehot_output_shape; + // ---------- importing standard Batch Normalization ---------- const op_bachNorm = @import("op_batchNormalization.zig"); pub const batchNormalization = op_bachNorm.batchNormalization; diff --git a/src/Core/Tensor/tensor.zig b/src/Core/Tensor/tensor.zig index 1d8bf3a..c5f2f74 100644 --- a/src/Core/Tensor/tensor.zig +++ b/src/Core/Tensor/tensor.zig @@ -120,7 +120,11 @@ pub fn Tensor(comptime T: type) type { @memcpy(tensorShape, shape); const tensorData = try allocator.alloc(T, total_size); - @memset(tensorData, 0); + if (T == bool) { + @memset(tensorData, false); + } else { + @memset(tensorData, 0); + } return @This(){ .data = tensorData, @@ -510,7 +514,7 @@ pub fn Tensor(comptime T: type) type { } // Helper function to calculate the flat index from multi-dimensional indices - fn get_flat_index(self: *Tensor(T), indices: []usize) !usize { + pub fn get_flat_index(self: *Tensor(T), indices: []usize) !usize { if (indices.len != self.shape.len) return TensorError.InvalidIndices; var flat_index: usize = 0; diff --git a/src/Utils/errorHandler.zig b/src/Utils/errorHandler.zig index 208f381..4586081 100644 --- a/src/Utils/errorHandler.zig +++ b/src/Utils/errorHandler.zig @@ -63,6 +63,12 @@ pub const TensorMathError = error{ InvalidZeroPointShape, OutputShapeMismatch, ShapeMismatch, + InvalidDepthShape, + InvalidDepthValue, + InvalidValuesShape, + IndexOutOfBounds, + InvalidShape, + InvalidOutputLength, }; /// Tensor errors diff --git a/tests/CodeGen/Python-ONNX/available_operations.txt b/tests/CodeGen/Python-ONNX/available_operations.txt index 1f3c59f..ae497f7 100644 --- a/tests/CodeGen/Python-ONNX/available_operations.txt +++ b/tests/CodeGen/Python-ONNX/available_operations.txt @@ -8,6 +8,7 @@ Div Div Elu Flatten +Floor Gather Gemm Identity @@ -16,6 +17,7 @@ MatMul MaxPool Mean Mul +OneHot Relu Reshape Sigmoid diff --git a/tests/CodeGen/Python-ONNX/onnx_gen.py b/tests/CodeGen/Python-ONNX/onnx_gen.py index a9992b9..0b4d2e3 100644 --- a/tests/CodeGen/Python-ONNX/onnx_gen.py +++ b/tests/CodeGen/Python-ONNX/onnx_gen.py @@ -27,7 +27,7 @@ def generate_fuzz_model(op_name): output_names = [f"{op_name}_param_out_{i}" for i in range(5)] metadata = {} - if op_name in ["Relu", "Sigmoid", "Ceil", "Tanh", "Identity", "Neg", "Shape"]: + if op_name in ["Relu", "Sigmoid", "Ceil", "Tanh", "Identity", "Neg", "Shape", "floor"]: # Operatori a singolo input con forma casuale (rank=4) shape = [1, random.randint(1,4), random.randint(10,50), random.randint(10,50)] # Crea dati casuali e li inserisce come initializer @@ -170,6 +170,61 @@ def generate_fuzz_model(op_name): metadata = {"input_shapes": [shape, shape2], "output_shapes": [out_shape]} return [input_info], output_info, [node], initializers, metadata + elif op_name == "OneHot": + # Operatore OneHot: genera indices, depth e values come initializer + # Genera un tensore indices con rango casuale (1 o 2) + rank = random.randint(1, 2) + indices_shape = random_shape(rank, min_dim=2, max_dim=3) + max_index = 3 # Limite massimo per gli indici + indices_data = np.random.randint(0, max_index, size=indices_shape).astype(np.int64) + indices_tensor = helper.make_tensor(input_names[0], TensorProto.INT64, indices_shape, indices_data.flatten().tolist()) + initializers.append(indices_tensor) + + # Genera depth (scalare) + depth_value = random.randint(3, max_index) # Valore positivo per depth + depth_tensor = helper.make_tensor(input_names[1], TensorProto.INT64, [], [depth_value]) + initializers.append(depth_tensor) + + # Genera values (tensore 1D di lunghezza 2, tipo float32) + values_data = np.array([0.0, 1.0], dtype=np.float32) # [off_value, on_value] + values_tensor = helper.make_tensor(input_names[2], TensorProto.FLOAT, [2], values_data.tolist()) + initializers.append(values_tensor) + + # Scegli un axis valido + output_rank = rank + 1 + # axis = random.randint(-output_rank, output_rank - 1) + axis = random.randint(max(-output_rank, -1000), min(output_rank - 1, 1000)) + + # Calcola la forma dell'output + out_shape = indices_shape.copy() + normalized_axis = axis if axis >= 0 else axis + output_rank + out_shape.insert(normalized_axis, depth_value) + + # Crea il nodo OneHot + output_info = helper.make_tensor_value_info(output_names[0], TensorProto.FLOAT, out_shape) + node = helper.make_node( + op_name, + inputs=[input_names[0], input_names[1], input_names[2]], + outputs=[output_names[0]], + axis=axis, + name=f"{op_name}_node_axis{axis}" + ) + + # Input info fittizio + input_info = helper.make_tensor_value_info("useless_input", TensorProto.INT64, indices_shape) + + # Metadati + metadata = { + "input_shapes": [indices_shape, [], [2]], # Forme di indices, depth, values + "output_shapes": [out_shape], + "axis": axis, + "depth": depth_value, + "indices": indices_data.flatten().tolist()[:5], # Solo i primi 5 per debug + "values": values_data.tolist() + } + + return [input_info], output_info, [node], initializers, metadata + elif op_name == "Gather": # First input: data; second input: indices shape = [5, random.randint(5,10)] diff --git a/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig b/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig index d95c7c4..124c8af 100644 --- a/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig +++ b/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig @@ -672,3 +672,30 @@ test "clip f32 large tensor with SIMD" { try std.testing.expectEqualSlices(f32, expected_data, output.data); } + +test "floor_standard - basic case with special values" { + std.debug.print("\n test: floor_standard - basic case with special values\n", .{}); + const allocator = pkgAllocator.allocator; + + // Input + var input_array = [_]f32{ 1.7, -2.3, 3.0, 0.0, -0.0, std.math.nan(f32), std.math.inf(f32), -std.math.inf(f32) }; + var shape = [_]usize{8}; + var input = try Tensor(f32).fromArray(&allocator, &input_array, &shape); + defer input.deinit(); + + // Floor + var result = try TensMath.floor(f32, &input); + defer result.deinit(); + + // Expected output + const expected = [_]f32{ 1.0, -3.0, 3.0, 0.0, -0.0, std.math.nan(f32), std.math.inf(f32), -std.math.inf(f32) }; + try std.testing.expectEqual(expected.len, result.data.len); + for (expected, result.data) |exp, res| { + if (std.math.isNan(exp)) { + try std.testing.expect(std.math.isNan(res)); + } else { + try std.testing.expectEqual(exp, res); + } + } + try std.testing.expectEqualSlices(usize, &shape, result.shape); +} diff --git a/tests/Core/Tensor/TensorMath/test_op_oneHot.zig b/tests/Core/Tensor/TensorMath/test_op_oneHot.zig new file mode 100644 index 0000000..4ac35d4 --- /dev/null +++ b/tests/Core/Tensor/TensorMath/test_op_oneHot.zig @@ -0,0 +1,169 @@ +const std = @import("std"); +const zant = @import("zant"); +const pkgAllocator = zant.utils.allocator; +const TensMath = zant.core.tensor.math_standard; +const Tensor = zant.core.tensor.Tensor; +const TensorMathError = zant.utils.error_handler.TensorMathError; + +test "onehot_standard f64 - basic case" { + std.debug.print("\n test: onehot_standard f64 - basic case", .{}); + const allocator = pkgAllocator.allocator; + + var indices_array = [_]i64{ 0, 1 }; + var indices_shape = [_]usize{2}; + + var depth_array = [_]i64{3}; + var depth_shape = [_]usize{1}; + + var values_array = [_]f64{ 0.0, 10.0 }; + var values_shape = [_]usize{2}; + + var indices = try Tensor(i64).fromArray(&allocator, &indices_array, &indices_shape); + var depth = try Tensor(i64).fromArray(&allocator, &depth_array, &depth_shape); + var values = try Tensor(f64).fromArray(&allocator, &values_array, &values_shape); + + defer indices.deinit(); + defer depth.deinit(); + defer values.deinit(); + + var result = try TensMath.oneHot(f64, &indices, &depth, &values, -1); + defer result.deinit(); + + try std.testing.expectEqualSlices(f64, result.data, &[6]f64{ 10.0, 0.0, 0.0, 0.0, 10.0, 0.0 }); +} + +test "onehot_standard f64 - indices out of range" { + std.debug.print("\n test: onehot_standard f64 - indices out of range", .{}); + const allocator = pkgAllocator.allocator; + + var indices_array = [_]i64{ -4, 0, 3, 1 }; // -4 e 3 sono fuori range [-3, 2] + var indices_shape = [_]usize{4}; + + var depth_array = [_]i64{3}; + var depth_shape = [_]usize{1}; + + var values_array = [_]f64{ 0.0, 10.0 }; + var values_shape = [_]usize{2}; + + var indices = try Tensor(i64).fromArray(&allocator, &indices_array, &indices_shape); + var depth = try Tensor(i64).fromArray(&allocator, &depth_array, &depth_shape); + var values = try Tensor(f64).fromArray(&allocator, &values_array, &values_shape); + + defer indices.deinit(); + defer depth.deinit(); + defer values.deinit(); + + var result = try TensMath.oneHot(f64, &indices, &depth, &values, -1); + defer result.deinit(); + + try std.testing.expectEqualSlices(f64, result.data, &[12]f64{ 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0 }); + try std.testing.expectEqualSlices(usize, &[_]usize{ 4, 3 }, result.shape); +} + +test "onehot_standard bool - axis 0" { + std.debug.print("\n test: onehot_standard bool - axis 0", .{}); + const allocator = pkgAllocator.allocator; + + var indices_array = [_]i64{ 0, 1 }; + var indices_shape = [_]usize{2}; + + var depth_array = [_]i64{3}; + var depth_shape = [_]usize{1}; + + var values_array = [_]bool{ false, true }; + var values_shape = [_]usize{2}; + + var indices = try Tensor(i64).fromArray(&allocator, &indices_array, &indices_shape); + var depth = try Tensor(i64).fromArray(&allocator, &depth_array, &depth_shape); + var values = try Tensor(bool).fromArray(&allocator, &values_array, &values_shape); + + defer indices.deinit(); + defer depth.deinit(); + defer values.deinit(); + + var result = try TensMath.oneHot(bool, &indices, &depth, &values, 0); + defer result.deinit(); + + try std.testing.expectEqualSlices(bool, result.data, &[6]bool{ true, false, false, true, false, false }); + try std.testing.expectEqualSlices(usize, &[_]usize{ 3, 2 }, result.shape); +} + +test "onehot_standard f64 - 2D indices" { + std.debug.print("\n test: onehot_standard f64 - 2D indices", .{}); + const allocator = pkgAllocator.allocator; + + var indices_array = [_]i64{ 0, 1, 2, 0 }; // Matrice 2x2 + var indices_shape = [_]usize{ 2, 2 }; + + var depth_array = [_]i64{3}; + var depth_shape = [_]usize{1}; + + var values_array = [_]f64{ 0.0, 10.0 }; + var values_shape = [_]usize{2}; + + var indices = try Tensor(i64).fromArray(&allocator, &indices_array, &indices_shape); + var depth = try Tensor(i64).fromArray(&allocator, &depth_array, &depth_shape); + var values = try Tensor(f64).fromArray(&allocator, &values_array, &values_shape); + + defer indices.deinit(); + defer depth.deinit(); + defer values.deinit(); + + var result = try TensMath.oneHot(f64, &indices, &depth, &values, -1); + defer result.deinit(); + + try std.testing.expectEqualSlices(f64, result.data, &[12]f64{ + 10.0, 0.0, 0.0, 0.0, 10.0, 0.0, + 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, + }); + try std.testing.expectEqualSlices(usize, &[_]usize{ 2, 2, 3 }, result.shape); +} + +test "onehot_standard f64 - invalid depth" { + std.debug.print("\n test: onehot_standard f64 - invalid depth", .{}); + const allocator = pkgAllocator.allocator; + + var indices_array = [_]i64{ 0, 1 }; + var indices_shape = [_]usize{2}; + + var depth_array = [_]i64{0}; // invalid depth + var depth_shape = [_]usize{1}; + + var values_array = [_]f64{ 0.0, 10.0 }; + var values_shape = [_]usize{2}; + + var indices = try Tensor(i64).fromArray(&allocator, &indices_array, &indices_shape); + var depth = try Tensor(i64).fromArray(&allocator, &depth_array, &depth_shape); + var values = try Tensor(f64).fromArray(&allocator, &values_array, &values_shape); + + defer indices.deinit(); + defer depth.deinit(); + defer values.deinit(); + + try std.testing.expectError(TensorMathError.InvalidDepthValue, TensMath.oneHot(f64, &indices, &depth, &values, -1)); +} + +test "onehot_standard f64 - invalid axis" { + std.debug.print("\n test: onehot_standard f64 - invalid axis", .{}); + const allocator = pkgAllocator.allocator; + + var indices_array = [_]i64{ 0, 1 }; + var indices_shape = [_]usize{2}; + + var depth_array = [_]i64{3}; + var depth_shape = [_]usize{1}; + + var values_array = [_]f64{ 0.0, 10.0 }; + var values_shape = [_]usize{2}; + + var indices = try Tensor(i64).fromArray(&allocator, &indices_array, &indices_shape); + var depth = try Tensor(i64).fromArray(&allocator, &depth_array, &depth_shape); + var values = try Tensor(f64).fromArray(&allocator, &values_array, &values_shape); + + defer indices.deinit(); + defer depth.deinit(); + defer values.deinit(); + + // axis = 2 รจ fuori range per rank=1 (range valido: [-2, 1]) + try std.testing.expectError(TensorMathError.InvalidAxes, TensMath.oneHot(f64, &indices, &depth, &values, 2)); +} diff --git a/tests/test_lib.zig b/tests/test_lib.zig index d79ffcd..db72ce7 100644 --- a/tests/test_lib.zig +++ b/tests/test_lib.zig @@ -1,11 +1,54 @@ const std = @import("std"); +const test_options = @import("test_options"); +const test_name = test_options.test_name; -test { - std.testing.log_level = .info; - - comptime { +comptime { + // if (test_name.len == 0 or std.mem.eql(u8, test_name, "modelSlimTemplate")) { + // _ = @import("CodeGen/test_model.slim.template.zig"); + // } + // if (test_name.len == 0 or std.mem.eql(u8, test_name, "modelTemplate")) { + // _ = @import("Codegen/test_model.template.zig"); + // } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "libElementWise")) { + _ = @import("Core/Tensor/TensorMath/test_lib_elementWise_math.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "libLogical")) { + _ = @import("Core/Tensor/TensorMath/test_lib_logical_math.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "libReduction")) { + _ = @import("Core/Tensor/TensorMath/test_lib_reduction_math.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "libShape")) { + _ = @import("Core/Tensor/TensorMath/test_lib_shape_math.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "opConvolution")) { _ = @import("Core/Tensor/TensorMath/test_op_convolution.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "opElu")) { + _ = @import("Core/Tensor/TensorMath/test_op_elu.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "opGemm")) { + _ = @import("Core/Tensor/TensorMath/test_op_gemm.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "opOneHot")) { + _ = @import("Core/Tensor/TensorMath/test_op_oneHot.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "opMat")) { + _ = @import("Core/Tensor/TensorMath/test_op_mat_mul.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "opPooling")) { + _ = @import("Core/Tensor/TensorMath/test_op_pooling.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "tensorMath")) { + _ = @import("Core/Tensor/TensorMath/test_tensor_math.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "tensor")) { + _ = @import("Core/Tensor/test_tensor.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "core")) { _ = @import("Core/test_core.zig"); + } + if (test_name.len == 0 or std.mem.eql(u8, test_name, "utils")) { _ = @import("Utils/test_utils.zig"); } }