From b10977b8c735bd8813ca8f5adb00446930d70bfd Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Tue, 11 Aug 2026 12:52:14 +0200 Subject: [PATCH] ci(publish): embed Apple kernel archives into the Apple klibs; kernel matrix native-cinterop tier (#959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit publish.yml: the macos build-native leg builds the three Mach-O static archives (device/simulator/macos) with an nm/objdump sdot assertion — clang treats an unknown target-feature string as a warning, so a typo'd dispatch attribute (#958) would otherwise ship silently scalar — uploads them fail-loud as native-static-apple, and the publish job verifies and injects them via -PskainetKernels{IosArm64,IosSimulatorArm64,MacosArm64}Dir, the same verified-artifact-or-fail contract as the Linux ELF archives. Kernel support matrix: new native-cinterop tier (priority 100, Native·linux + Native·apple, the 7 packed-quant formats). Native·linux was under-reported as scalar — NativeKnKernelProvider has served it since the cinterop embedding landed. Narrative doc + KDoc now state the manual installNativeKernels() contract and the Apple A12 fallback. Refs #920 --- .github/workflows/publish.yml | 52 ++++++++++++++++++- CHANGELOG.md | 18 +++++++ docs/eager-execution-backends-and-kernels.md | 3 +- .../reference/kernel-support-matrix.adoc | 14 ++--- .../exec/kernel/KernelSupportMatrixTest.kt | 8 +++ .../exec/kernel/NativeKnKernelProvider.kt | 7 +++ 6 files changed, 93 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 224dc14d4..dfdbbd541 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -127,6 +127,40 @@ jobs: if-no-files-found: error retention-days: 14 + # K/N klib embedding for the Apple targets (#959, iOS kernel track of + # #920): the three Mach-O static archives are built on the mac leg with + # the platform SDKs and injected into the publish job, mirroring the + # linux pair above. The `nm | grep sdot` check guards against clang + # silently ignoring an unknown target-feature string in the #958 + # dispatch attribute (that failure mode is a warning, not an error). + - name: Build Apple K/N static archives (macOS only) + if: matrix.arch_label == 'macos-arm64' + env: + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 + run: | + ./gradlew --no-daemon --stacktrace --no-configuration-cache \ + :skainet-backends:skainet-backend-native-cpu:buildNativeKernelsIosArm64 \ + :skainet-backends:skainet-backend-native-cpu:buildNativeKernelsIosSimulatorArm64 \ + :skainet-backends:skainet-backend-native-cpu:buildNativeKernelsMacosArm64 + BASE=skainet-backends/skainet-backend-native-cpu/build/native + for d in cmake-build-ios-arm64 cmake-build-ios-sim-arm64 cmake-build-macos-arm64; do + nm "$BASE/$d/libskainet_kernels.a" | grep -q . || { echo "empty archive: $d" >&2; exit 1; } + objdump -d "$BASE/$d/libskainet_kernels.a" | grep -qw sdot \ + || { echo "no sdot in $d — dotprod dispatch body missing (#958)" >&2; exit 1; } + done + + - name: Upload Apple K/N static archives (macOS only) + if: matrix.arch_label == 'macos-arm64' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: native-static-apple + path: | + skainet-backends/skainet-backend-native-cpu/build/native/cmake-build-ios-arm64/libskainet_kernels.a + skainet-backends/skainet-backend-native-cpu/build/native/cmake-build-ios-sim-arm64/libskainet_kernels.a + skainet-backends/skainet-backend-native-cpu/build/native/cmake-build-macos-arm64/libskainet_kernels.a + if-no-files-found: error + retention-days: 14 + publish: name: Release build and publish needs: build-native @@ -207,12 +241,28 @@ jobs: test -f "$ARM64_DIR/libskainet_kernels.a" || { echo "Missing linux-arm64 static archive" >&2; exit 1; } echo "SKAINET_KERNELS_X64_DIR=$X64_DIR" >> "$GITHUB_ENV" echo "SKAINET_KERNELS_ARM64_DIR=$ARM64_DIR" >> "$GITHUB_ENV" + # Apple archives (#959): same verified-artifact-or-fail contract. + # Injection also suppresses a redundant local rebuild on this macOS + # host (injected dirs skip the cinterop -> CMake task dependency). + APPLE="$PWD/native-artifacts/native-static-apple" + IOS_DIR="$APPLE/cmake-build-ios-arm64" + IOS_SIM_DIR="$APPLE/cmake-build-ios-sim-arm64" + MACOS_DIR="$APPLE/cmake-build-macos-arm64" + test -f "$IOS_DIR/libskainet_kernels.a" || { echo "Missing ios-arm64 static archive" >&2; exit 1; } + test -f "$IOS_SIM_DIR/libskainet_kernels.a" || { echo "Missing ios-sim-arm64 static archive" >&2; exit 1; } + test -f "$MACOS_DIR/libskainet_kernels.a" || { echo "Missing macos-arm64 static archive" >&2; exit 1; } + echo "SKAINET_KERNELS_IOS_DIR=$IOS_DIR" >> "$GITHUB_ENV" + echo "SKAINET_KERNELS_IOS_SIM_DIR=$IOS_SIM_DIR" >> "$GITHUB_ENV" + echo "SKAINET_KERNELS_MACOS_DIR=$MACOS_DIR" >> "$GITHUB_ENV" - name: Publish to MavenCentral run: | ./gradlew publish --no-configuration-cache --stacktrace \ -PskainetKernelsX64Dir="$SKAINET_KERNELS_X64_DIR" \ - -PskainetKernelsArm64Dir="$SKAINET_KERNELS_ARM64_DIR" + -PskainetKernelsArm64Dir="$SKAINET_KERNELS_ARM64_DIR" \ + -PskainetKernelsIosArm64Dir="$SKAINET_KERNELS_IOS_DIR" \ + -PskainetKernelsIosSimulatorArm64Dir="$SKAINET_KERNELS_IOS_SIM_DIR" \ + -PskainetKernelsMacosArm64Dir="$SKAINET_KERNELS_MACOS_DIR" env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} diff --git a/CHANGELOG.md b/CHANGELOG.md index cc3358361..66f810ee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,24 @@ rebuilt per access. The README now points LLM users to SKaiNET-transformers. on K/N remains manual via `installNativeKernels()` — Apple consumers call it once at startup, same as Linux. +### CI + +- **Releases embed the Apple kernel archives** ([#959](https://github.com/SKaiNET-developers/SKaiNET/issues/959)): + publish.yml's macos leg builds the three Mach-O static archives (with an `nm`/`objdump` + `sdot` assertion guarding the #958 dispatch body against clang's silent unknown-feature + ignore), uploads them fail-loud, and the publish job verifies and injects them via the + `-PskainetKernels{IosArm64,IosSimulatorArm64,MacosArm64}Dir` properties — the same + verified-artifact-or-fail contract as the Linux ELF archives. + +### Documentation + +- **Kernel support matrix gains the `native-cinterop` tier** (Native·linux + Native·apple, + the 7 packed-quant formats) — the Native·linux column was under-reported as `scalar` + before; both native columns now reflect `NativeKnKernelProvider` + ([#959](https://github.com/SKaiNET-developers/SKaiNET/issues/959)). The eager-backends + mindmap and the `installNativeKernels()` KDoc document the manual-registration contract + and the Apple A12 dispatch fallback. + ## [0.39.0] - 2026-08-10 Headline: **on-device AI on Android becomes real.** A JNI NEON kernel backend diff --git a/docs/eager-execution-backends-and-kernels.md b/docs/eager-execution-backends-and-kernels.md index 932e2c239..6b879524e 100644 --- a/docs/eager-execution-backends-and-kernels.md +++ b/docs/eager-execution-backends-and-kernels.md @@ -82,7 +82,8 @@ those formats were JVM-only and broke on Native. ## In progress / missing (with trackers) - ❌ **Native FFM Q5_1/Q5_0/Q6_K** — the C kernel set covers FP32/BF16/Q8_0/Q4_0/Q4_K only. Tracked by **SKaiNET#708** (core kernel) and **SKaiNET-transformers#170** (converter wiring). -- ❌ **Native SIMD on linux** — Kotlin/Native linux targets run the scalar floor; no cinterop/OpenBLAS or SIMD path (Apple has Accelerate for dense ops). Tracked by **SKaiNET#722**. +- ✅ **Native packed-quant kernels on Kotlin/Native** — `NativeKnKernelProvider` (priority 100, `skainet-backend-native-cpu`) serves Q8_0/Q4_0/Q4_K/Q5_K/Q6_K/Q5_0/Q5_1 from the C kernels statically embedded in the klib, on linuxX64/linuxArm64 and (since #959) iosArm64/iosSimulatorArm64/macosArm64. Apple archives use runtime FEAT_DotProd dispatch (#958) so one device archive serves A12 through M-series. Registration is **manual** — call `installNativeKernels()` once at startup (no ServiceLoader on K/N). +- ❌ **Dense FP32/BF16 SIMD on Kotlin/Native linux** — the dense floats still run the scalar floor there (Apple has Accelerate). Tracked by **SKaiNET#722** / **#910**. - ❌ **Other GGML quant formats** (Q5_K, Q2_K, Q3_K, Q8_K, IQ4_NL/XS) — loadable via dequant-to-FP32, but no packed matmul kernel. - ❌ **Non-CPU eager backends** (IREE, Metal, GPU) — the `KernelProvider` SPI anticipates them, but none are implemented for the eager path today. diff --git a/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc b/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc index 8b67e2813..798c0d3e3 100644 --- a/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc +++ b/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc @@ -11,13 +11,13 @@ Each cell is the best (highest-priority) provider that serves `Float32 × format | `Float32` | native-ffm | panama-vector | scalar | scalar | scalar | `BFloat16` | native-ffm | panama-vector | scalar | scalar | scalar -| `Q8_0` | native-ffm | native-jni | scalar | scalar | scalar -| `Q4_0` | native-ffm | native-jni | scalar | scalar | scalar -| `Q4_K` | native-ffm | native-jni | scalar | scalar | scalar -| `Q6_K` | panama-vector | native-jni | scalar | scalar | scalar -| `Q5_K` | native-ffm | native-jni | scalar | scalar | scalar -| `Q5_1` | panama-vector | panama-vector | scalar | scalar | scalar -| `Q5_0` | panama-vector | panama-vector | scalar | scalar | scalar +| `Q8_0` | native-ffm | native-jni | native-cinterop | native-cinterop | scalar +| `Q4_0` | native-ffm | native-jni | native-cinterop | native-cinterop | scalar +| `Q4_K` | native-ffm | native-jni | native-cinterop | native-cinterop | scalar +| `Q6_K` | panama-vector | native-jni | native-cinterop | native-cinterop | scalar +| `Q5_K` | native-ffm | native-jni | native-cinterop | native-cinterop | scalar +| `Q5_1` | native-ffm | native-jni | native-cinterop | native-cinterop | scalar +| `Q5_0` | native-ffm | native-jni | native-cinterop | native-cinterop | scalar |=== See also the eager backends & kernels mindmap (`docs/eager-execution-backends-and-kernels.md`) for the narrative overview and gaps. diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt index 42f7ee033..5da2052c4 100644 --- a/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt @@ -43,6 +43,14 @@ class KernelSupportMatrixTest { setOf("Float32", "BFloat16", "Q8_0", "Q4_0", "Q4_K", "Q5_K", "Q5_1", "Q5_0")), Tier("native-jni", 100, setOf("Android"), setOf("Q8_0", "Q4_0", "Q4_K", "Q5_K", "Q6_K", "Q5_1", "Q5_0")), + // native-cinterop: NativeKnKernelProvider (nativeMain) — the same C + // kernels statically embedded into the K/N klibs (#941/#942). Linux + // since 0.39.x; Apple (iosArm64/iosSimulatorArm64/macosArm64) since + // #959, with runtime FEAT_DotProd dispatch on Apple (#958: A13+/ + // M-series fast path, A12 scalar-int fallback). Registration is + // manual via installNativeKernels() — no ServiceLoader on K/N. + Tier("native-cinterop", 100, setOf("Native·linux", "Native·apple"), + setOf("Q8_0", "Q4_0", "Q4_K", "Q5_K", "Q6_K", "Q5_1", "Q5_0")), ) private fun best(fmt: String, platform: String, tiers: List): String? = diff --git a/skainet-backends/skainet-backend-native-cpu/src/nativeMain/kotlin/sk/ainet/exec/kernel/NativeKnKernelProvider.kt b/skainet-backends/skainet-backend-native-cpu/src/nativeMain/kotlin/sk/ainet/exec/kernel/NativeKnKernelProvider.kt index 42ffdd4ed..44ef11c96 100644 --- a/skainet-backends/skainet-backend-native-cpu/src/nativeMain/kotlin/sk/ainet/exec/kernel/NativeKnKernelProvider.kt +++ b/skainet-backends/skainet-backend-native-cpu/src/nativeMain/kotlin/sk/ainet/exec/kernel/NativeKnKernelProvider.kt @@ -58,6 +58,13 @@ public object NativeKnKernelProvider : KernelProvider { * (re-registering the same instance is a no-op). Call once at startup before any * `ops.matmul` on quantized weights. * + * Available on every published K/N target: linuxX64/linuxArm64 and, since #959, + * iosArm64/iosSimulatorArm64/macosArm64 — the Apple klibs embed the same C + * kernels compiled at baseline arm64 with runtime FEAT_DotProd dispatch (#958: + * A13+/M-series take the sdot fast path, A12 the scalar-int fallback — one + * archive, no SIGILL). On Apple this coexists with AccelerateCpuOps, which + * covers dense FP32; the formats are disjoint. + * * For quant types without a C kernel also register the commonMain * `ScalarKernelProvider` (from `skainet-backend-cpu`) as the fallback — it lives * in a different module, so the consumer wires it: