Commit 094023e
feat(native-cpu): native FFM Q4_K matmul kernel (PR 2 of 5)
PR 2 of the staged native (FFM) kernel provider rollout described in
docs/.../perf/native-ffm-plan.adoc. Wires a real Q4_K matmul into the
public SPI: NativeKernelProvider now reports isAvailable() = true on
hosts where the bundled libskainet_kernels resolves and
skainet_q4k_matmul links, and matmulQ4K() returns NativeQ4KMatmulKernel
at priority 100 — winning KernelRegistry.bestAvailable() over Panama
(50) for Q4_K on JVM.
Native side (native/):
- src/q4k_matmul.c implements skainet_q4k_matmul over the canonical
ggml Q4_K super-block layout (256 elements / 144 bytes; FP16 d/dMin;
12-byte get_scale_min_k4 packed sub-scales; 128 bytes of strided
4-bit codes). Mirrors PanamaVectorQ4KMatmulKernel byte-for-byte —
same lazy-dmin trick (codeSum + inputSum per sub-block; combine via
d*scaleIdx*codeSum - dMin*minIdx*inputSum). Single-threaded, scalar
C; the 32-iteration inner loop is straight-line FP arithmetic that
-O3 -ffast-math auto-vectorizes on AVX2 / NEON.
- include/skainet_kernels.h declares the new export with the
SKAINET_API visibility macro.
- CMakeLists.txt picks up q4k_matmul.c and adds -O3 -ffast-math
-funroll-loops to the compile flags so the auto-vec actually fires.
Kotlin side (src/jvmMain):
- NativeQ4KMatmulKernel implements Q4KMatmulKernel via FFM downcall
(Linker.downcallHandle on FunctionDescriptor.ofVoid with 8 args
matching the C signature). Heap arrays are copied into Arena.
ofConfined off-heap segments, the kernel runs, output bulk-copies
back. The MemorySegment-input overload that avoids the heap copy
for mmap'd Q4_K weights ships in PR 3.
- NativeKernelProvider.isAvailable() now returns
NativeQ4KMatmulKernel.isAvailable() (lib loaded + symbol resolved).
matmulQ4K() returns the native kernel when available; cascades to
Panama otherwise. matmulFp32() still null pending a later PR.
Tests (src/jvmTest):
- NativeQ4KMatmulKernelParityTest: 5 parity assertions vs
PanamaVectorQ4KMatmulKernel (the existing priority-50 reference)
across single-block / multi-block / LLM-typical (4096×64) shapes
with the same fixture pattern as PanamaVectorQ4KMatmulKernelTest.
Tolerance: 1e-2 to 5e-1 absolute or 1e-4 relative — the same bar
Panama-vs-scalar parity uses, which already swallows FMA + native
-ffast-math reassociation differences.
- Q4KMatmulMicrobenchTest: wall-clock comparison vs Panama at
1024² / 2048² / 4096². Skipped by default; activates with
-Dskainet.runBench=true (forwarded from Gradle CLI through a new
systemProperty bridge in build.gradle.kts).
- NativeFfmPipelineTest: stub-flip assertion updated to expect
isAvailable() = true and matmulQ4K() != null.
build.gradle.kts:
- jvmTest dependencies pick up :skainet-backend-cpu (for the parity
reference) and kotlinx-coroutines (transitive: PanamaVector uses
parallelChunks).
- Test JVM args extended with --add-modules jdk.incubator.vector so
the parity test can load Panama.
Microbench numbers (Linux x86_64, JDK 21.0.10, gcc 13.3, -O3 -ffast-
math; warmup=20, samples=21, median µs):
shape native panama ratio
1024² 379 2225 5.87×
2048² 1393 6558 4.71×
4096² 5958 24865 4.17×
Crushes both PRD targets:
- ≥2.5× over scalar Q4_K dequant baseline (Panama is already >>
scalar; native is 4.17–5.87× faster than Panama)
- ≥1.5× over Panama Vector → exceeded by 2.7–3.9× margin
Verification (linux-x86_64, JDK 21.0.10, cmake 3.28.3):
- :skainet-backends:skainet-backend-native-cpu:jvmTest — 8/8 (3
pipeline + 5 parity, microbench skipped without -D)
- :skainet-backends:skainet-backend-cpu:jvmTest — 218/218 (cascade
unchanged; with native registered the registry now hands out the
native Q4_K kernel ahead of Panama)
Out of scope (deferred per asciidoc staging):
- PR 3: Q4KMemSegMatmulKernel SPI sibling for zero-copy mmap'd weights
- PR 4: linuxX64 AVX2 + NEON intrinsics + cross-arch CI matrix
- PR 5: native FP32 / Q6_K / Q8_0 kernels
- JMH integration in :skainet-backends:benchmarks:jvm-cpu-jmh
(Q4KMatmulMicrobenchTest is a stand-in)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 961d68f commit 094023e
9 files changed
Lines changed: 551 additions & 22 deletions
File tree
- skainet-backends/skainet-backend-native-cpu
- native
- include
- src
- src
- jvmMain/kotlin/sk/ainet/exec/kernel
- jvmTest/kotlin/sk/ainet/exec/kernel
Lines changed: 13 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
18 | 24 | | |
19 | 25 | | |
20 | 26 | | |
| |||
106 | 112 | | |
107 | 113 | | |
108 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
109 | 119 | | |
110 | | - | |
| 120 | + | |
| 121 | + | |
111 | 122 | | |
112 | 123 | | |
113 | 124 | | |
114 | | - | |
| 125 | + | |
115 | 126 | | |
Lines changed: 10 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
26 | | - | |
| 27 | + | |
| 28 | + | |
27 | 29 | | |
28 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
29 | 37 | | |
30 | 38 | | |
Lines changed: 24 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
28 | 52 | | |
29 | 53 | | |
30 | 54 | | |
| |||
Lines changed: 151 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
Lines changed: 11 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
15 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
16 | 17 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
| 18 | + | |
| 19 | + | |
23 | 20 | | |
24 | 21 | | |
25 | 22 | | |
26 | 23 | | |
27 | 24 | | |
28 | | - | |
| 25 | + | |
29 | 26 | | |
30 | 27 | | |
31 | 28 | | |
32 | | - | |
| 29 | + | |
| 30 | + | |
33 | 31 | | |
Lines changed: 102 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
0 commit comments