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
34 changes: 24 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@

## [Unreleased]

### Fixed

- **`DTypeConstraintResolutionPass` no longer disconnects the nodes it resolves** (`skainet-compile-opt`, #1495): the pass marked a policy-carrying node by removing and re-adding it, which dropped every edge connected to that node, so a `dag { … dtypePolicy(…) }` graph left the pass with those nodes unwired. It now rebuilds the graph with the edges intact, and reports no change on an already resolved graph, so a multi-iteration pipeline converges.
- **A same-dtype `PrecisionBoundary` leaves no cast in a trace** (`skainet-lang-core`, SKEEP-008 phase A2, #1433): `stage("body", dtype = FP32)` inside an FP32 network called `convert`, which returns its input on the CPU backend but is still recorded by the tracing wrapper, so the exported module carried two `stablehlo.convert` `f32 -> f32`. The boundary now passes a tensor of the target dtype through without calling `convert`.
- **`concat` backward with inputs of different sizes** (`skainet-compile-dag`, #1490): the rule split the upstream gradient by one input's size and picked chunk `offset / size`, which only lines up when every input has the same extent along the concat axis; other shapes got another input's slice, or `null`, without an error. Each input now receives its own `narrow` of the upstream at the offset the forward gave it, for any sizes and a negative `dim`.
- **`squeeze` backward with no axis** (`skainet-compile-dag`, #1490): `squeeze(x)` removes every size-1 axis, but the backward unsqueezed dim 0 alone, so `x: [1, 3, 1]` got a `[1, 3]` gradient. The gradient is now the upstream reshaped to the input's shape.
## [0.64.0] - 2026-11-11

### Removed

- **`dense<TLayer>()`, `stage<TStage>()` and the mixed-precision scaffold** (`skainet-lang-core`, SKEEP-008 phase A4, #1433): the generic overloads of `NeuralNetworkDsl`, deprecated at `ERROR` level in 0.63.0, are gone; use `dense(outputDimension, dtype = FP16) { }` / `stage(id, dtype = FP16) { }`. `MixedPrecisionModule`, `InternalMixedPrecisionModule` and `MixedPrecisionTensorOps`, deprecated in 0.63.0 and never implemented, are removed, together with `MixedPrecisionException`, which only they could throw. A third-party `NeuralNetworkDsl` implementation drops its three overrides.
Headline: **every differentiable op has a backward rule, and mixed precision is finished: it trains, exports without
redundant casts and is checked by the compiler** (see *Added*). `scaledDotProductAttention` gets its adjoint, so
attention layers train and the ops matrix Backward column reads 60/60. `TensorOps.convert` is differentiable, so the
layers behind `dense(64, dtype = BF16) { }` / `stage("head", dtype = FP16) { }` train too; `CastFoldingPass` folds
the casts between adjacent boundaries that change no value, and a boundary's dtype travels with the traced graph as a
`DTypePolicy.Prefer` that `DTypeConstraintResolutionPass` checks. SKEEP-008 is implemented. The generic
`dense<T>()` / `stage<T>()` overloads and the `MixedPrecision*` scaffold, deprecated in 0.63.0, are removed.

### Added

Expand All @@ -21,7 +20,22 @@
- **Training through a mixed-precision boundary** (`skainet-lang-core`, `skainet-compile-dag`, SKEEP-008 phase A3, #1433): `TensorOps.convert` is a `@Diff` op. Its backward rule treats the cast as the identity and casts the upstream gradient back to the source dtype, so a gradient entering an FP16 / BF16 region is rounded to that format and the layers behind `dense(dtype = …)` / `stage(id, dtype = …)` train; a cast from or to an integer dtype has no gradient. `MixedPrecisionBackwardTest` checks the rule, a finite-difference gradient through FP16 boundaries, parity with the all-FP32 network, and an SGD run. The inference-only notes are gone; the ops matrix Backward column reads 60/60.
- **SKEEP-008 acceptance tests** (`skainet-lang-core`, `skainet-backend-cpu`, #1433): `repackTo(Dense(4))` is checked bit-exact for Q5_0, Q5_1, Q5_K, Q6_K and `BITNET_B1_58` too, and one FP32 network with both a BF16 `dense` and an FP16 `stage` runs against the FP32 reference.
- **Mixed-precision export test** (`skainet-compile-hlo`, SKEEP-008 phase A2, #1433): `MixedPrecisionExportTest` traces an FP32 network with one BF16 `dense` and one FP16 `stage` and pins the result: one `convert` pair per boundary in the graph, inner layers typed in the narrow dtype, and `stablehlo.convert` `f32 -> bf16`, `bf16 -> f32`, `f32 -> f16`, `f16 -> f32` in the exported module.
- **`scaledDotProductAttention` backward** (`skainet-compile-dag`, #1384): the attention adjoint (dQ, dK, dV and the dense mask's gradient) replaces the stub that returned `null`, so attention layers train. It honours `scale`, `causal`, sliding `window` / `rightContext`, `keyLengths` and the dense mask, recomputes the probabilities instead of storing them, and reduces grouped-query heads into their shared K/V. Masked scores get exactly zero gradient. `SdpaBackwardTest` checks every option against finite differences and three cases against PyTorch autograd; `stubbedBackwardOpNames` is empty and the coverage matrix's Backward column is complete.
- **`scaledDotProductAttention` backward** (`skainet-compile-dag`, #1384): the attention adjoint (dQ, dK, dV and the dense mask's gradient) replaces the stub that returned `null`, so attention layers train. It honours `scale`, `causal`, sliding `window` / `rightContext`, `keyLengths` and the dense mask, recomputes the probabilities instead of storing them, and reduces grouped-query heads into their shared K/V. Masked scores get exactly zero gradient. `SdpaBackwardTest` checks every option against finite differences and three cases against PyTorch autograd; `stubbedBackwardOpNames` is empty and the coverage matrix's Backward column is complete. `TransformerBlockBackwardTest` checks all 204 parameter entries of a pre-norm Transformer block (embeddings, RMSNorm, two-head causal attention, gated GELU feed-forward) against central finite differences.

### Changed

- **Dependencies**: Gradle wrapper 9.9.0 (#1488), Dokka Gradle plugin 2.3.0 (#1486), json-schema-validator 3.0.9 (#1487), `actions/setup-java` 6.2.0 (#1485), CodeQL upload-sarif 4.39.3 (#1484).

### Removed

- **`dense<TLayer>()`, `stage<TStage>()` and the mixed-precision scaffold** (`skainet-lang-core`, SKEEP-008 phase A4, #1433): the generic overloads of `NeuralNetworkDsl`, deprecated at `ERROR` level in 0.63.0, are gone; use `dense(outputDimension, dtype = FP16) { }` / `stage(id, dtype = FP16) { }`. `MixedPrecisionModule`, `InternalMixedPrecisionModule` and `MixedPrecisionTensorOps`, deprecated in 0.63.0 and never implemented, are removed, together with `MixedPrecisionException`, which only they could throw. A third-party `NeuralNetworkDsl` implementation drops its three overrides.

### Fixed

- **`DTypeConstraintResolutionPass` no longer disconnects the nodes it resolves** (`skainet-compile-opt`, #1495): the pass marked a policy-carrying node by removing and re-adding it, which dropped every edge connected to that node, so a `dag { … dtypePolicy(…) }` graph left the pass with those nodes unwired. It now rebuilds the graph with the edges intact, and reports no change on an already resolved graph, so a multi-iteration pipeline converges.
- **A same-dtype `PrecisionBoundary` leaves no cast in a trace** (`skainet-lang-core`, SKEEP-008 phase A2, #1433): `stage("body", dtype = FP32)` inside an FP32 network called `convert`, which returns its input on the CPU backend but is still recorded by the tracing wrapper, so the exported module carried two `stablehlo.convert` `f32 -> f32`. The boundary now passes a tensor of the target dtype through without calling `convert`.
- **`concat` backward with inputs of different sizes** (`skainet-compile-dag`, #1490): the rule split the upstream gradient by one input's size and picked chunk `offset / size`, which only lines up when every input has the same extent along the concat axis; other shapes got another input's slice, or `null`, without an error. Each input now receives its own `narrow` of the upstream at the offset the forward gave it, for any sizes and a negative `dim`.
- **`squeeze` backward with no axis** (`skainet-compile-dag`, #1490): `squeeze(x)` removes every size-1 axis, but the backward unsqueezed dim 0 alone, so `x: [1, 3, 1]` got a `[1, 3]` gradient. The gradient is now the upstream reshaped to the input's shape.

## [0.63.0] - 2026-11-06

Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ authors:
orcid: https://orcid.org/0009-0006-7981-4075
- name: SKaiNET-developers contributors
website: https://github.com/SKaiNET-developers
version: "0.63.0"
date-released: "2026-11-06"
version: "0.64.0"
date-released: "2026-11-11"
license: MIT
repository-code: https://github.com/SKaiNET-developers/SKaiNET
url: https://skainet-developers.github.io/SKaiNET/
Expand Down
59 changes: 31 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Add the core dependencies (Gradle Kotlin DSL):
```kotlin
dependencies {
// Recommended: import the umbrella BOM and drop versions on the engine modules.
implementation(platform("sk.ainet:skainet-bom:0.63.0"))
implementation(platform("sk.ainet:skainet-bom:0.64.0"))

implementation("sk.ainet.core:skainet-lang-core")
implementation("sk.ainet.core:skainet-backend-cpu")
Expand Down Expand Up @@ -308,32 +308,28 @@ val withoutLabel = dataPipeline<RawDataset>()

---

## What's New in 0.63.0

Block-quantized weights reach an NPU through StableHLO, and the engine gains mixed precision, explicit re-encoding,
`convTranspose2d` and a working ONNX import path.

- **Q8_0 / Q4_0 weights to an NPU** — `PackedConstantHandling.KEEP_PACKED` keeps a packed weight in the traced
graph, `BlockQuantMatmulFusionPass` folds every `x · Wᵀ` over one, and the StableHLO export emits the
dequantize-then-`batch_matmul` chain with `i8` / `i4` codes and `bf16` scales inline. Torq 2.3.0
(`--iree-input-type=stablehlo-torq`) compiles it to its `mixed_quant_matmul` kernel: the whole FunctionGemma 270M
decode step runs on an SL2610 NPU in one dispatch at 162 ms per step, with the same next token as the f32
reference. Weights from the GGUF loader (off-heap) export the same as heap ones.
- **StableHLO and TOSA passes** — the `STABLE_HLO` phase of a target optimizer rewrites the emitted module, and
`DivideByBroadcastPass`, `RankTwoWeightMatmulPass` and `TosaFinish` prepare a module for StableHLO's own TOSA
legalization (Moonshine's streaming encoder runs on an NPU through `tosa-torq` in one dispatch). `Int4` maps to
`i4`.
- **Mixed precision per layer** — `dense(64, dtype = BF16) { }` and `stage("head", dtype = FP16) { }` build a layer
in another dtype behind a `PrecisionBoundary` that casts in and out (inference only). The generic
`dense<T>()` / `stage<T>()` overloads, which could only throw, are deprecated at `ERROR` level.
- **Re-encoding storages** — `repackTo(Dense(4))` decodes every packed encoding with a dequantizer bit-exactly, and
`requantizeTo` is the explicit lossy direction.
- **New ops and layers** — `convTranspose2d` with backward and StableHLO lowering, the `GroupNormalization` forward
pass, `sliceCopy {}`, and precision, recall and F1 metrics with binary, macro and micro averaging.
- **ONNX** — `LeakyRelu`, `Silu`, `Clip`, `BatchNormalization`, `Pad` and `ConvTranspose` import as executable ops,
and `OnnxModelReader.open` reads metadata up front and each tensor on demand.
- **Fixes** — the JVM tokenizer's `Split` regex follows Unicode character classes, and `randn` never returns
`Inf` / `NaN`.
## What's New in 0.64.0

Every differentiable op has a backward rule, and mixed precision is finished: it trains, exports without redundant
casts and is checked by the compiler.

- **Attention trains** — `scaledDotProductAttention` has its adjoint (dQ, dK, dV and the dense mask's gradient) for
every option: `scale`, `causal`, sliding windows, key lengths, grouped-query heads. It is checked against finite
differences and PyTorch autograd, and a whole Transformer block is checked parameter by parameter. The ops
matrix Backward column reads 60/60.
- **Training through mixed precision** — `TensorOps.convert` is differentiable, so the layers behind
`dense(64, dtype = BF16) { }` and `stage("head", dtype = FP16) { }` train. The cast passes the gradient straight
through and rounds it to the region's format; integer casts have no gradient.
- **Fewer casts in the export** — `CastFoldingPass`, part of the default optimization pipelines, folds casts that
change no value: the `bf16 -> f32 -> f16` between two adjacent boundaries exports as one `stablehlo.convert`, and
a same-dtype boundary exports none. A narrowing round trip is never folded.
- **A layer's dtype is checked** — ops traced inside a boundary carry `DTypePolicy.Prefer(dtype)`, and
`DTypeConstraintResolutionPass` reports a region that a later pass retyped. `ComputeGraphExecutor` runs
`convert` nodes, so a traced mixed-precision graph executes.
- **Removed** — the generic `dense<T>()` / `stage<T>()` overloads and the `MixedPrecision*` scaffold, deprecated in
0.63.0; use the `dtype =` argument.
- **Fixes** — `concat` backward with inputs of different sizes, `squeeze` backward with no axis, and
`DTypeConstraintResolutionPass` no longer drops the edges of the nodes it resolves.

See [CHANGELOG.md](CHANGELOG.md) for full release notes, including every prior release.

Expand All @@ -359,6 +355,13 @@ We love contributions! Whether it's a new operator, documentation, or a bug fix:

Browse the full codebase documentation on [DeepWiki](https://deepwiki.com/SKaiNET-developers/SKaiNET).

### Contributors (0.64.0)

- **Michal Harakal** ([@michalharakal](https://github.com/michalharakal)) — the `scaledDotProductAttention` backward
with its finite-difference and Transformer-block tests, the `concat` / `squeeze` backward fixes, the rest of
SKEEP-008 (differentiable `convert`, export tests, `CastFoldingPass`, the checked boundary dtype, the scaffold
removal), and the release

### Contributors (0.63.0)

- **Michal Harakal** ([@michalharakal](https://github.com/michalharakal)) — the block-quant StableHLO export and its
Expand Down Expand Up @@ -511,7 +514,7 @@ from it). Ready-to-paste BibTeX:
author = {Harakal, Michal and {SKaiNET-developers contributors}},
title = {{SKaiNET}: A {Kotlin} Multiplatform {AI} Framework},
year = {2026},
note = {Version 0.63.0. MIT License},
note = {Version 0.64.0. MIT License},
howpublished = {\url{https://github.com/SKaiNET-developers/SKaiNET}}
}
```
Expand Down
2 changes: 1 addition & 1 deletion docs/antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ asciidoc:
framework_name: SKaiNET
# Current SKaiNET release — bump once per release; referenced as
# {skainet_version} in dependency snippets (blocks need subs="attributes+").
skainet_version: 0.63.0
skainet_version: 0.64.0
ksp_version: 2.2.21-2.0.5
dokka_version: 2.1.0
asciidoctorj_version: 3.0.0
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
= Kernel × platform support matrix
:description: Which compute-kernel provider serves each weight format on each KMP target.

Generated from `kernel-support.json` (version `0.63.0`) by `KernelSupportMatrixTest` — registry introspection of the registered `KernelProvider` implementations. Do not edit by hand; run `./gradlew generateKernelMatrix` to refresh.
Generated from `kernel-support.json` (version `0.64.0`) by `KernelSupportMatrixTest` — registry introspection of the registered `KernelProvider` implementations. Do not edit by hand; run `./gradlew generateKernelMatrix` to refresh.

Each cell is the best (highest-priority) provider that serves `Float32 × format` `matmul` on that platform: *native-ffm* / *native-jni* / *native-cinterop* (100) → *panama-vector* / *wasm-simd128* (50) → *scalar* (0). An empty cell (`—`) means no provider carries a kernel there (the format is dequant-to-FP32 only). The JS/WASM column assumes `WasmSimd.install()` has run; without the sidecar it is the scalar floor.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
= AI-NET Operators Reference

Generated from version `0.63.0` on 2026-11-01
Generated from version `0.64.0` on 2026-11-11

== Operators by Modality

Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/reference/ops-status-matrix.adoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
= Operator Coverage Matrix
:description: Cross-backend status for every operator function in SKaiNET.

Generated from `operators.json` version `0.63.0` on 2026-11-01.
Generated from `operators.json` version `0.64.0` on 2026-11-11.

Rows are `Operator.function` pairs. A gap is marked in source with `@NotImplemented(reason = …, issue = …)` (or `@InProgress`) and shows up here as ❌ (or 🚧) linked to its tracking issue; the PR that closes the gap removes the annotation. See xref:contributing/issue-taxonomy.adoc[Issue taxonomy] to pick one up.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ An Android project with Kotlin. Add the SKaiNET modules:
[source,kotlin]
----
dependencies {
implementation("sk.ainet.core:skainet-lang-core:0.63.0") // tensors, DSL, training
implementation("sk.ainet.core:skainet-backend-cpu:0.63.0") // CPU ops
implementation("sk.ainet.core:skainet-compile-dag:0.63.0") // autograd (training context)
implementation("sk.ainet.core:skainet-data-api:0.63.0") // Dataset / DataBatch
implementation("sk.ainet.core:skainet-data-simple:0.63.0") // embedded Iris
runtimeOnly("sk.ainet.core:skainet-backend-jni-cpu:0.63.0") // NEON kernels (see below)
implementation("sk.ainet.core:skainet-lang-core:0.64.0") // tensors, DSL, training
implementation("sk.ainet.core:skainet-backend-cpu:0.64.0") // CPU ops
implementation("sk.ainet.core:skainet-compile-dag:0.64.0") // autograd (training context)
implementation("sk.ainet.core:skainet-data-api:0.64.0") // Dataset / DataBatch
implementation("sk.ainet.core:skainet-data-simple:0.64.0") // embedded Iris
runtimeOnly("sk.ainet.core:skainet-backend-jni-cpu:0.64.0") // NEON kernels (see below)
}
----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Origin: the stub inventory behind the operator coverage matrix (https://github.c

| B1 | Lossless `repackTo`, lossy `requantizeTo` | https://github.com/SKaiNET-developers/SKaiNET/pull/1470[#1470], 0.63.0
| A1 | `PrecisionBoundary`, `dense(dtype = …)`, `stage(id, dtype = …)`, deprecations | https://github.com/SKaiNET-developers/SKaiNET/pull/1469[#1469], 0.63.0
| A2 | Trace and StableHLO export pinned by `MixedPrecisionExportTest`; a same-dtype boundary no longer records a cast | https://github.com/SKaiNET-developers/SKaiNET/pull/1492[#1492]
| A3 | `convert` is `@Diff`; gradient check and training through a boundary in `MixedPrecisionBackwardTest` | https://github.com/SKaiNET-developers/SKaiNET/pull/1492[#1492]
| A4 | Generic `dense<T>()` / `stage<T>()` overloads and the `MixedPrecision*` scaffold removed | https://github.com/SKaiNET-developers/SKaiNET/pull/1492[#1492]
| A2 | Trace and StableHLO export pinned by `MixedPrecisionExportTest`; a same-dtype boundary no longer records a cast | https://github.com/SKaiNET-developers/SKaiNET/pull/1492[#1492], 0.64.0
| A3 | `convert` is `@Diff`; gradient check and training through a boundary in `MixedPrecisionBackwardTest` | https://github.com/SKaiNET-developers/SKaiNET/pull/1492[#1492], 0.64.0
| A4 | Generic `dense<T>()` / `stage<T>()` overloads and the `MixedPrecision*` scaffold removed | https://github.com/SKaiNET-developers/SKaiNET/pull/1492[#1492], 0.64.0
|===

The sections below are the proposal as accepted. Where the implementation differs, the text
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
GROUP=sk.ainet.core
VERSION_NAME=0.63.0
VERSION_NAME=0.64.0
POM_DESCRIPTION=SKaiNET

POM_URL=https://github.com/SKaiNET-developers/skainet/
Expand Down
Loading