Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }}
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion docs/eager-execution-backends-and-kernels.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
14 changes: 7 additions & 7 deletions docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Tier>): String? =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading