feat(backend): skainet-backend-jni-cpu — Android JNI bridge for the NEON kernels (#920) - #943
Merged
Merged
Conversation
…EON kernels ART has no java.lang.foreign, so the priority-100 FFM provider can never run on Android; inference there ran on the priority-0 scalar floor (the 1 tok/s cliff measured in the #920 field report). This AAR ships the same C kernel sources (single source of truth — the CMakeLists includes them from skainet-backend-native-cpu/native) via NDK with thin JNI shims and a priority-100 KernelProvider. Runtime CPU-feature dispatch, the Android-specific correctness trap: a Play-store .so cannot assume armv8.2 (dotprod SIGILLs on Cortex-A53). Two libs are built from the same sources — libskainet_jni.so (baseline armv8-a: NEON guaranteed on AArch64, 0 dot-product instructions, verified by llvm-objdump) and libskainet_jni_v82.so (-march=armv8.2-a+fp16+dotprod: 32 udot/sdot sites) — and JniKernels loads exactly ONE per process after checking asimddp/fphp in /proc/cpuinfo. Selection-at-load beats symbol renaming or ifunc for simplicity and auditability. Details: - JNI shims use GetPrimitiveArrayCritical (zero-copy pins on ART), no JNI calls inside the critical window, releases in reverse order, JNI_ABORT for read-only arrays. Method names contain no underscores (JNI mangles _ to _1 — silent-mismatch trap). - Q8_0/Q4_0/Q4_K/Q5_K/Q6_K bridged; fp32 GEMM deferred (different ABI shape, dense fp32 is not the LLM decode hot path). - ServiceLoader discovery: ART supports java.util.ServiceLoader; PlatformCpuOpsFactory.android now installs discovered providers like the JVM does, then registers the scalar floor. consumer-rules.pro keeps the service entry and native method names through R8 full mode (without it, release builds silently fall back to scalar). - .so files are 16 KB-page aligned (-Wl,-z,max-page-size=16384, Android 15+ requirement; LOAD align 0x4000 verified via llvm-readelf). - Host unit tests pin the graceful-degradation contract (no .so -> unavailable, kernels null, nothing throws). On-device androidTest parity suite (vs scalar references) compiles and is ready for a device run; the KernelSupportMatrix tier list gains native-jni. Verified locally: assembleRelease green (AAR contains 4 .so files: arm64 baseline+v82 differ, x86_64 pair identical by design), testDebugUnitTest 4/4, assembleDebugAndroidTest compiles, backend-cpu compileAndroidMain green, KernelSupportMatrixTest green. Device execution of the parity suite + tok/s measurement on the PromptPong reproducer is the next validation step (physical bench). Refs #920
aharakal
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Android half of #920: ART has no
java.lang.foreign, so the priority-100 FFM provider can never run there — Android inference has been sitting on the priority-0 scalar floor (the measured 1 tok/s cliff from the field report). This AAR ships the same C kernel sources (CMake includes them fromskainet-backend-native-cpu/native— single source of truth) with thin JNI shims and a priority-100KernelProvider.Runtime CPU-feature dispatch — the Android-specific trap
A Play-store
.socannot assume armv8.2: dotprod instructions SIGILL on Cortex-A53-class cores, but leaving dotprod off wastes thevdotq_s32q4k/q6k paths on modern SoCs. Two libs are built from the same sources, andJniKernelsloads exactly one per process after checking/proc/cpuinfo:libskainet_jni.sofmla, 0udot/sdot— safe on every arm64 corelibskainet_jni_v82.soudot/sdotsitesSelection-at-load beats symbol renaming / ifunc for simplicity and auditability; the loaded tier is exposed (
JniKernelProvider.activeVariant) so field reports name the actual code path.Details
GetPrimitiveArrayCritical(zero-copy pins on ART), no JNI calls inside the critical window, reverse-order releases,JNI_ABORTfor read-only arrays; method names contain no underscores (JNI mangles_→_1). Q8_0/Q4_0/Q4_K/Q5_K/Q6_K bridged; fp32 GEMM deferred (different ABI shape, not the LLM decode hot path).java.util.ServiceLoader—PlatformCpuOpsFactory.androidnow installs discovered providers exactly like the JVM does, then the scalar floor.consumer-rules.prokeeps the service entry + native method names through R8 full mode (without it, release builds silently fall back to scalar — the worst failure mode)..sos linked withmax-page-size=16384(Android 15+ requirement);LOADalign0x4000verified viallvm-readelf.arm64-v8a+x86_64(emulator CI); 32-bit ARM deliberately out of scope.KernelSupportMatrixTesttier list gainsnative-jni(Android, priority 100).Verified locally
assembleRelease: AAR contains all four.sos (arm64 pair differs — different codegen; x86_64 pair identical by design, no per-ABI loader special case)testDebugUnitTest4/4 — host graceful-degradation contract (no.so→ unavailable, kernels null, nothing throws)assembleDebugAndroidTestcompiles the on-device parity suite (vs scalar references):skainet-backends:skainet-backend-cpu:compileAndroidMain+KernelSupportMatrixTestgreenNot yet done (next steps, flagged honestly)
Refs #920