diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a8727b2..0d84d028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ ## [Unreleased] +## [0.54.0] - 2026-09-06 + +Headline: **every `ExecutionContext` gets a `Schedule` — and the CI run that exercised it found a +real deadlock.** `sk.ainet.context.schedule.Schedule` splits *what* an op computes from *how its +independent chunks spread across cores*: `scaledDotProductAttention` is the first scheduled op, +`parallelChunks` no longer hides a `runBlocking(Dispatchers.Default)` island, and a JVM +`CoroutineSchedule` spreads chunks across a shared pool. Turning it on deadlocked +`skainet-backend-cpu:jvmTest` on CI's 4-vCPU runner for three of the last four `test (jvm)` runs — +not the OOM a first pass assumed, but a `coroutineScope` waiting on children the pool had no thread +left to run. A region is now a shared chunk queue instead: whatever the pool is doing, the caller +can always finish its own region alone. Also in this release: `SafeTensorsParametersLoader`'s +`tensorFilter` reaches parity with the sharded loader, and the `sk.ainet.lang.memory` API drops its +`ExperimentalMemoryApi` opt-in gate now that SKEEP-003's M0–M2 have shipped. + ### Added - **Schedules — the compute-level algorithm/schedule split (SKEEP-005)** @@ -40,6 +54,23 @@ complete in 0.49.0. `ExperimentalMemoryApi` is deleted along with every `@OptIn`/ `@ExperimentalMemoryApi` annotation referencing it. +### Fixed + +- **`CoroutineSchedule` deadlock when a region is entered from its own pool** + ([#1262](https://github.com/SKaiNET-developers/SKaiNET/issues/1262), + [#1264](https://github.com/SKaiNET-developers/SKaiNET/issues/1264), + [#1266](https://github.com/SKaiNET-developers/SKaiNET/pull/1266)): `CoroutineSchedule.forRange` + ran a region as `runBlocking { coroutineScope { launch(Dispatchers.Default) … } }`, and a + `coroutineScope` waits for every child — including ones the pool never got a thread for. Once + every `Dispatchers.Default` worker was itself inside a region (routine on a 4-vCPU CI runner; + never reproduced on a many-core laptop), nobody was left to run the children and the JVM parked + forever — the intermittent `test (jvm)` timeout that #1264's "force fully serial" fix mistook for + an OOM hang. A region is now a shared chunk queue: `tasks - 1` helpers dispatch to the pool, the + caller runs chunk 0 and then drains the queue itself, and waits only for chunks a thread has + already claimed — a caller can always finish its own region alone, whatever the pool is doing. + Contract unchanged (first failure wins, nested regions run inline, writes happen-before return); + public API unchanged. + ## [0.53.0] - 2026-09-02 Headline: **the export pipeline emits billion-parameter models.** Tracing a 4.5B-parameter diff --git a/README.md b/README.md index 7f430f92..865d2f97 100644 --- a/README.md +++ b/README.md @@ -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.53.0")) + implementation(platform("sk.ainet:skainet-bom:0.54.0")) implementation("sk.ainet.core:skainet-lang-core") implementation("sk.ainet.core:skainet-backend-cpu") @@ -308,27 +308,26 @@ val withoutLabel = dataPipeline() --- -## What's New in 0.53.0 - -The export pipeline emits billion-parameter models: - -- **Full Gemma 3n E2B export** — the DSL → tape → StableHLO path traced a 4.5B-parameter model - into an OOM at a 46 GB heap; the same repro now exports the whole model in under a minute. - Shape-only tracing no longer materializes zero buffers, graph constants alias the live weights - instead of copying them, and ≥2 GiB constants travel as `BufferHandle.Floats` — an aliased - float array — because the tied embedding is exactly one byte over what a JVM byte array holds. -- **Conversion fails loudly** — `StableHloConverter` is strict by default: an unconvertible node - throws `HloConversionException`, an unresolved operand throws `MissingOperandException`, and a - packed weight reaching constant extraction throws `PackedConstantException` instead of silently - becoming a function argument. `ConversionErrorPolicy.LENIENT` restores the old - comment-and-continue behavior for inspection. -- **Sharded SafeTensors in the engine** — `ShardedSafeTensorsParametersLoader` consumes - `model.safetensors.index.json` with the single-file loader's BF16/FP16 policies, a fail-fast - dtype pre-scan, and a `tensorFilter` hook; the per-family hand-rolled loaders downstream can - collapse onto it. -- **Registry gaps the strictness surfaced** — `clamp` and the camelCase `indexSelect` the tracer - actually emits now lower, and `createBasic` registers the neural-net converter like - `createExtended` does. +## What's New in 0.54.0 + +Structured concurrency lands as a first-class citizen — and the CI run that exercised it found a +real deadlock: + +- **`Schedule` on every `ExecutionContext`** (SKEEP-005) — `sk.ainet.context.schedule.Schedule` + splits *what* an op computes from *how its independent chunks spread across cores*. + `scaledDotProductAttention` is the first scheduled op, `parallelChunks` no longer hides a + `runBlocking(Dispatchers.Default)` island, and the JVM `CoroutineSchedule.hardware()` default + spreads chunks across cores while `Schedule.Sequential` keeps every kernel single-threaded. +- **A real deadlock, found by turning scheduling on** — `CoroutineSchedule.forRange`'s region + waited on children the pool had no thread left to run, once every worker was itself inside a + region; it looked like the OOM hang a first CI fix assumed. A region is now a shared chunk + queue, so a caller can always finish its own region alone, whatever the pool is doing. +- **`tensorFilter` on the single-file `SafeTensorsParametersLoader`** — parity with the sharded + loader; lets a family load selectively from a checkpoint that carries tensors the requested + dtype can't accept. +- **`ExperimentalMemoryApi` opt-in gate removed** — SKEEP-003's M0–M2 shipped complete back in + 0.49.0, so `Storage`, `Scope`, `Format`, `TensorView`, `WeightForm`, and the rest of + `sk.ainet.lang.memory` no longer need `@OptIn`. See [CHANGELOG.md](CHANGELOG.md) for full release notes, including every prior release. @@ -354,6 +353,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.54.0) + +- **Michal Harakal** ([@michalharakal](https://github.com/michalharakal)) — the SKEEP-005 + structured-concurrency Schedule API, the coroutine-pool deadlock it uncovered on CI and its + shared-chunk-queue fix, `SafeTensorsParametersLoader` `tensorFilter` parity, and retiring the + `ExperimentalMemoryApi` opt-in gate now that SKEEP-003 has shipped + ### Contributors (0.53.0) - **Michal Harakal** ([@michalharakal](https://github.com/michalharakal)) — the billion-parameter diff --git a/docs/antora.yml b/docs/antora.yml index ac0e802c..2af15377 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -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.53.0 + skainet_version: 0.54.0 ksp_version: 2.2.21-2.0.5 dokka_version: 2.1.0 asciidoctorj_version: 3.0.0 diff --git a/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc b/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc index 51bde34d..d6e96796 100644 --- a/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc +++ b/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc @@ -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.53.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.54.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* (100) → *panama-vector* (50) → *scalar* (0). An empty cell (`—`) means no provider carries a kernel there (the format is dequant-to-FP32 only). diff --git a/docs/modules/ROOT/pages/reference/operators/generated/index.adoc b/docs/modules/ROOT/pages/reference/operators/generated/index.adoc index 541a83fb..0700d23c 100644 --- a/docs/modules/ROOT/pages/reference/operators/generated/index.adoc +++ b/docs/modules/ROOT/pages/reference/operators/generated/index.adoc @@ -1,6 +1,6 @@ = AI-NET Operators Reference -Generated from version `0.53.0` on 2026-09-02 +Generated from version `0.54.0` on 2026-09-06 == Operators by Modality diff --git a/docs/modules/ROOT/pages/reference/ops-status-matrix.adoc b/docs/modules/ROOT/pages/reference/ops-status-matrix.adoc index cec66f93..8a456f69 100644 --- a/docs/modules/ROOT/pages/reference/ops-status-matrix.adoc +++ b/docs/modules/ROOT/pages/reference/ops-status-matrix.adoc @@ -1,7 +1,7 @@ = Operator Coverage Matrix :description: Cross-backend status for every operator function in SKaiNET. -Generated from `operators.json` version `0.53.0` on 2026-09-02. +Generated from `operators.json` version `0.54.0` on 2026-09-06. Rows are `Operator.function` pairs. The `Validated` column shows whether the function's documentation has been DARC-validated by a reviewer (see xref:contributing/darc-workflow.adoc[DARC workflow]). Remaining columns are backends that appear in any function's `statusByBackend` map — a missing entry means the backend makes no claim about the function (treat it as "unknown", not "not supported"). diff --git a/docs/modules/ROOT/pages/tutorials/android-classifier-getting-started.adoc b/docs/modules/ROOT/pages/tutorials/android-classifier-getting-started.adoc index 825ee0b0..13c26f73 100644 --- a/docs/modules/ROOT/pages/tutorials/android-classifier-getting-started.adoc +++ b/docs/modules/ROOT/pages/tutorials/android-classifier-getting-started.adoc @@ -19,12 +19,12 @@ An Android project with Kotlin. Add the SKaiNET modules: [source,kotlin] ---- dependencies { - implementation("sk.ainet.core:skainet-lang-core:0.53.0") // tensors, DSL, training - implementation("sk.ainet.core:skainet-backend-cpu:0.53.0") // CPU ops - implementation("sk.ainet.core:skainet-compile-dag:0.53.0") // autograd (training context) - implementation("sk.ainet.core:skainet-data-api:0.53.0") // Dataset / DataBatch - implementation("sk.ainet.core:skainet-data-simple:0.53.0") // embedded Iris - runtimeOnly("sk.ainet.core:skainet-backend-jni-cpu:0.53.0") // NEON kernels (see below) + implementation("sk.ainet.core:skainet-lang-core:0.54.0") // tensors, DSL, training + implementation("sk.ainet.core:skainet-backend-cpu:0.54.0") // CPU ops + implementation("sk.ainet.core:skainet-compile-dag:0.54.0") // autograd (training context) + implementation("sk.ainet.core:skainet-data-api:0.54.0") // Dataset / DataBatch + implementation("sk.ainet.core:skainet-data-simple:0.54.0") // embedded Iris + runtimeOnly("sk.ainet.core:skainet-backend-jni-cpu:0.54.0") // NEON kernels (see below) } ---- diff --git a/gradle.properties b/gradle.properties index 468c5cf2..d54aa31e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=sk.ainet.core -VERSION_NAME=0.53.0 +VERSION_NAME=0.54.0 POM_DESCRIPTION=SKaiNET POM_URL=https://github.com/SKaiNET-developers/skainet/