diff --git a/CHANGELOG.md b/CHANGELOG.md index 9feca3fd..e9a1b95b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,14 +11,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -Against engine **0.61.0** (SKEEP-007, the browser lane). The BOM re-exports `skainet-bom:0.61.0`. -Engine 0.61.0 removed its SKEEP-006 compatibility aliases as scheduled; nothing here referenced them. +## [0.62.0] — 2026-10-17 + +**Laya on the packed Q8_0 lane.** Against engine **0.61.0** (SKEEP-007, the browser lane: +quantize-at-load for the SafeTensors and GGUF loaders, the `skainet-backend-wasm-simd` SIMD128 +sidecar, packed-weight dispatch order). The Laya loader is common code over a `RandomAccessSource`, +and `LayaWeightForms.q8_0For` quantizes the encoder and head projections and the token table to +Q8_0 at load — about a quarter of the FP32 bytes, served by the packed kernels on every tier. A +decision-quality gate (every fixture choice agrees with FP32, no value shifts by more than 0.05, +measured 0.035) is the contract of that lane. The BOM re-exports `skainet-bom:0.61.0`. Engine +0.61.0 removed its SKEEP-006 compatibility aliases as scheduled; nothing here referenced them. The +transformers SKEEP-006 schedule moves by one more minor (see *Deprecated*). ### Added - **Laya on the packed Q8_0 lane** (SKEEP-007 Tier 2 consumer). `LayaModelLoader.load` is common code: the four side files as text plus a `RandomAccessSource` for `model.safetensors`, so the JVM, Android and browser builds share one loader; the JVM `load(directory: Path, …)` is now an extension over it (source-compatible). `weightForm` / `weightFormFor` pass through to the engine's SafeTensors loader, and `LayaWeightForms.q8_0For` quantizes the encoder and head projections and the 256k × 768 token table to Q8_0 at load — about a quarter of the FP32 bytes, served by the packed kernels on every tier. `LayaWeightPreparation` splits the fused `Wqkv` / `Wi` / `in_proj` matrices in their packed form (row blocks are contiguous byte ranges) and wraps the token table as a `PackedRowDequantTensorData`, so the embedding reads rows on demand. - **Decision-quality gate for the quantized lane**: `LayaQuantizedParityTest` (model-gated) requires every `predict.json` choice of the Q8_0 agent to agree with the FP32 agent's and no probability, score, noul value or confidence to shift by more than 0.05 (`LAYA_Q8_TOL`; measured 2026-10-16 on the multilingual checkpoint: 5/5 choices agree, 45 values, max shift 0.035). FP32 parity (1.5e-4) remains the contract of the reference lane. On the eager JVM path the Q8_0 lane is a memory measure, not a speed one — the row-major packed kernels run one activation row at a time, so the six fixture cases take 173 s against 69 s in FP32 on a 12-core laptop; the speed comes with the browser sidecar and the compiled lane. +### Changed + +- **Engine 0.61.0.** The version catalog moves `sk.ainet.core:*` to 0.61.0; the new + `skainet-backend-wasm-simd` module is available to browser builds through the engine BOM. + +### Deprecated + +- **Schedule moved by one more minor.** The SKEEP-006 legacy surface (`GGUFTokenizer` and its + strategies, `TokenizerType`, `SentencePieceSpecialTokens`, `TekkenTokenizerAdapter`, `ModelRegistry`, + the `sk.ainet.apps.kllama.GGUFTokenizer` alias) stays at WARNING in 0.62, goes to ERROR in 0.63 + and leaves in 0.64; `TokenizerFactory` and the `sk.ainet.apps.llm.Tokenizer` sub-interface are + deprecated in 0.63 and removed in 0.64. This release carries the engine bump and the quantized + Laya lane; the runtimes, CLIs and benchmarks still reach the engine through the factory, and that + migration stays a release of its own. + +## [0.61.0] — 2026-10-16 **Padded batches for the encoders.** Against engine **0.60.0**, which gives `scaledDotProductAttention` key lengths, a window and a right context carried as op metadata diff --git a/README.md b/README.md index 4d2b4b48..e82eeb3e 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,11 @@ Honest status — see the project-status note at the top of this README. ## Current release -The current release is **0.61.0**, against **SKaiNET 0.60.0**. +The current release is **0.62.0**, against **SKaiNET 0.61.0**. + +**Laya on the packed Q8_0 lane.** SKaiNET 0.61.0 (SKEEP-007, the browser lane) brings quantize-at-load for the SafeTensors and GGUF loaders, a SIMD128 kernel sidecar for browser builds and the packed-weight dispatch order. The Laya loader is common code over a `RandomAccessSource`, so the JVM, Android and browser builds share one loader, and `LayaWeightForms.q8_0For` quantizes the encoder and head projections and the 256k × 768 token table to Q8_0 at load — about a quarter of the FP32 bytes, served by the packed kernels on every tier. The lane is gated on decision quality: every `predict.json` choice agrees with FP32 and no value shifts by more than 0.05 (measured 0.035, 5/5 choices). FP32 parity stays the contract of the reference lane. + +It builds on **0.61.0**, against **SKaiNET 0.60.0**: **Padded batches for the encoders.** SKaiNET 0.60.0 gives `scaledDotProductAttention` key lengths, a window and a right context carried as op metadata (`maskKind`), with a `skainet.sdpa` composite for targets that have a fused attention kernel. BERT-class encoders and the Laya decision models run the sequences of one request as right-padded batches (#490): `BertEncoderRuntime.encodeBatch` / `encodePadded`, `LayaDecisionModel.logitsBatch`, the Laya adapter with `batchSupport = true`. One attention node per layer takes the batch's lengths instead of a dense mask, so the compiled path gets the lengths as a graph input. The multilingual predict fixtures reproduce through the batched path (55 values, max |diff| 4.99e-5) and batched and single logits agree within 1.9e-6. On the eager FP32 CPU path a batch saves about 10 % per question at best; the gain is for the compiled lane and accelerators. The SKEEP-006 legacy tokenizer surface stays at WARNING in 0.61; the schedule moves by one more minor (ERROR in 0.62, gone in 0.63). @@ -295,7 +299,7 @@ The recommended way to consume is via the BOM. It pins every published `skainet- ```kotlin dependencies { - implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) // Versions resolved from the BOM: implementation("sk.ainet.transformers:skainet-transformers-core") diff --git a/docs/modules/ROOT/pages/how-to/export-moonshine-v2-streaming.adoc b/docs/modules/ROOT/pages/how-to/export-moonshine-v2-streaming.adoc index d03cdac9..473e5f9b 100644 --- a/docs/modules/ROOT/pages/how-to/export-moonshine-v2-streaming.adoc +++ b/docs/modules/ROOT/pages/how-to/export-moonshine-v2-streaming.adoc @@ -32,7 +32,7 @@ From your own build, resolve the published module and run its main class: ---- val exportTool by configurations.creating dependencies { - exportTool(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + exportTool(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) exportTool("sk.ainet.transformers:skainet-transformers-inference-moonshine") } tasks.register("exportMoonshine") { diff --git a/docs/modules/ROOT/pages/reference/moonshine-encoder.adoc b/docs/modules/ROOT/pages/reference/moonshine-encoder.adoc index 121609b5..5ee88a76 100644 --- a/docs/modules/ROOT/pages/reference/moonshine-encoder.adoc +++ b/docs/modules/ROOT/pages/reference/moonshine-encoder.adoc @@ -24,7 +24,7 @@ a DSL decoder is future work. [source,kotlin] ---- dependencies { - implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) implementation("sk.ainet.transformers:skainet-transformers-inference-moonshine") } ---- diff --git a/docs/modules/ROOT/pages/reference/tokenizer-api.adoc b/docs/modules/ROOT/pages/reference/tokenizer-api.adoc index f968ee9b..2cba51ba 100644 --- a/docs/modules/ROOT/pages/reference/tokenizer-api.adoc +++ b/docs/modules/ROOT/pages/reference/tokenizer-api.adoc @@ -87,8 +87,8 @@ descriptor with `+`. `GGUFModelInfo.identity` is the string identity |=== `HuggingFaceBPETokenizer` (no callers since 0.58) was removed in 0.59.0. The schedule for the -rest moved by one minor in 0.59.0, in 0.60.0 and again in 0.61.0: the legacy classes stay at -WARNING through 0.61, go to ERROR in 0.62 and leave in 0.63, because the runtimes, CLIs and +rest moved by one minor in 0.59.0, 0.60.0, 0.61.0 and again in 0.62.0: the legacy classes stay at +WARNING through 0.62, go to ERROR in 0.63 and leave in 0.64, because the runtimes, CLIs and benchmarks still reach the engine through `TokenizerFactory` and the sub-interface, and moving them is a release of its own. The engine's io-core aliases they wrapped are errors since SKaiNET 0.60.0; new code imports the `sk.ainet.frontend.tokenizer` home directly. diff --git a/docs/modules/ROOT/pages/reference/typed-decisions.adoc b/docs/modules/ROOT/pages/reference/typed-decisions.adoc index 7100a45a..441361f8 100644 --- a/docs/modules/ROOT/pages/reference/typed-decisions.adoc +++ b/docs/modules/ROOT/pages/reference/typed-decisions.adoc @@ -12,7 +12,7 @@ below put that on the JVM path with the same contract for every model. [source,kotlin] ---- dependencies { - implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) implementation("sk.ainet.transformers:skainet-transformers-decisions-api") // the contract, no engine dependency implementation("sk.ainet.transformers:skainet-transformers-decisions-runtime") // validation + acceptance policy implementation("sk.ainet.transformers:skainet-transformers-inference-laya") // Laya as a DecisionAdapter diff --git a/docs/modules/ROOT/pages/tutorials/android-getting-started.adoc b/docs/modules/ROOT/pages/tutorials/android-getting-started.adoc index 0ee8ff3b..786e9f0b 100644 --- a/docs/modules/ROOT/pages/tutorials/android-getting-started.adoc +++ b/docs/modules/ROOT/pages/tutorials/android-getting-started.adoc @@ -55,7 +55,7 @@ dependencies { // self-registers it on ART at process start — nothing to call. runtimeOnly("sk.ainet.core:skainet-backend-jni-cpu") - implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) implementation("sk.ainet.transformers:skainet-transformers-core") implementation("sk.ainet.transformers:skainet-transformers-runtime-kllama") implementation("sk.ainet.transformers:skainet-transformers-inference-llama") diff --git a/docs/modules/ROOT/pages/tutorials/getting-started-java.adoc b/docs/modules/ROOT/pages/tutorials/getting-started-java.adoc index dfd17a7d..aa5f089e 100644 --- a/docs/modules/ROOT/pages/tutorials/getting-started-java.adoc +++ b/docs/modules/ROOT/pages/tutorials/getting-started-java.adoc @@ -25,7 +25,7 @@ In your `build.gradle.kts`: [source,kotlin] ---- dependencies { - implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) implementation("sk.ainet.transformers:skainet-transformers-runtime-kllama") implementation("sk.ainet.transformers:skainet-transformers-agent") @@ -41,7 +41,7 @@ Or in Maven (Maven needs the `-jvm` classifier suffix on platform artifacts): sk.ainet.transformers skainet-transformers-bom - 0.61.0 + 0.62.0 pom import diff --git a/docs/modules/ROOT/pages/tutorials/getting-started-leaf.adoc b/docs/modules/ROOT/pages/tutorials/getting-started-leaf.adoc index a73064e6..38975ba6 100644 --- a/docs/modules/ROOT/pages/tutorials/getting-started-leaf.adoc +++ b/docs/modules/ROOT/pages/tutorials/getting-started-leaf.adoc @@ -34,7 +34,7 @@ encoder output to the advertised dimensionality. The runtime applies it automati [source,kotlin] ---- dependencies { - implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) implementation("sk.ainet.transformers:skainet-transformers-providers") } ---- diff --git a/docs/modules/ROOT/pages/tutorials/llama3-tool-calling.adoc b/docs/modules/ROOT/pages/tutorials/llama3-tool-calling.adoc index 49beb751..8c6d4b13 100644 --- a/docs/modules/ROOT/pages/tutorials/llama3-tool-calling.adoc +++ b/docs/modules/ROOT/pages/tutorials/llama3-tool-calling.adoc @@ -52,7 +52,7 @@ The pieces you need live in three modules: [source,kotlin] ---- dependencies { - implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.61.0")) + implementation(platform("sk.ainet.transformers:skainet-transformers-bom:0.62.0")) implementation("sk.ainet.transformers:skainet-transformers-runtime-kllama") implementation("sk.ainet.transformers:skainet-transformers-agent") diff --git a/gradle.properties b/gradle.properties index b965875b..5870822e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=sk.ainet.transformers -VERSION_NAME=0.61.0 +VERSION_NAME=0.62.0 POM_DESCRIPTION=SKaiNET-transformers