diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1130dd0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +# This repository has never had a workflow that builds it. +# +# That is the same gap that hid sixteen defects in gHashTag/zig-golden-float and +# five in gHashTag/zig-hdc: a package can look maintained, be imported by name, +# and not compile for anybody, because nothing ever asked it to. +# +# Pinned to 0.15.2 because that is the version the packages in this estate that +# would consume it are built with. Building on something else would prove the +# package works for somebody who is not the consumer. + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.15.2 + - run: zig build + - run: zig build test diff --git a/build.zig b/build.zig index 9fd25d8..1a563fb 100644 --- a/build.zig +++ b/build.zig @@ -1,45 +1,58 @@ // zig-half build script +// +// The previous version of this file had never compiled and could not have. It +// declared `pub fn test(b: *std.Build)`, and `test` is a keyword; it assigned +// `b.standardTargetOptions` without calling it; it passed two arguments to +// installArtifact and read `b.step` as a field. None of that is a version +// difference -- it is a sketch shaped like a build script, and nothing in this +// repository ever ran it, because there was no workflow that built anything. +// +// Rewritten for 0.15, the version the packages that would consume this are +// built with. const std = @import("std"); -pub fn build(b: *std.Build) !void { - const target = b.standardTargetOptions; - const optimize = b.standardOptimizeOption; +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); - const lib = b.addStaticLibrary(.{ - .name = "zig-half", - .root_source_file = "src/root.zig", + const root = b.addModule("zig-half", .{ + .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); + _ = root; - b.installArtifact(lib, .{}); -} - -pub fn test(b: *std.Build) !void { - const test_step = b.addTest(.{ - .root_source_file = "src/f16_utils.zig", - .name = "f16_utils_tests", - }); - - _ = b.addTest(.{ - .root_source_file = "src/f16_shadow.zig", - .name = "f16_shadow_tests", - }); - - _ = b.addTest(.{ - .root_source_file = "src/sparse_simd.zig", - .name = "sparse_simd_tests", - }); - - _ = b.addTest(.{ - .root_source_file = "src/ternary_pack.zig", - .name = "ternary_pack_tests", - }); - - _ = b.addTest(.{ - .root_source_file = "src/simd_config.zig", - .name = "simd_config_tests", + const lib = b.addLibrary(.{ + .name = "zig-half", + .linkage = .static, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }), }); - - b.step.dependOn(&test_step.step); + b.installArtifact(lib); + + // One test target per file, as before, plus the module root. The root was + // not tested at all previously, which is the surface every consumer gets: + // Zig analyses top-level declarations lazily, so a test run over the other + // files proves nothing about the declarations they do not reference. + const test_step = b.step("test", "Run the tests"); + for ([_][]const u8{ + "src/root.zig", + "src/f16_utils.zig", + "src/f16_shadow.zig", + "src/sparse_simd.zig", + "src/ternary_pack.zig", + "src/simd_config.zig", + }) |path| { + const t = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + }), + }); + test_step.dependOn(&b.addRunArtifact(t).step); + } } diff --git a/build.zig.zon b/build.zig.zon index 5a7c26f..2c0462b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,5 +1,8 @@ .{ - .name = "zig-half", + .name = .zig_half, .version = "0.1.0", - .paths = .{"src"}, + .minimum_zig_version = "0.15.0", + .fingerprint = 0x3330a2c2540783d1, + .paths = .{ "src", "build.zig", "build.zig.zon" }, + .dependencies = .{}, } diff --git a/src/f16_utils.zig b/src/f16_utils.zig index 2d78218..b78f5a9 100644 --- a/src/f16_utils.zig +++ b/src/f16_utils.zig @@ -12,7 +12,7 @@ // φ² + 1/φ² = 3 | TRINITY const std = @import("std"); -const simd_config = @import("src/simd_config.zig"); +const simd_config = @import("simd_config.zig"); // ═══════════════════════════════════════════════════════════════════════════════ // ADAPTIVE VECTOR TYPES — Width from CPU feature detection diff --git a/src/root.zig b/src/root.zig index 840fd2a..1f14b0c 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,12 +1,13 @@ // zig-half — f16/bf16 SIMD library for Zig // -// A high-performance half-precision float library with: -// - Adaptive SIMD width (AVX2, AVX-512, NEON, SSE2) -// - f16 ↔ f32 conversions with zero-copy vectorization -// - Ternary quantization {-1, 0, +1} with 2-bit packing -// - Sparse ternary matvec with zero-chunk skipping -// - Shadow weight storage for gradient accumulation -// - Comprehensive benchmarks +// The re-exports below were written as `pub use module.{ A, B as C };`, which is +// Rust. Zig has no `use` statement and no `as` aliasing, so this file -- the +// module root, the surface every consumer imports -- has never been valid Zig, +// and the package has therefore never been usable by anybody. Nothing reported +// it because the repository had no workflow that built anything. +// +// Each of those blocks is now the Zig it was standing in for: one `pub const` +// per name, with the aliases preserved exactly as they were spelled. // // Extracted from Trinity HSLM training infrastructure. // See: https://github.com/gHashTag/trinity @@ -15,80 +16,72 @@ const std = @import("std"); // Re-export SIMD configuration and detection pub const simd_config = @import("simd_config.zig"); -pub use simd_config.{ capabilities, VecF16, VecF32, VecI8, zeroVecF16, zeroVecF32, zeroVecI8 }; +pub const capabilities = simd_config.capabilities; +pub const VecF16 = simd_config.VecF16; +pub const VecF32 = simd_config.VecF32; +pub const VecI8 = simd_config.VecI8; +pub const zeroVecF16 = simd_config.zeroVecF16; +pub const zeroVecF32 = simd_config.zeroVecF32; +pub const zeroVecI8 = simd_config.zeroVecI8; // Re-export f16 utilities pub const f16_utils = @import("f16_utils.zig"); -pub use f16_utils.{ - VEC_F16_SIZE, - VEC_F32_SIZE, - VecF16 as VecF16Alias, - VecF32 as VecF32Alias, - zeroVecF16 as zeroVecF16Alias, - zeroVecF32 as zeroVecF32Alias, - f32ToF16Slice, - f16ToF32Slice, - vecF16ToF32, - vecF32ToF16, - isTernarySafeF16, - countTernarySafeF16, - countNonFiniteF16, - maxAbsF16, - maxAbsF16Simd, - dotProductF16, - l2NormF16, - cosineSimilarityF16, - quantizeF16ToTernary, -}; +pub const VEC_F16_SIZE = f16_utils.VEC_F16_SIZE; +pub const VecF16Alias = f16_utils.VecF16; +pub const VecF32Alias = f16_utils.VecF32; +pub const zeroVecF16Alias = f16_utils.zeroVecF16; +pub const zeroVecF32Alias = f16_utils.zeroVecF32; +pub const f32ToF16Slice = f16_utils.f32ToF16Slice; +pub const f16ToF32Slice = f16_utils.f16ToF32Slice; +pub const vecF16ToF32 = f16_utils.vecF16ToF32; +pub const vecF32ToF16 = f16_utils.vecF32ToF16; +pub const isTernarySafeF16 = f16_utils.isTernarySafeF16; +pub const countTernarySafeF16 = f16_utils.countTernarySafeF16; +pub const countNonFiniteF16 = f16_utils.countNonFiniteF16; +pub const maxAbsF16 = f16_utils.maxAbsF16; +pub const maxAbsF16Simd = f16_utils.maxAbsF16Simd; +pub const dotProductF16 = f16_utils.dotProductF16; +pub const l2NormF16 = f16_utils.l2NormF16; +pub const cosineSimilarityF16 = f16_utils.cosineSimilarityF16; +pub const quantizeF16ToTernary = f16_utils.quantizeF16ToTernary; // Re-export f16 shadow weights pub const f16_shadow = @import("f16_shadow.zig"); -pub use f16_shadow.{ - F16ShadowStorage, - DEFAULT_SYNC_INTERVAL, - DEFAULT_QUANTIZE_THRESHOLD, - f32ToF16Slice as f32ToF16SliceShadow, - f16ToF32Slice as f16ToF32SliceShadow, - dotProductF16 as dotProductF16Shadow, -}; +pub const F16ShadowStorage = f16_shadow.F16ShadowStorage; +pub const f32ToF16SliceShadow = f16_shadow.f32ToF16Slice; +pub const f16ToF32SliceShadow = f16_shadow.f16ToF32Slice; +pub const dotProductF16Shadow = f16_shadow.dotProductF16; // Re-export sparse SIMD pub const sparse_simd = @import("sparse_simd.zig"); -pub use sparse_simd.{ - VEC_I8_SIZE, - VEC_F16_SIZE as VEC_F16_SIZE_Sparse, - VEC_F32_SIZE as VEC_F32_SIZE_Sparse, - VecI8 as VecI8Alias, - VecF16 as VecF16SparseAlias, - VecF32 as VecF32SparseAlias, - zeroVecI8 as zeroVecI8Alias, - zeroVecF16 as zeroVecF16SparseAlias, - sparseTernaryDot, - denseTernaryDot, - sparseTernaryMatvec, - denseTernaryMatvec, - countZeroChunks, - sparsityRatio, - estimateSpeedup, -}; +pub const VEC_I8_SIZE = sparse_simd.VEC_I8_SIZE; +pub const VEC_F16_SIZE_Sparse = sparse_simd.VEC_F16_SIZE; +pub const VEC_F32_SIZE_Sparse = sparse_simd.VEC_F32_SIZE; +pub const VecI8Alias = sparse_simd.VecI8; +pub const VecF16SparseAlias = sparse_simd.VecF16; +pub const VecF32SparseAlias = sparse_simd.VecF32; +pub const zeroVecI8Alias = sparse_simd.zeroVecI8; +pub const zeroVecF16SparseAlias = sparse_simd.zeroVecF16; +pub const sparseTernaryDot = sparse_simd.sparseTernaryDot; +pub const denseTernaryDot = sparse_simd.denseTernaryDot; +pub const sparseTernaryMatvec = sparse_simd.sparseTernaryMatvec; +pub const denseTernaryMatvec = sparse_simd.denseTernaryMatvec; +pub const countZeroChunks = sparse_simd.countZeroChunks; +pub const sparsityRatio = sparse_simd.sparsityRatio; +pub const estimateSpeedup = sparse_simd.estimateSpeedup; // Re-export ternary packing pub const ternary_pack = @import("ternary_pack.zig"); -pub use ternary_pack.{ - TRIT_NEG, - TRIT_ZERO, - TRIT_POS, - packTernary16, - unpackTernary16, - packTernarySlice, - unpackTernarySlice, - tritToChar, - charToTrit, - tritsToString, - stringToTrits, - countTrits, - compressionRatio, -}; +pub const packTernary16 = ternary_pack.packTernary16; +pub const unpackTernary16 = ternary_pack.unpackTernary16; +pub const packTernarySlice = ternary_pack.packTernarySlice; +pub const unpackTernarySlice = ternary_pack.unpackTernarySlice; +pub const tritToChar = ternary_pack.tritToChar; +pub const charToTrit = ternary_pack.charToTrit; +pub const tritsToString = ternary_pack.tritsToString; +pub const stringToTrits = ternary_pack.stringToTrits; +pub const countTrits = ternary_pack.countTrits; +pub const compressionRatio = ternary_pack.compressionRatio; // ═════════════════════════════════════════════════════════════════════════════ // VERSION & INFO @@ -142,3 +135,11 @@ pub inline fn printConfig() void { // // Print SIMD info // zig_half.printConfig(); // } + +test "every public declaration of this module is analysed" { + // src/root.zig is what a consumer imports, and no test target rooted it + // before -- so the one surface that matters was the one never compiled. + // The same omission hid sixteen defects in gHashTag/zig-golden-float and + // five in gHashTag/zig-hdc. + @import("std").testing.refAllDeclsRecursive(@This()); +} diff --git a/src/simd_bench.zig b/src/simd_bench.zig index 126c6a3..aadf946 100644 --- a/src/simd_bench.zig +++ b/src/simd_bench.zig @@ -13,7 +13,7 @@ const std = @import("std"); const f16_utils = @import("f16_utils.zig"); const sparse_simd = @import("sparse_simd.zig"); -const simd_config = @import("src/simd_config.zig"); +const simd_config = @import("simd_config.zig"); const EMBED_DIM = 243; const HIDDEN_DIM = 729; diff --git a/src/simd_config.zig b/src/simd_config.zig index c629a32..8f373df 100644 --- a/src/simd_config.zig +++ b/src/simd_config.zig @@ -156,7 +156,11 @@ pub fn simdInfoString() []const u8 { /// Print SIMD configuration at runtime /// Note: Only works in executables, not in test mode pub fn printSimdConfig() void { - const stdout = std.io.getStdOut().writer(); + // std.io.getStdOut() was removed in 0.15. + var stdout_buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + defer stdout.flush() catch {}; stdout.print("SIMD Configuration:\n", .{}) catch return; stdout.print(" Architecture: {s}\n", .{capabilities.arch_name}) catch return; diff --git a/src/sparse_simd.zig b/src/sparse_simd.zig index d279eef..f6b6860 100644 --- a/src/sparse_simd.zig +++ b/src/sparse_simd.zig @@ -13,7 +13,7 @@ // φ² + 1/φ² = 3 | TRINITY const std = @import("std"); -const simd_config = @import("src/simd_config.zig"); +const simd_config = @import("simd_config.zig"); // ═══════════════════════════════════════════════════════════════════════════════ // ADAPTIVE VECTOR TYPES