Click the diagram for the full architecture reference, or read the short ARCHITECTURE.md.
SKaiNET is a Kotlin Multiplatform AI framework. New here? Choose the path that matches what you want to try first.
| Goal | Start here | Time |
|---|---|---|
| Run tensor operations | Quickstart (below) | 2–5 min |
| Build and train a neural net | Hello Neural Net (below) | 5 min |
| Run a local GGUF model | SKaiNET Transformers starter | 5 min after model setup |
| Export a secure MCU bundle | Minerva getting started | 10 min without firmware flashing |
Working in Java? SKaiNET ships first-class Java support — see the Java getting-started guide.
Note
Looking for LLM inference? Llama, Qwen, Gemma, Apertus, BERT embeddings and
GGUF chat models live in
SKaiNET-transformers —
this repository is the engine underneath it (tensors, NN DSL, compiler, CPU/native
backends, GGUF/SafeTensors IO). Depend on the sk.ainet.transformers artifacts,
pinned together by the
transformers BOM.
Use the version shown in this README as the source of truth for first-run snippets. If another page shows a different version, please open an issue or PR.
Add the core dependencies (Gradle Kotlin DSL):
dependencies {
// Recommended: import the umbrella BOM and drop versions on the engine modules.
implementation(platform("sk.ainet:skainet-bom:0.62.4"))
implementation("sk.ainet.core:skainet-lang-core")
implementation("sk.ainet.core:skainet-backend-cpu")
}val model = nn {
input(28 * 28)
dense(out = 128)
relu()
dense(out = 10)
}val a = tensor(shape(2, 2)) { float(1f, 2f, 3f, 4f) }
val b = tensor(shape(2, 2)) { float(5f, 6f, 7f, 8f) }
val c = a matMul b
val d = c.relu()// Recommended: streaming reader — memory-efficient, supports quantized types
val source = JvmRandomAccessSource.open("model.gguf")
StreamingGGUFReader.open(source).use { reader ->
println("Tensors: ${reader.tensorCount}")
// Load specific tensor on demand (no whole-file loading)
val bytes = reader.loadTensor("token_embd.weight")
// Or get a TensorStorage descriptor with encoding/placement metadata
val storage = reader.loadTensorStorage("token_embd.weight")
}More examples: SKaiNET-examples | SKaiNET-notebook
SKaiNET is a modular ecosystem. While this repository contains the core engine, specialized high-level libraries are maintained in standalone repositories:
| Project | Description |
|---|---|
| SKaiNET-transformers | Pre-built transformer architectures and layers |
| SKaiNET-examples | Sample projects and integration demos |
| Goal | Start here |
|---|---|
| Examples and sample projects | SKaiNET-examples |
| Interactive notebooks | SKaiNET-notebook |
| Eager backends & kernels (what runs where) | Backends & kernels map |
| Design proposals and long-lived API decisions | SKEEP proposals |
| Memory & storage architecture (storage, views, scopes, planning) | The memory model · Packed weight layout |
First time here? Three links are all you need:
- 🚀 Getting started as a contributor — the two workflows in one page, and how to claim a task.
- 🟣 Open good first issues — filter further by what you know:
skill:android,skill:numerics(no Kotlin),skill:docs, or by size:size:xs. - 🏷️ Issue taxonomy — what every label and
[Lane N · skill]title prefix means.
Small fixes can go straight through the normal contribution flow described in CONTRIBUTING.md and GITFLOW.adoc.
Non-trivial work goes through one of two processes:
- DARC (Document / Assess / Research / Code) for one feature — a new operator, metric, layer, format reader, or kernel strategy. A feature is one parent issue plus skill-labelled sub-issues ("lanes"). See the DARC workflow.
- SKEEP (SKaiNET Evolution and Enhancement Process) for one architectural
decision — public APIs, DSL syntax, tensor semantics, compiler/runtime
integration, storage behavior, compatibility policy. SKEEP files live under
docs/modules/skeep/pages/with three-digit numbering. See the SKEEP index.
SKaiNET ships an official Phoronix-Test-Suite-compatible benchmark
program for the compute engine. See the
methodology and replay docs,
the release manifest, and the
CI workflow. Smoke runs fire
on every PR via ubuntu-latest; full publishable runs fire on a
self-hosted Linux x86 runner on release.
Quick local replay:
./gradlew :skainet-backends:benchmarks:jvm-cpu-publish:shadowJar
./scripts/run_engine_smoke.shSKaiNET is built around one path: a model is defined once in the Kotlin DSL, then either compiled or executed eagerly — without rewriting it.
- Define the model with the DSL (
nn { }/dag { }). - Capture it as a tape (traced execution) or a DAG (explicit graph) — a
ComputeGraph. - Run it one of two ways:
- Compile — lower the captured
ComputeGraphthrough one of several sibling code-generation backends, each emitting code for a different target from the same graph:- StableHLO / MLIR (
HloGenerator) → IREE-compilable, for native / edge / accelerator targets and the wider MLIR ecosystem. - Arduino / C99 → standalone, statically-allocated C for microcontrollers.
- Minerva → a secure-MCU bundle (weights + firmware skeleton + fingerprinted manifest).
- StableHLO / MLIR (
- Eager — execute directly on an available backend. On the JVM this is the primary, go-to path.
- Compile — lower the captured
StableHLO/MLIR is therefore one code-generation backend among siblings — the IREE/native path next to the C99/Arduino and Minerva MCU paths — not a separate pipeline.
flowchart LR
DSL["Model — Kotlin DSL"] --> Graph["Tape / DAG (ComputeGraph)"]
Graph --> Eager["Eager backend (JVM, …)"]
Graph -->|code generation| HLO["StableHLO / MLIR"]
Graph -->|code generation| C99["Arduino / C99"]
Graph -->|code generation| Minerva["Minerva"]
HLO --> Native["IREE → native / edge / accelerator"]
C99 --> MCU["Microcontroller"]
Minerva --> SecMCU["Secure-MCU bundle"]
The same DSL model feeds every path: eager execution for development and JVM deployment, and the code-generation backends — StableHLO/MLIR (→ IREE), Arduino/C99, and Minerva — as sibling alternatives for native, edge, and secure-MCU targets.
SKaiNET now includes a Minerva export backend for secure MCU deployment. It is a sibling to StableHLO and Arduino/C99 export: it starts from a supported ComputeGraph, lowers static MLPs to a Minerva compiler input, invokes libminerva when configured, and packages generated weights, host fixtures, firmware skeletons, and a fingerprinted manifest.json.
Start here:
- Minerva getting started — run the maintained tiny MLP dry sample, then the real libminerva runtime profile.
- Minerva export how-to — configure compiler paths, keys, calibration, CMake/CTest host verification, and troubleshooting.
- How Minerva secure MCU export fits — understand why Minerva is not an Arduino replacement and when to choose StableHLO instead.
Runnable examples:
./gradlew :skainet-compile:skainet-compile-minerva:runMinervaSecureMcuExamples
./gradlew :skainet-compile:skainet-compile-minerva:runMinervaSecureMcuExamples \
-Pminerva.example=sensor-classifier- Targets: JVM, macOS (Native), JS, WASM (Browser + WasmWasi)
- Single codebase shared across all platforms via Kotlin Multiplatform
- ComputeGraphExecutor: Optimized engine with fusion passes and trace-to-DAG bridging.
- SDPA & Gather: High-performance Scaled Dot-Product Attention and indexing operations.
- TurboQuant: Runtime KV-cache compression (~8x at 4-bit) for long-context LLM inference. Presets:
safe-lowbit,balanced,experimental-max. SeeTurboQuantUsagefor integration guide.
- Sequential:
nn { input(); dense(); relu(); dense() } - DAG / Graph: arbitrary wiring with
dag { }for ResNet-style skip connections and multi-scale detector heads - Layers: Dense, Conv1d/2d/3d, MaxPool, AvgPool, BatchNorm, Dropout, LeakyReLU, ELU
- KAN (Kolmogorov–Arnold Networks) layer (experimental)
- Autograd engine with reverse-mode gradients, SGD and Adam/AdamW optimizers
- Built-in loaders: MNIST, Fashion-MNIST, CIFAR-10, Iris
- URI-backed data sources:
file://,https://,hf+https://, andhf://... - Dataset operations: deterministic shuffle/split, stratified split, filter/map/transform views, batch flows, and epoch flows
- Raw dataset parsers: CSV, TSV, JSON arrays/objects, JSON Lines (
.jsonl,.ndjson) - Type-safe transform DSLs: image/tensor transforms plus suspendable raw data pipelines
- Formats: GGUF, ONNX, SafeTensors, JSON, Image (JPEG, PNG)
val raw = JvmDataSourceResolver().rawDataset {
from("hf://datasets/org/repo@main/train.jsonl")
format(DataFormat.JSON_LINES)
cachePolicy(CachePolicy.Use)
}
val withoutLabel = dataPipeline<RawDataset>()
.stage(
dataTransformer(
name = "drop-label",
outputSchema = { schema -> DataSchema(schema.columns - "label") }
) { dataset ->
val columns = dataset.schema.columns - "label"
dataset.copy(
schema = DataSchema(columns),
rows = dataset.rows.map { row ->
RawDataRow(row.values.filterKeys { key -> key in columns })
}
)
}
)
.execute(raw)- Start with the data sources getting started guide
- Export trained models to standalone, optimized C99 with static memory allocation
- Ready-to-use Arduino library output
- Export supported static MLP graphs to Minerva project bundles for secure MCU inference
- Emits compiler NPZ input, libminerva weights, a fingerprinted manifest, host harness, firmware example, and host verification results
- Start with the Minerva getting started guide
- Lower Kotlin DSL to MLIR StableHLO dialect
- Optimization passes: constant folding, operation fusion, dead code elimination
- Valid IREE-compilable output with streaming API and public
HloGenerator
- Use StableHLO when you want portable MLIR/IREE-compatible graphs for native, accelerator, or ecosystem compiler flows.
- Use Arduino / C99 export / Minerva export when you want standalone generated C with static memory allocation or external secure runtime.
An arm64 release. On macOS the JVM's dense FP32 products — A · B on heap or segment-backed operands and x · Wᵀ
on a resident dense weight — now go to Accelerate's SGEMM by default, which runs on the matrix units of Apple
silicon and threads itself; JvmBlas had never reached a BLAS before (the framework lookup and the heap downcall
both failed silently), and -Dskainet.cpu.blas.enabled=false restores the bundled kernels. Elsewhere the default
stays off and skainet.cpu.blas.library names an OpenBLAS or reference BLAS to use. The native dense FP32 kernels
get register tiles on AArch64 (Apple silicon, Android arm64, Linux arm64): an 8 × 8 tile of lane-indexed FMAs over a
packed panel of B for A · B and a four-by-three tile for x · Wᵀ, bit-identical across slices, threads and whole
calls, exercised on Apple silicon by the macOS CI leg. Both are unmeasured on arm64 in this release; the pull
requests (#1449, #1450) say how to run the bench.
A load-time and kernel release. StreamingGGUFReader decodes the GGUF metadata from 64 KB blocks instead of one
source read per value, and TextFrontEnds.fromGguf builds a SentencePiece tokenizer in one pass over the vocabulary
instead of three to four copies of it; fields, the public API and the token ids stay the same. On a Gemma-3
tokenizer GGUF a file-source parse drops from 1.0 s to 21–45 ms on the JVM and from 23.3 s to 1.2 s on a 32-bit ARM
Android device, where read + build on the first load in a process goes from 8.4–11.2 s to 6.0–6.4 s. The native
dense FP32 A · B kernel gets an AVX2/FMA register-tiled lane on x86-64 (63 × 1024 × 1024: 21 → 281 GFLOP/s), and
no longer overwrites the parts of a sub-block caller's output buffer outside the block.
A performance release for the JVM native lane. The dense FP32 x · Wᵀ kernel — what every resident dense
weight resolves to once skainet-backend-native-cpu is on the classpath — now splits its work across the
context Schedule (SKEEP-005) instead of a fixed four-thread C pool, and on x86-64 hosts with AVX2 and FMA
runs a runtime-selected eight-lane register-tiled lane; threaded, sliced and single calls stay bit-identical.
A single 63 × 1024 × 1024 call goes from 29.6 to 131.7 GFLOP/s on a six-core laptop, and a ModernBERT-large
encoder request in the LayaMoE example from 13.0 s to 6.4 s with unchanged answers. The text front-end
indexes added tokens by first character and length, so a Gemma chat turn encodes in 36 µs instead of 2.0 ms.
CoroutineSchedule.forRange never hands a body an empty range any more.
A fix release on top of 0.62.0: the native FFM bindings of the Q8_0, Q4_0, Q5_0 and Q5_1 kernels
copied a zero-filled staging buffer back from index 0, so a product served row by row — any
quantize-at-load encoder on the JVM with skainet-backend-native-cpu on the classpath — kept only
its last row. Single-row decode steps were never affected. A regression test now covers every packed
format for one, seven and fifty rows. Also: the YOLOv8 model code moves out of core into its cartridge
blueprint (skainet-model-yolo is gone; Letterbox, BoundingBox and nonMaxSuppression stay in
skainet-data-media), the StableHLO softmax axis and logSoftmax lowering are fixed, and the docs
gain Flexibility at every compile stage and the SKEEP-008 draft on precision and encoding conversion.
JVM ↔ Kotlin/Wasm parity: the browser tier runs the same kernels as the JVM, keeps its activations in the sidecar, and reads models the way the JVM does — with every remaining difference documented.
- Every kernel on the wasm tier — explicit SIMD128 bodies for every matmul format (K-quants,
Q5_0/Q5_1, dense FP32/BF16/FP16), the ternary BitNet kernels, elementwise, activation and
reduction kernels, fused softmax, normalisation and banded attention, and convolutions as
im2col + GEMM, all threaded over the sidecar's row pool. Chains of ops stay resident in the
sidecar's linear memory (
Storage.OffHeapin a browser) and come back to the Kotlin heap only when something outside needs them. - Both directions — the JVM binds the same C kernels through FFM: the direct
x · Wᵀkernel (no transposed weight copy), fused softmax, layer and RMS normalisation, and SDPA with key lengths, windows and grouped-query heads. One C source per kernel serves every native build. - Browser IO —
Blobsources addressLongpositions (shards over 2 GB), copy in bulk, and serve blocking reads inside a Web Worker; sharded SafeTensors load from Blobs;PlannerProfile.BROWSER;MemoryProbereports the JavaScript heap, linear memory and the sidecar's resident bytes;skainet-data-sourceresolves HTTP and Hugging Face URIs overfetchon Kotlin/JS and Kotlin/Wasm. - One benchmark table — the
engine-*scenarios of the JVM publish harness run with identical shapes inwasm-bench, and the kernel support matrix names what each tier serves and why the rest is N/A. The operator coverage matrix is generated from backend data, with StableHLO, backward and NN-layer columns and a CI leg that rejects unmarked stubs. - JVM decode allocation — four fixes from the BitNet 2B4T profile: the ternary GEMV on the
shared row pool (+19 % decode), intrinsified Vector API elementwise paths, a dense
splitfast path and bulk copies from mapped tensors.
See CHANGELOG.md for full release notes, including every prior release.
- Q1 2026: Comprehensive documentation ✅
- Q2 2026: TurboQuant KV-cache compression ✅ (shipped in 0.18.0); Qwen/LLaMA tokenizers ✅ (shipped in 0.20.0)
- Q3 2026: Missing ML features: metrics, optimizers, and training utilities.
- Q4 2026: On-Device AI, small LLMs improvements
We love contributions! Whether it's a new operator, documentation, or a bug fix:
- Read Getting started as a contributor (five minutes), then the Contribution Guide when you need the procedure.
- Pick an open good first issue — every one names the file to copy the pattern from and the exact Gradle task to run. Comment on it to claim it.
- Open a discussion or issue on GitHub; the issue chooser has templates for DARC features, lane tasks and SKEEP proposals.
Browse the full codebase documentation on DeepWiki.
- Michal Harakal (@michalharakal) — the Accelerate/BLAS routing of the JVM's dense FP32 products, the AArch64 register tiles for the native dense FP32 kernels with their tests, and the release
- Michal Harakal (@michalharakal) — the buffered GGUF metadata reader, the one-pass tokenizer build, the AVX2/FMA dense FP32 SGEMM lane with their tests and measurements, and the release
- Michal Harakal (@michalharakal) — the schedule-driven and AVX2/FMA
dense FP32 lanes with their tests and measurements, the
CoroutineSchedulefix, the added-token index, and the release
- Michal Harakal (@michalharakal) — the native packed-kernel binding fix and its regression test, the YOLO move-out, the compile-flexibility explanation, the SKEEP-008 draft, and the release
- Michal Harakal (@michalharakal) — the JVM ↔ wasm parity
series: SIMD128 bodies for every sidecar kernel, the ternary and direct
x · Wᵀkernels, resident activations with the elementwise, fused and convolution kernels on both tiers, the browser IO parity,skainet-data-sourceon JS and wasm, the operator coverage matrix, the BitNet decode allocation fixes, and the release - Edwin (@EdinEd) — the
ClassificationSupporthelpers extracted fromAccuracy, preparing the precision, recall and F1 metrics (#1222)
- Michal Harakal (@michalharakal) — SKEEP-007: the Tier 0
dispatch-order and dense-path fixes, quantize-at-load for the SafeTensors and GGUF loaders with the
Q8_0 quantizer and
skainet-quantize, theskainet-backend-wasm-simdsidecar kernel pack, the scheduled SKEEP-006 removal, and the release
- Michal Harakal (@michalharakal) — key-padding lengths
and window metadata for
scaledDotProductAttentionacross the CPU kernel, recorder, executor and StableHLO lowering, the n-ary composite seam with theskainet.sdpacomposite, the SKEEP-006 deprecation step, and the release
- Michal Harakal (@michalharakal) — exact GELU and
erfacross the CPU kernels, graph executor and backward rules, the lowering-metadata path with composite emission and the legalization pass, and the release
- Michal Harakal (@michalharakal) — SKEEP-006: the front-end and model-identity modules, the tokenizer move with Metaspace BPE, Unigram and WordPiece, the golden corpus, and the release
- Michal Harakal (@michalharakal) — masked grouped-query attention in the StableHLO export, the kotlinx-io API fix, the Kotlin 2.4.20 upgrade, and the publish dry run
- Michal Harakal (@michalharakal) — grouped-query-native
scaledDotProductAttentionand its StableHLO lowering, structural schedule defaults, and schedule-aware graph execution contexts (SKEEP-005 phase 2)
- Michal Harakal (@michalharakal) — the billion-parameter
export arc: allocation-free void tracing, aliased constant extraction, strict StableHLO
conversion, the array-free
BufferHandle.Floatspath, and the sharded SafeTensors loader
- Michal Harakal (@michalharakal) — the Gemma 4 engine-gap
arc: self-healing kernel dispatch and the
ViewKernelPackSPI, dense FP32 for mapped/off-heap storage, the decode-shaped FP32 kernel work, Android-native targets across the downstream chain, and the ternary packs joining the self-healing SPI
- Michal Harakal (@michalharakal) — off-heap ternary
storage, zero-copy mmap for
SEQUENTIALI2_S, the AOT GGUF converter and its IREE-facing counterpart in SKaiNET-IREE-tools, and the scoped dense-FP32 activation matmul-chooser fix
- Michal Harakal (@michalharakal) — the SKEEP-003 memory & storage architecture end to end: M0/M1/M2 milestones, the weight-form and placement-resolution arcs, scope-recycled execution, multi-format footprint analysis, the compile-lane carriage arc, the BitNet/ternary kernel track, and the release docs
- Ajith Goveas (@AjithGoveas) — Iris dataset provider (#1044, #1101), now powering the Android classifier tutorial
- Michal Harakal (@michalharakal) — packed-quant
transpose()block-grid correctness fix, all three kernel tiers (#968, #969)
- Michal Harakal (@michalharakal) — off-heap/mmap tensor storage on Android (#921), GGUF
DEQUANTIZE_TO_FP32over-allocation fix (#782), native Q5_0/Q5_1 packed matmul kernels (#708), Apple arm64 runtime FEAT_DotProd dispatch (#958), Apple iOS/macOS Kotlin/Native kernel targets (#959)
- Michal Harakal (@michalharakal) — primitive FP32 fast paths for the eager CPU ops (#949), README pointer to SKaiNET-transformers (#923)
- Michal Harakal (@michalharakal) — Android JNI NEON kernel backend with runtime dotprod dispatch (#943, #945), Android
createRandomAccessSourcestreaming loads (#922), cinterop klib archive embedding (#942), Q4_0 NEON kernel (#939), GGUF loader fail-fast (#919), tensor-storage correctness fixes (#927, #928, #929, #930, #931), AAR release publishing (#947)
- Michal Harakal (@michalharakal) — dynamic tensor dimensions for streaming KV-cache decode (#891), shared narrow-float BF16/FP16 layer (#886), zero-copy transpose for input-major narrow weights (#895), native FP16 matmul kernel (#896), read-once weight tiling in the native narrow kernels (#897)
- @MacOS — least-privilege permissions on the build workflow (#899), MathJax npm install pinned by version (#889)
- Michal Harakal (@michalharakal) —
Lstmlayer (#824),Dropoutmasking (#867), LR schedules (#866), optional/openLinear(#870, #875), SDPA scale fix (#880), autograd fixes (#877),argMaxDAG spec (#878), tokenizer +gatherfixes (#879), Android native IO targets (#836, #842, #845) - @MacOS — OpenSSF Scorecard workflow and badge (#814), commit-hash pinning across all CI workflows and reproducible docs Docker image (#816, #821, #827, #830–#838, #846, #848, #868)
- @MacOS — REUSE compliance CI workflow and status badge (#806, #807)
- Dhia Chemingui (@dhiaspaner) — Android KMP plugin migration (#385, #386)
If you use SKaiNET in your research, please cite it. Citation metadata lives in
CITATION.cff (GitHub's Cite this repository button exports BibTeX and APA
from it). Ready-to-paste BibTeX:
@misc{skainet,
author = {Harakal, Michal and {SKaiNET-developers contributors}},
title = {{SKaiNET}: A {Kotlin} Multiplatform {AI} Framework},
year = {2026},
note = {Version 0.62.4. MIT License},
howpublished = {\url{https://github.com/SKaiNET-developers/SKaiNET}}
}Please include the version you used (each release is tagged).
MIT — see LICENCE.
