diff --git a/build.zig b/build.zig index 664adba0..330cac5a 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 645e8b75..40c35dab 100644 --- a/src/CodeGen/math_handler.zig +++ b/src/CodeGen/math_handler.zig @@ -92,10 +92,14 @@ 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")) { try write_gemm(writer, node); + } else if (std.mem.eql(u8, node.nodeProto.op_type, "Gelu")) { + try write_gelu(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Identity")) { try write_identity(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "LeakyRelu")) { @@ -111,7 +115,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")) { @@ -130,6 +134,8 @@ pub fn write_math_op(writer: std.fs.File.Writer, node: *ReadyNode) !void { try write_slice(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Split")) { try write_split(writer, node); + } else if (std.mem.eql(u8, node.nodeProto.op_type, "Sqrt")) { + try write_sqrt(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Sub")) { try write_sub(writer, node); } else if (std.mem.eql(u8, node.nodeProto.op_type, "Sum")) { @@ -366,6 +372,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 +2274,67 @@ 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_gelu(writer: std.fs.File.Writer, node: *ReadyNode) !void { + var approximate: []const u8 = "none"; + for (node.nodeProto.attribute) |attr| { + if (std.mem.eql(u8, attr.name, "approximate")) { + if (attr.type == AttributeType.STRING) approximate = attr.s; + } + } + + 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.gelu_lean(T, {s}, "{s}", &tensor_{s}) + , .{ + input_tensor_string, + approximate, + 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: @@ -2208,6 +2366,30 @@ inline fn write_tanh(writer: std.fs.File.Writer, node: *ReadyNode) !void { }); } +inline fn write_sqrt(writer: std.fs.File.Writer, node: *ReadyNode) !void { + 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.sqrt_lean(T, {s}, &tensor_{s}) + , .{ + input_tensor_string, + try utils.getSanitizedName(node.outputs.items[0].name), + }); +} + inline fn write_ceil(writer: std.fs.File.Writer, node: *ReadyNode) !void { // https://onnx.ai/onnx/operators/onnx__Ceil.html // INPUTS: diff --git a/src/CodeGen/shape_handler.zig b/src/CodeGen/shape_handler.zig index d2d25e5a..1c5ca24a 100644 --- a/src/CodeGen/shape_handler.zig +++ b/src/CodeGen/shape_handler.zig @@ -80,11 +80,17 @@ 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")) { //https://onnx.ai/onnx/operators/onnx__Gemm.html try compute_gemm_output_shape(readyNode); + } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Gelu")) { + //https://onnx.ai/onnx/operators/onnx__Gelu.html + try compute_gelu_output_shape(readyNode); } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "LeakyRelu")) { try compute_leaky_relu_output_shape(readyNode); } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "LogSoftmax")) { @@ -100,8 +106,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); @@ -127,6 +133,8 @@ pub fn compute_output_shape(readyNode: *ReadyNode) !void { } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Split")) { //https://onnx.ai/onnx/operators/onnx__Split.html try compute_split_output_shape(readyNode); + } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Sqrt")) { + try compute_sqrt_output_shape(readyNode); } else if (std.mem.eql(u8, readyNode.nodeProto.op_type, "Sub")) { //https://onnx.ai/onnx/operators/onnx__Sub.html try compute_Sub_output_shape(readyNode); @@ -438,6 +446,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.?}); @@ -1010,6 +1105,24 @@ pub fn compute_concat_output_shape(readyNode: *ReadyNode) !void { // Codegen_log.debug("\n Cleanup complete", .{}); } +inline fn compute_sqrt_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; + Codegen_log.info("\n input_shape: []i64 = {any}", .{input_shape}); + + shape = try utils.usizeSliceToI64Slice(try tensorMath.get_sqrt_output_shape(try utils.i64SliceToUsizeSlice(input_shape))); + } + readyNode.outputs.items[0].shape = shape; +} + inline fn compute_tanh_output_shape(readyNode: *ReadyNode) !void { const input = readyNode.inputs.items[0] orelse { return error.InputTensorIsNull; @@ -1028,6 +1141,42 @@ inline fn compute_tanh_output_shape(readyNode: *ReadyNode) !void { readyNode.outputs.items[0].shape = shape; } +inline fn compute_gelu_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; + Codegen_log.info("\n input_shape: []i64 = {any}", .{input_shape}); + + shape = try utils.usizeSliceToI64Slice(try tensorMath.get_gelu_output_shape(try utils.i64SliceToUsizeSlice(input_shape))); + } + 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 00000000..e90222e9 --- /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/lib_elementWise_math/op_gelu.zig b/src/Core/Tensor/TensorMath/lib_elementWise_math/op_gelu.zig new file mode 100644 index 00000000..ad676a15 --- /dev/null +++ b/src/Core/Tensor/TensorMath/lib_elementWise_math/op_gelu.zig @@ -0,0 +1,94 @@ +const std = @import("std"); +const zant = @import("../../../../zant.zig"); +const pkg_allocator = zant.utils.allocator.allocator; + +const Tensor = zant.core.tensor.Tensor; + +pub fn get_gelu_output_shape(input_shape: []const usize) ![]usize { + 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 gelu(comptime T: anytype, input: *Tensor(T), approximate: ?[]const u8) !Tensor(T) { + //check type + comptime if (!(std.meta.eql(T, f16) or std.meta.eql(T, f32) or std.meta.eql(T, f64))) { + @compileError("unsupported type in Gelu"); + }; + + //check approximate + if (!(std.mem.eql(u8, approximate.?, "tanh") or std.mem.eql(u8, approximate.?, "none"))) { + return error.ApproximateError; + } + + //compute outputshape + const output_shape = try get_gelu_output_shape(input.shape); + defer pkg_allocator.free(output_shape); + + var output = try Tensor(T).fromShape(&pkg_allocator, output_shape); + errdefer output.deinit(); + + //call lean version + try gelu_lean(T, input, approximate, &output); + + return output; +} + +pub fn gelu_lean(comptime T: type, input: *Tensor(T), approximate: ?[]const u8, output: *Tensor(T)) !void { + if (input.data.len != output.data.len) { + return error.ShapeMismatch; + } + + const sqrt_2 = @sqrt(@as(f32, 2.0)); + const sqrt_2_over_pi = @sqrt(@as(f32, 2.0 / std.math.pi)); + const coeff = @as(f32, 0.044715); + + for (input.data, output.data) |x, *out_val| { + const x_f32 = @as(f32, @floatCast(x)); + if (std.mem.eql(u8, approximate.?, "tanh")) { + // x * 0.5 * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3))) + const x_cubed = x_f32 * x_f32 * x_f32; + const tanh_arg = sqrt_2_over_pi * (x_f32 + coeff * x_cubed); + const tanh_val = std.math.tanh(tanh_arg); + out_val.* = @as(T, @floatCast(x_f32 * 0.5 * (1.0 + tanh_val))); + } else { + // x * 0.5 * (1 + erf(x / sqrt(2))) + const erf_arg = x_f32 / sqrt_2; + const erf_val = erf(erf_arg); + out_val.* = @as(T, @floatCast(x_f32 * 0.5 * (1.0 + erf_val))); + } + } +} + +/// Computes the definite integral of f(x) between a and b using the trapezoidal method +pub fn integrateTrapezoid( + comptime T: type, + f: fn (T) T, + a: T, + b: T, + n: usize, +) T { + const h = (b - a) / @as(T, @floatFromInt(n)); + var sum = (f(a) + f(b)) / @as(T, 2); + + var i: usize = 1; + while (i < n) : (i += 1) { + const x = a + h * @as(T, @floatFromInt(i)); + sum += f(x); + } + + return h * sum; +} + +pub fn erfIntegrand(x: f64) f64 { + return std.math.exp(-x * x); +} + +pub fn erf(x: f64) f64 { + const sqrt_pi_inv = 2.0 / std.math.sqrt(std.math.pi); + const n = 10000; // Number of subintervals, increase for higher precision + return sqrt_pi_inv * integrateTrapezoid(f64, erfIntegrand, 0.0, x, n); +} diff --git a/src/Core/Tensor/TensorMath/lib_elementWise_math/op_sqrt.zig b/src/Core/Tensor/TensorMath/lib_elementWise_math/op_sqrt.zig new file mode 100644 index 00000000..364f7860 --- /dev/null +++ b/src/Core/Tensor/TensorMath/lib_elementWise_math/op_sqrt.zig @@ -0,0 +1,39 @@ +const std = @import("std"); +const zant = @import("../../../../zant.zig"); +const pkg_allocator = zant.utils.allocator.allocator; + +const Tensor = zant.core.tensor.Tensor; + +pub fn get_sqrt_output_shape(input_shape: []const usize) ![]usize { + const output_shape = try pkg_allocator.alloc(usize, input_shape.len); + errdefer pkg_allocator.free(output_shape); + + @memcpy(output_shape, input_shape); + + return output_shape; +} + +pub fn sqrt_lean(comptime T: anytype, input: *Tensor(T), output: *Tensor(T)) !void { + for (input.data, output.data) |in_val, *out_val| { + if (in_val < 0) { + out_val.* = std.math.nan(T); + } else { + out_val.* = std.math.pow(T, in_val, 0.5); + } + } +} + +pub fn sqrt(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 sqrt_lean"); + }; + + const output_shape = try get_sqrt_output_shape(input.shape); + defer pkg_allocator.free(output_shape); + + var output = try Tensor(T).fromShape(&pkg_allocator, output_shape); + errdefer output.deinit(); + + try sqrt_lean(T, input, &output); + return output; +} diff --git a/src/Core/Tensor/TensorMath/op_oneHot.zig b/src/Core/Tensor/TensorMath/op_oneHot.zig new file mode 100644 index 00000000..1a8f6955 --- /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 13ffca6e..f2dc0838 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; @@ -221,6 +235,13 @@ pub const tanh = tanhy.tanh; pub const tanh_lean = tanhy.tanh_lean; pub const get_tanh_output_shape = tanhy.get_tanh_output_shape; +//--gelu +const Gelu = @import("lib_elementWise_math/op_gelu.zig"); + +pub const gelu = Gelu.gelu; +pub const gelu_lean = Gelu.gelu_lean; +pub const get_gelu_output_shape = Gelu.get_gelu_output_shape; + //--ceil const Ceil = @import("lib_elementWise_math/op_ceil.zig"); @@ -228,6 +249,13 @@ pub const ceil = Ceil.ceil; pub const ceil_lean = Ceil.ceil_lean; pub const get_ceil_output_shape = Ceil.get_ceil_output_shape; +//--sqrt +const Sqrt = @import("lib_elementWise_math/op_sqrt.zig"); + +pub const sqrt = Sqrt.sqrt; +pub const sqrt_lean = Sqrt.sqrt_lean; +pub const get_sqrt_output_shape = Sqrt.get_sqrt_output_shape; + // ---------- importing standard basic methods ---------- const logical_math_lib = @import("lib_logical_math.zig"); pub const isOneHot = logical_math_lib.isOneHot; diff --git a/src/Core/Tensor/tensor.zig b/src/Core/Tensor/tensor.zig index 1d8bf3a9..c5f2f74d 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 208f381a..45860812 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 1f3c59f5..ae497f70 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 a9992b9e..9d748265 100644 --- a/tests/CodeGen/Python-ONNX/onnx_gen.py +++ b/tests/CodeGen/Python-ONNX/onnx_gen.py @@ -27,11 +27,14 @@ 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", "Sqrt"]: # 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 - data = np.random.randn(*shape).astype(np.float32) + if op_name == "Sqrt": + data = np.abs(np.random.randn(*shape)).astype(np.float32) + else: + data = np.random.randn(*shape).astype(np.float32) init_tensor = helper.make_tensor(input_names[0], TensorProto.FLOAT, shape, data.flatten().tolist()) initializers.append(init_tensor) @@ -42,6 +45,30 @@ def generate_fuzz_model(op_name): name=f"{op_name}_node") metadata = {"input_shapes": [shape], "output_shapes": [shape]} return [input_info], output_info, [node], initializers, metadata + + elif op_name == "Gelu": + shape = [1, random.randint(1,4), random.randint(10,50), random.randint(10,50)] + approximate = random.choice(["none", "tanh"]) + + data = np.random.randn(*shape).astype(np.float32) + + init_tensor = helper.make_tensor(input_names[0], TensorProto.FLOAT, shape, data.flatten().tolist()) + initializers.append(init_tensor) + + input_info = helper.make_tensor_value_info("useless_input", TensorProto.FLOAT, shape) + output_info = helper.make_tensor_value_info(output_names[0], TensorProto.FLOAT, shape) + + node = helper.make_node( + op_name, + inputs=[input_names[0]], + outputs=[output_names[0]], + approximate=approximate, + name=f"{op_name}node_approx{approximate}", + ) + + metadata = {"input_shapes": [shape], "output_shapes": [shape], "approximate": approximate} + return [input_info], output_info, [node], initializers, metadata + elif op_name == "LeakyRelu": shape = [1, random.randint(1,4), random.randint(10,50), random.randint(10,50)] @@ -170,6 +197,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)] @@ -943,7 +1025,10 @@ def generate_model(op_name, filename, model_id=0): doc_string=f"Test graph for {op_name} operation with configuration: {metadata}" ) - opset_imports = [helper.make_opsetid("", 13)] + opset_imports = [ + helper.make_opsetid("", 13), # Standard ONNX opset + helper.make_opsetid("", 20) + ] model = helper.make_model( graph, diff --git a/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig b/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig index d95c7c4e..a3da1e57 100644 --- a/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig +++ b/tests/Core/Tensor/TensorMath/test_lib_elementWise_math.zig @@ -8,6 +8,89 @@ const TensorError = zant.utils.error_handler.TensorError; const TensorMathError = zant.utils.error_handler.TensorMathError; const tests_log = std.log.scoped(.lib_elementWise); + +test "test sqrt with valid f32 tensor" { + const allocator = pkgAllocator.allocator; + + var inputArray: [2][2]f32 = [_][2]f32{ + [_]f32{ -5.0, 7.0 }, + [_]f32{ 0.0, 11.0 }, + }; + + var shape = [_]usize{ 2, 2 }; + + var input = try Tensor(f32).fromArray(&allocator, &inputArray, &shape); + defer input.deinit(); + + var result = try TensMath.sqrt(f32, &input); + defer result.deinit(); + + try std.testing.expect(std.math.isNan(result.data[0])); + try std.testing.expectEqual(std.math.pow(f32, 7.0, 0.5), result.data[1]); + try std.testing.expectEqual(std.math.pow(f32, 0.0, 0.5), result.data[2]); + try std.testing.expectEqual(std.math.pow(f32, 11.0, 0.5), result.data[3]); +} + +test "test gelu with approximate = none and valid f32 tensor" { + const allocator = std.testing.allocator; + + var inputArray: [2][3]f32 = [_][3]f32{ + [_]f32{ 0.0, 1.0, -1.0 }, + [_]f32{ 0.5, -0.5, 2.0 }, + }; + var shape: [2]usize = [_]usize{ 2, 3 }; + + var tensor = try Tensor(f32).fromArray(&allocator, &inputArray, &shape); + defer tensor.deinit(); + + var result = try TensMath.gelu(f32, &tensor, "none"); + defer result.deinit(); + + const expected_values = [_]f32{ + 0.0000000000000000, + 0.8413447460685429, + -0.15865525393145707, + 0.34573123063700656, + -0.15426876936299344, + 1.9544997361036416, + }; + + const epsilon: f32 = 1e-6; + for (0..result.size) |i| { + try std.testing.expect(std.math.approxEqAbs(f32, result.data[i], expected_values[i], epsilon)); + } +} + +test "test gelu with approximate = tanh and valid f32 tensor" { + const allocator = std.testing.allocator; + + var inputArray: [2][3]f32 = [_][3]f32{ + [_]f32{ 0.0, 1.0, -1.0 }, + [_]f32{ 0.5, -0.5, 2.0 }, + }; + var shape: [2]usize = [_]usize{ 2, 3 }; + + var tensor = try Tensor(f32).fromArray(&allocator, &inputArray, &shape); + defer tensor.deinit(); + + var result = try TensMath.gelu(f32, &tensor, "tanh"); + defer result.deinit(); + + const expected_values = [_]f32{ + 0.00000000, // GELU(0.0) = 0.5 * 0 * (1 + tanh(...)) = 0 + 0.8411919906082768, // GELU(1.0) ≈ 0.84119225 + -0.15880800939172324, // GELU(-1.0) ≈ -0.15880775 + 0.34571400982514394, // GELU(0.5) ≈ 0.34567261 + -0.15428599017485606, // GELU(-0.5) ≈ -0.15432739 + 1.954597694087775, // GELU(2.0) ≈ 1.95450306 + }; + + const epsilon: f32 = 1e-5; + for (0..result.size) |i| { + try std.testing.expect(std.math.approxEqAbs(f32, result.data[i], expected_values[i], epsilon)); + } +} + test "Sum two tensors on CPU architecture" { tests_log.info("\n test: Sum two tensors on CPU architecture", .{}); const allocator = pkgAllocator.allocator; @@ -672,3 +755,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" { + tests_log.info("\n test: floor_standard - basic case with special values", .{}); + 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 00000000..4ac35d41 --- /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 d79ffcd5..db72ce75 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"); } }