From 185f847878477dbfc5dc51e7ac73b5cbe101ef9b Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Wed, 11 Nov 2026 14:20:59 +0100 Subject: [PATCH 1/4] docs(skeep): SKEEP-003 status matches the code (#1500) An audit at 0.64.0 found the old storage layer still in place beside the new one. The SKEEP goes back to Accepted and gains a status table per acceptance criterion and per open decision, linking the issues that track them. 003a, SKEEP-002, memory-model.adoc and the TensorStorage KDoc lose statements that later work made untrue. Co-Authored-By: Claude Fable 5.2 --- CHANGELOG.md | 4 ++ .../ROOT/pages/explanation/memory-model.adoc | 9 +-- .../002-android-offheap-tensor-storage.adoc | 33 +++++------ .../pages/003-unified-tensor-storage.adoc | 57 ++++++++++++++++++- ...03a-placement-and-planning-resolution.adoc | 8 +-- docs/modules/skeep/pages/index.adoc | 6 +- .../lang/tensor/storage/TensorStorage.kt | 13 +++-- 7 files changed, 95 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac786a7..4652f488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- **SKEEP-003 is back at `Accepted`, with an implementation-status table** (docs, #1500): an audit at 0.64.0 found the new storage model in use and the old layer (`TensorStorage`, `BufferHandle`, `TensorData` downcasts in the ops) still in place, so three of the five acceptance criteria are open. The SKEEP now says which, and what tracks each (#932). SKEEP-003a no longer lists #1148 as open, SKEEP-002's status records the Q4_K / Q6_K mapped staging of #1190 and its device measurement, `memory-model.adoc` drops a limit that #1190 removed, and the `TensorStorage` KDoc stops telling new code to target it. + ## [0.64.0] - 2026-11-11 Headline: **every differentiable op has a backward rule, and mixed precision is finished: it trains, exports without diff --git a/docs/modules/ROOT/pages/explanation/memory-model.adoc b/docs/modules/ROOT/pages/explanation/memory-model.adoc index 8aa0e8d7..c6b72d02 100644 --- a/docs/modules/ROOT/pages/explanation/memory-model.adoc +++ b/docs/modules/ROOT/pages/explanation/memory-model.adoc @@ -204,7 +204,7 @@ ARMv8.2 Cortex-A55 reference board. [cols="2,3",options="header"] |=== | Delivered | Notes -| `Storage` / `Scope` / `TensorView` / `Format` / `Layout` | All targets; `TensorData` implementations are façades over views +| `Storage` / `Scope` / `TensorView` / `Format` / `Layout` | All targets; every `TensorData` family used by the kernels exposes a view. Scoped activations (`StorageFloatTensorData`) sit on a `Storage`; the other classes still own their bytes and wrap the view around them (xref:skeep:003-unified-tensor-storage.adoc[SKEEP-003], implementation status) | `KernelKey` dispatch for matmul | Reference kernel plus packed and ternary kernel packs | Ternary encodings and `bitnet_gemv` | `TQ1_0`, `TQ2_0`, BitNet b1.58; reference kernel plus a NEON pack on Android | Memory plan, device profiles, fit check | `skainet-plan`, mobile/desktop/native profiles, two-pool device check @@ -247,9 +247,10 @@ anything is loaded rather than after an OOM. Known limits, stated rather than implied: -* *Packed weights still reach the managed heap under mapped staging.* The packed matmul SPI takes -`ByteArray`s, so a buffer-aware kernel is future work. Mapping therefore lifts the Android heap -ceiling for dense checkpoints, not yet for a Q4_K_M one. +* *Some packed weights still reach the managed heap under mapped staging.* Q4_K and Q6_K weights +stay in the file mapping (`BufferPackedTensorData`, read by row-major kernels through a direct +buffer, #1190), so a Q4_K_M checkpoint larger than the Android heap loads. The other packed +formats are still handed to their kernels as `ByteArray`s. * *Whole-file mapping only.* Files larger than 2 GB are refused rather than mapped in windows. * *Graph-level planning* (allocating a whole forward pass at once) stays in the downstream compiler by decision (#1134). Device placement itself was resolved in 0.49.0: `AllocationResolver` diff --git a/docs/modules/skeep/pages/002-android-offheap-tensor-storage.adoc b/docs/modules/skeep/pages/002-android-offheap-tensor-storage.adoc index 40f44773..44cd5988 100644 --- a/docs/modules/skeep/pages/002-android-offheap-tensor-storage.adoc +++ b/docs/modules/skeep/pages/002-android-offheap-tensor-storage.adoc @@ -232,14 +232,13 @@ keeps working unchanged and the overload is purely opt-in. Expected consumers: (packed quant blocks) and a synthesized SafeTensors file (dense F32/F16/BF16) produce bit-identical tensors under either placement. -== Implementation status (updated 2026-08-26, for 0.49.0) +== Implementation status (updated 2026-11-11, at 0.64.0) Tracked by SKEEP-003's M2 slice https://github.com/SKaiNET-developers/SKaiNET/issues/1038[#1038]. This SKEEP stays *Draft*: the mechanism is in `develop` (and since 0.49.0 the loader speaks one `WeightForm` — the legacy -`StagingPolicy`/`QuantPolicy`/`WeightOrientation` axes were removed in #1159), the acceptance -criteria that need a physical device are not met yet, and one of them cannot be met until an -unrelated contract is fixed. +`StagingPolicy`/`QuantPolicy`/`WeightOrientation` axes were removed in #1159), one acceptance +criterion has a device measurement behind it, and the others are not recorded. Landed: @@ -257,20 +256,22 @@ Landed: `AndroidGguf.fits(context, path, ctx)` answers from the GGUF header before a byte of payload is read, naming the pool that runs out and what to do about it. +* *Phase 4 for Q4_K and Q6_K* — `BufferPackedTensorData` keeps a packed weight in the file + mapping, and row-major kernels read it through a direct `ByteBuffer` + (https://github.com/SKaiNET-developers/SKaiNET/issues/1189[#1189], PR #1190). Measured on a Pixel 8a with the 256 MB heap cap + (https://github.com/SKaiNET-developers/SKaiNET/issues/1130[#1130]): Qwen2.5-1.5B Q4_K_M, 1.0 GB on disk, loads with 566 KB of + weights on the managed heap and decodes at 153 ms per step; before, it ran out of memory at + load. That is the acceptance criterion "a Q4_K model larger than the heap loads and generates". + Not landed, and why: -* *Packed weights still reach the managed heap.* Mapped staging serves dense F32 tensors as - zero-heap views, but every quantized tensor is still handed to its kernel as a `ByteArray`, - because the packed matmul SPI takes arrays. That is Phase 4, and it is blocked on - https://github.com/SKaiNET-developers/SKaiNET/issues/973[#973] — the packed-quant byte-order - contract — since a buffer-aware kernel first needs an unambiguous answer to *which* byte order it - is reading. Until then the heap ceiling is lifted for dense checkpoints, not for a Q4_K_M one, - which is exactly the acceptance criterion below that remains open. -* *The device numbers.* "≤ 40 MB managed heap for SmolLM2-135M Q8_0" and "a ~600 MB Q4_K model - loads on a 256 MB heap" are measurements on a physical device; the repository's CI has none. They - belong to the `skainet-decode` sample in SKaiNET-transformers, which owns a model and a device - lane. `androidHostTest` covers what can be proven without hardware: the Android compilation - builds, maps, loads, and produces bit-identical tensors under either staging. +* *Other packed formats still reach the managed heap.* Q8_0, Q4_0, Q5_0, Q5_1, Q5_K and the + ternary encodings are handed to their kernels as `ByteArray`s under mapped staging; only Q4_K + and Q6_K have the buffer-reading kernels. +* *The SmolLM2-135M Q8_0 heap figure and the mapped-versus-heap throughput comparison* are not + recorded. Q8_0 is one of the formats above, so the "less than 40 MB" criterion cannot hold yet. +* *Whole-file mapping only.* A file larger than 2 GB is refused instead of being mapped in + windows (https://github.com/SKaiNET-developers/SKaiNET/issues/1422[#1422] asks for the same in the IRPA writer). == Risks diff --git a/docs/modules/skeep/pages/003-unified-tensor-storage.adoc b/docs/modules/skeep/pages/003-unified-tensor-storage.adoc index 86a75a0c..09d7bc47 100644 --- a/docs/modules/skeep/pages/003-unified-tensor-storage.adoc +++ b/docs/modules/skeep/pages/003-unified-tensor-storage.adoc @@ -1,11 +1,64 @@ = SKEEP-003: Unifying the tensor storage model — one byte-owner, enforced ownership, coherent dtype/encoding :description: SKaiNET proposal to converge the TensorData and TensorStorage layers into one storage model with real ownership, a single view mechanism, and dtype/encoding coherence. -Status: Implemented (accepted 2026-08-22; M0/M1/M2 + the P7/P8 resolution shipped in 0.49.0) + +Status: Accepted (2026-08-22; M0/M1/M2 and the placement resolution shipped in 0.49.0; the removal of the old layer is open, see <>) + Audience: SKaiNET maintainers and contributors + Created: 2026-08-10 + Tracking issue: https://github.com/SKaiNET-developers/SKaiNET/issues/932[#932] (umbrella) → https://github.com/SKaiNET-developers/SKaiNET/issues/1001[#1001 M0] · https://github.com/SKaiNET-developers/SKaiNET/issues/1002[#1002 M1] · https://github.com/SKaiNET-developers/SKaiNET/issues/1003[#1003 M2] + -Implemented: the model as built is documented in xref:ROOT:explanation/memory-model.adoc[The memory model] and xref:ROOT:explanation/packed-weight-layout.adoc[Packed weight layout]; the proposal and milestone PRD that led there are in the pull requests and issues linked below (#932, #1001, #1002, #1003). +As built: the model is documented in xref:ROOT:explanation/memory-model.adoc[The memory model] and xref:ROOT:explanation/packed-weight-layout.adoc[Packed weight layout]; the proposal and milestone PRD that led there are in the pull requests and issues linked below (#932, #1001, #1002, #1003). + +[#implementation-status] +== Implementation status (audited 2026-11-11, at 0.64.0) + +The model is built and in use on every target: `Storage`, `TensorView`, `Format`, `Scope`, the +memory plan, the tracer, mapped staging. The old layer it was meant to replace is still in the +code, so this proposal is back at `Accepted` until the open rows below are closed. The umbrella +https://github.com/SKaiNET-developers/SKaiNET/issues/932[#932] lists the work. + +[cols="3,1,3",options="header"] +|=== +| Acceptance criterion | State | Where it stands + +| One abstraction owns bytes, ownership enforced +| Partly +| Enforcement is tested (`StorageTest`, `ScopeTest`, `TensorViewTest`, `JvmStorageTest`). `TensorStorage` and `BufferHandle` still exist beside `Storage`, and the streaming readers' `loadTensorStorage*` return the old type (https://github.com/SKaiNET-developers/SKaiNET/issues/1502[#1502], https://github.com/SKaiNET-developers/SKaiNET/issues/1507[#1507]). + +| One view mechanism +| Partly +| `OneViewMechanismTest` covers slice, transpose and unsqueeze on the new view. The public `slice()` family still returns the deprecated `SlicedTensorView` (https://github.com/SKaiNET-developers/SKaiNET/issues/1502[#1502]). + +| A packed tensor reports FP32 and its encoding; no dispatch site downcasts to a concrete `TensorData` class outside the data package +| Not met +| The first half holds (`FormatTest`). About 130 downcasts remain in the CPU, Accelerate and wasm ops, and matmul reaches `KernelKey` dispatch only as its last fallback (https://github.com/SKaiNET-developers/SKaiNET/issues/1501[#1501] for the guard, https://github.com/SKaiNET-developers/SKaiNET/issues/1507[#1507] for the migration). + +| No kernel regression against a baseline +| Partly +| The benchmarks exist; the recorded baseline was deleted with `docs/design/` in #1106 (https://github.com/SKaiNET-developers/SKaiNET/issues/1501[#1501]). + +| Flat direct memory under a forward scope on the JVM +| No test +| Slab reuse is tested (`ScopedOpOutputsTest`), direct memory only under the GC arena (`MemSegArenaLeakTest`) (https://github.com/SKaiNET-developers/SKaiNET/issues/1501[#1501]). +|=== + +Decisions 1, 2 (naming and package), 6, 9, 11, 12 and 13 are delivered; `LogicalDType` is +deprecated and its removal is https://github.com/SKaiNET-developers/SKaiNET/issues/1502[#1502]. The rest: + +[cols="1,4,3",options="header"] +|=== +| # | Open part | Tracked by + +| 1 | Façades: only `StorageFloatTensorData` sits on a `Storage`; the other `TensorData` classes own their bytes and wrap a view around them on each read | https://github.com/SKaiNET-developers/SKaiNET/issues/1507[#1507] +| 2 | `sk.ainet.lang.graph.Layout` is not deprecated; `BlockOrderLayout` implements it now | SKEEP-004, which owns the compile-side layout +| 3 | `get()` on a packed view decodes; the debug-mode warning on hot paths does not exist | https://github.com/SKaiNET-developers/SKaiNET/issues/1503[#1503] +| 4 | The `(head, tail)` pair and its gather fallback exist (`WindowedKV`); only the reference attention consumes them | https://github.com/SKaiNET-developers/SKaiNET/issues/1504[#1504], outside this SKEEP +| 5 | `BlockSpec.activation` drives the eager dispatcher; the StableHLO export does not read it | https://github.com/SKaiNET-developers/SKaiNET/issues/1505[#1505], outside this SKEEP +| 7 | `SuspendingRandomAccessSource` exists in `skainet-io-core`; there is no remote module | https://github.com/SKaiNET-developers/SKaiNET/issues/1506[#1506], outside this SKEEP +| 8 | Axis labels on `Shape` | https://github.com/SKaiNET-developers/SKaiNET/issues/1503[#1503] +| 10 | Activation ids exist when tracing, not in eager debug mode | https://github.com/SKaiNET-developers/SKaiNET/issues/1503[#1503] +|=== + +Compiled parity (P7 below) was closed as superseded: IREE validation lives in the IREE-tools and +conformity repositories (#1148), and this repository has no eager-versus-compiled parity test. == Decision (2026-08-22) diff --git a/docs/modules/skeep/pages/003a-placement-and-planning-resolution.adoc b/docs/modules/skeep/pages/003a-placement-and-planning-resolution.adoc index 082c289d..9ef9a4e4 100644 --- a/docs/modules/skeep/pages/003a-placement-and-planning-resolution.adoc +++ b/docs/modules/skeep/pages/003a-placement-and-planning-resolution.adoc @@ -98,9 +98,9 @@ The carry work landed as the #1178/#1179/#1180 arc (0.49.0): `TensorRef` carries encoding and block order; `TensorSpec` metadata accessors follow the untyped precedent; the structural `skainet.tensor_layouts` module attribute and `ExternalParameterRef.blockOrder` declare the facts to the consumer; and `LayoutAssignmentPass` runs on the production path via -`HloGenerator.generate(target = …)`, populating the pre-built `ResolvedComputeGraph` seams. Until -the conformity pipeline exists (#1148, the future IREE-validation SKEEP), the carriage tests are -the metadata's reader. +`HloGenerator.generate(target = …)`, populating the pre-built `ResolvedComputeGraph` seams. The +conformity pipeline that reads the metadata lives outside this repository (#1148 was closed as +superseded); here, the carriage tests are the metadata's reader. == Where everything landed @@ -113,7 +113,7 @@ the metadata's reader. | `memoryScope` wired into creation | #1145 | PR #1156 | Op outputs through the scope + offset-aware SIMD | #1146, #1173 | landed (PRs #1168/#1175) | Layout/placement carriage to StableHLO | #1147 (#1178/#1179/#1180) | landed (PRs #1182/#1183/#1184) -| `.vmfb` parity acceptance run | #1148 | open (follow-up) +| `.vmfb` parity acceptance run | #1148 | closed as superseded 2026-09-02: IREE validation lives in the IREE-tools and conformity repositories; no eager-versus-compiled parity test in this repository |=== The SKEEP-003 items "`StorageSpec`, `MemoryPlanner` and the annotations are deleted unless wired" diff --git a/docs/modules/skeep/pages/index.adoc b/docs/modules/skeep/pages/index.adoc index 1083211b..9c2f381d 100644 --- a/docs/modules/skeep/pages/index.adoc +++ b/docs/modules/skeep/pages/index.adoc @@ -116,12 +116,12 @@ Every proposal should include: | Off-heap tensor storage on Android | xref:skeep:003-unified-tensor-storage.adoc[SKEEP-003] -| Implemented +| Accepted | Unifying the tensor storage model | xref:skeep:003a-placement-and-planning-resolution.adoc[SKEEP-003a] -| Resolved -| Placement, eager lifetime and graph-level planning — the P7/P8 answers +| Implemented +| Addendum to SKEEP-003: placement, eager lifetime and graph-level planning | xref:skeep:004-virtual-tensor-layout.adoc[SKEEP-004] | Draft diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt index e6b57530..e4af9e76 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt @@ -8,9 +8,9 @@ import sk.ainet.lang.types.DType /** * Runtime descriptor for a tensor's backing memory. * - * [TensorStorage] is the main architectural type that replaces ad-hoc - * array passing between loaders, planners, and backends. It carries enough - * information to handle a tensor without inspecting its bytes: + * [TensorStorage] describes a tensor read from a file before it becomes a + * tensor: the streaming GGUF and SafeTensors readers return it. It carries + * enough information to handle a tensor without inspecting its bytes: * * - **What** the values mean: [logicalType] * - **How** they are stored: [encoding] @@ -18,9 +18,10 @@ import sk.ainet.lang.types.DType * - **Layout**: [shape], [byteOffset], [strides], [isContiguous] * - **Ownership**: via [buffer]'s [BufferHandle] subtype * - * Existing [sk.ainet.lang.tensor.data.TensorData] remains as a - * compatibility façade. New loaders, planners, and backends should target - * [TensorStorage] directly. + * This is the older of two storage descriptions. The model new code targets is + * [sk.ainet.lang.memory.Storage] (owns bytes) with + * [sk.ainet.lang.memory.TensorView] (interprets them); SKEEP-003 replaces this + * type with a view once the readers return one (#1507). */ public data class TensorStorage( val shape: Shape, From 84db7cc48b83082138abe56e4992a7034d0e5ea5 Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Wed, 11 Nov 2026 15:03:05 +0100 Subject: [PATCH 2/4] build: guards for SKEEP-003's acceptance criteria (#1501) checkTensorDataDowncasts counts downcasts to concrete TensorData classes outside the data package against an allowlist that has to match the code exactly: 162 sites in 21 files today. It runs in the stub-markers CI leg. ForwardScopeFlatMemoryTest runs 300 forward passes under a forward scope on the JVM and asserts that direct memory, RSS and the slab stay flat. reference/benchmark-baseline.adoc records the JMH numbers a dispatch or storage change is compared against; scripts/pr-gate.sh points at it. Co-Authored-By: Claude Fable 5.2 --- .github/workflows/build.yml | 7 +- CHANGELOG.md | 4 + .../kotlin/CheckTensorDataDowncastsTask.kt | 140 ++++++++++++++++++ .../src/main/kotlin/DocumentationPlugin.kt | 16 ++ config/tensor-data-downcast-allowlist.txt | 34 +++++ docs/modules/ROOT/nav.adoc | 1 + .../pages/reference/benchmark-baseline.adoc | 77 ++++++++++ scripts/pr-gate.sh | 2 +- .../tensor/ops/ForwardScopeFlatMemoryTest.kt | 98 ++++++++++++ 9 files changed, 375 insertions(+), 4 deletions(-) create mode 100644 build-logic/convention/src/main/kotlin/CheckTensorDataDowncastsTask.kt create mode 100644 config/tensor-data-downcast-allowlist.txt create mode 100644 docs/modules/ROOT/pages/reference/benchmark-baseline.adoc create mode 100644 skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8fec037e..29de43db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,10 +76,11 @@ jobs: # associates with dumps (#1171 merged a stale dump exactly that way; #1174 repaired it). - name: api-compatibility tasks: apiCheck - # Every NotImplementedError / TODO( in production code is either @NotImplemented - # (so it shows in the ops coverage matrix) or listed in config/stub-allowlist.txt. + # Both guards scan sources only, so they share one leg: stubs are either @NotImplemented + # or in config/stub-allowlist.txt, and downcasts to concrete TensorData classes outside + # the data package (SKEEP-003) are in config/tensor-data-downcast-allowlist.txt. - name: stub-markers - tasks: checkUnmarkedStubs + tasks: checkUnmarkedStubs checkTensorDataDowncasts permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 4652f488..25384573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- **Guards for SKEEP-003's acceptance criteria** (build, `skainet-backend-cpu`, docs, #1501): `./gradlew checkTensorDataDowncasts` counts the downcasts to concrete `TensorData` classes outside the data package (162 in 21 files today) against `config/tensor-data-downcast-allowlist.txt`, so a new one fails the build and the list can only shrink; it runs in the `stub-markers` CI leg. `ForwardScopeFlatMemoryTest` runs 300 forward passes under a forward scope on the JVM and asserts that direct memory, RSS and the slab stay flat. `reference/benchmark-baseline.adoc` records the JMH numbers for matmul, elementwise and reductions that a dispatch or storage change is compared against, replacing the baseline deleted in #1106. + ### Changed - **SKEEP-003 is back at `Accepted`, with an implementation-status table** (docs, #1500): an audit at 0.64.0 found the new storage model in use and the old layer (`TensorStorage`, `BufferHandle`, `TensorData` downcasts in the ops) still in place, so three of the five acceptance criteria are open. The SKEEP now says which, and what tracks each (#932). SKEEP-003a no longer lists #1148 as open, SKEEP-002's status records the Q4_K / Q6_K mapped staging of #1190 and its device measurement, `memory-model.adoc` drops a limit that #1190 removed, and the `TensorStorage` KDoc stops telling new code to target it. diff --git a/build-logic/convention/src/main/kotlin/CheckTensorDataDowncastsTask.kt b/build-logic/convention/src/main/kotlin/CheckTensorDataDowncastsTask.kt new file mode 100644 index 00000000..04179ab2 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/CheckTensorDataDowncastsTask.kt @@ -0,0 +1,140 @@ +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.* +import java.io.File + +/** + * Guards a SKEEP-003 acceptance criterion: no dispatch site downcasts to a concrete `TensorData` + * class outside the data package itself. Kernels are meant to be chosen from a view's + * `Format(dtype, encoding)`, not from the Kotlin class that happens to hold the bytes. + * + * A downcast is an `is` / `!is` / `as` / `as?` whose target is a class named `…TensorData` + * (the generic base `TensorData` itself does not count) or one of the storage markers in + * [markerTypes]. Production code only; the packages that define the classes are exempt. + * + * The sites that exist today are listed in [allowlistFile] as `path|count|reason`, where `count` + * is the exact number of downcasts in the file. A new downcast fails the build until the entry is + * raised, which a reviewer sees; removing one fails until the entry is lowered, so the list can + * only describe the code as it is. The goal is an empty list. + */ +@CacheableTask +abstract class CheckTensorDataDowncastsTask : DefaultTask() { + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val sources: ConfigurableFileCollection + + /** Repository root that report and allowlist paths are relative to. */ + @get:Internal + abstract val rootDirectory: DirectoryProperty + + @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val allowlistFile: RegularFileProperty + + @get:OutputFile + abstract val reportFile: RegularFileProperty + + private val cast = Regex("""(?:!is|\bis|\bas\?|\bas)\s+(?:[A-Za-z_][\w]*\.)*([A-Z]\w*)""") + private val mainSourceDir = Regex("""/src/[A-Za-z0-9]*[Mm]ain/""") + + /** Marker interfaces that stand for a concrete storage class in a dispatch `when`. */ + private val markerTypes = setOf( + "PackedBlockStorage", + "RowDequantSource", + "MemorySegmentBackedData", + "Q4MemorySegmentMarker", + "Q8MemorySegmentMarker", + ) + + /** Packages that define the classes and may inspect them. */ + private val exemptPackages = listOf( + "/sk/ainet/lang/tensor/data/", + "/sk/ainet/lang/tensor/storage/", + "/sk/ainet/lang/memory/", + ) + + @TaskAction + fun check() { + val root = rootDirectory.get().asFile + val allowlist = parseAllowlist(allowlistFile.get().asFile) + val found = mutableMapOf>() + + sources.files + .filter { it.extension == "kt" } + .forEach { file -> + val rel = file.relativeTo(root).invariantSeparatorsPath + if (!mainSourceDir.containsMatchIn("/$rel")) return@forEach + if (exemptPackages.any { it in "/$rel" }) return@forEach + file.readLines().forEachIndexed { index, line -> + val trimmed = line.trimStart() + if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) return@forEachIndexed + val code = stripStrings(line).substringBefore("//") + val hits = cast.findAll(code).count { isConcreteStorageType(it.groupValues[1]) } + repeat(hits) { found.getOrPut(rel) { mutableListOf() } += index + 1 } + } + } + + val problems = mutableListOf() + found.toSortedMap().forEach { (path, hits) -> + val expected = allowlist[path] + when { + expected == null -> hits.forEach { problems += "$path:$it — downcast to a concrete TensorData class" } + expected != hits.size -> problems += "$path — ${hits.size} downcasts, allowlist expects $expected (lines ${hits.joinToString()})" + } + } + allowlist.keys.filter { it !in found }.sorted().forEach { path -> + problems += "$path — allowlisted but has no downcasts any more; remove the entry" + } + + reportFile.get().asFile.apply { parentFile.mkdirs() }.writeText( + found.toSortedMap().entries.joinToString("\n", postfix = "\n") { (path, hits) -> "$path|${hits.size}|${hits.joinToString(",")}" }, + ) + + if (problems.isNotEmpty()) { + throw GradleException( + buildString { + appendLine("Downcasts to concrete TensorData classes outside the data package (SKEEP-003):") + problems.forEach { appendLine(" $it") } + appendLine() + appendLine("Choose the kernel from the view's Format(dtype, encoding) instead of the class. If the") + appendLine("count went down, lower it in ${allowlistFile.get().asFile.relativeTo(root).invariantSeparatorsPath} (path|count|reason).") + }, + ) + } + logger.lifecycle("✅ TensorData downcasts match the allowlist (${found.values.sumOf { it.size }} in ${found.size} files)") + } + + private fun isConcreteStorageType(name: String): Boolean = + name in markerTypes || (name.endsWith("TensorData") && name != "TensorData") + + /** Blank out the contents of `"…"` literals on one line (escape-aware). */ + private fun stripStrings(line: String): String = buildString { + var inString = false + var i = 0 + while (i < line.length) { + val ch = line[i] + when { + inString && ch == '\\' -> i++ + ch == '"' -> { inString = !inString; append(ch) } + !inString -> append(ch) + } + i++ + } + } + + private fun parseAllowlist(file: File): Map = + file.readLines() + .map { it.trim() } + .filter { it.isNotEmpty() && !it.startsWith("#") } + .associate { line -> + val parts = line.split("|").map { it.trim() } + if (parts.size < 3 || parts[2].isEmpty()) { + throw GradleException("Malformed allowlist line (want path|count|reason): $line") + } + parts[0] to (parts[1].toIntOrNull() ?: throw GradleException("Bad count in allowlist line: $line")) + } +} diff --git a/build-logic/convention/src/main/kotlin/DocumentationPlugin.kt b/build-logic/convention/src/main/kotlin/DocumentationPlugin.kt index b2fbd42f..e7bef0eb 100644 --- a/build-logic/convention/src/main/kotlin/DocumentationPlugin.kt +++ b/build-logic/convention/src/main/kotlin/DocumentationPlugin.kt @@ -57,6 +57,22 @@ class DocumentationPlugin : Plugin { } }) + // Guard (SKEEP-003): ops choose kernels from a view's Format, not by downcasting to a + // concrete TensorData class. Today's sites are allowlisted; the list may only shrink. + project.tasks.register("checkTensorDataDowncasts", CheckTensorDataDowncastsTask::class.java, object : Action { + override fun execute(task: CheckTensorDataDowncastsTask) { + task.group = "verification" + task.description = "Fail on downcasts to concrete TensorData classes that are not in the allowlist" + val root = project.rootProject.layout.projectDirectory + project.rootProject.allprojects.forEach { p -> + task.sources.from(p.fileTree("src") { include("**/*.kt") }) + } + task.rootDirectory.set(root) + task.allowlistFile.set(root.file("config/tensor-data-downcast-allowlist.txt")) + task.reportFile.set(project.layout.buildDirectory.file("reports/downcasts/tensor-data-downcasts.txt")) + } + }) + // Register schema validation task in plugin (migrated from skainet-lang-export-ops) val validateTaskProvider = project.tasks.register("validateOperatorSchema", SchemaValidationTask::class.java, object : Action { override fun execute(task: SchemaValidationTask) { diff --git a/config/tensor-data-downcast-allowlist.txt b/config/tensor-data-downcast-allowlist.txt new file mode 100644 index 00000000..cfc51ac5 --- /dev/null +++ b/config/tensor-data-downcast-allowlist.txt @@ -0,0 +1,34 @@ +# Downcasts to concrete TensorData classes outside the data package. Checked by +# `./gradlew checkTensorDataDowncasts`. +# +# SKEEP-003 acceptance criterion: no dispatch site downcasts to a concrete `TensorData` class +# outside the storage/data package itself; a kernel is chosen from the view's +# Format(dtype, encoding). These are the sites that still do. The goal is an empty file. +# +# Format: path|count|issue or reason +# count = exact number of `is` / `!is` / `as` / `as?` whose target is a `…TensorData` class +# or a storage marker (PackedBlockStorage, RowDequantSource, MemorySegmentBackedData, +# Q4/Q8MemorySegmentMarker). Removing a downcast means lowering the count (or deleting the +# line). Do not raise a count: dispatch on the format instead. + +skainet-apps/skainet-grayscale-cli/src/main/kotlin/sk/ainet/apps/grayscale/HloCompiler.kt|1|#1507 +skainet-apps/skainet-grayscale-cli/src/main/kotlin/sk/ainet/apps/grayscale/TensorConversionPipeline.kt|1|#1507 +skainet-apps/skainet-tensor-tools/src/main/kotlin/sk/ainet/apps/tool/quantize/QuantizeCli.kt|3|#1507 +skainet-backends/skainet-backend-cpu/src/appleMain/kotlin/sk/ainet/exec/tensor/ops/AccelerateCpuOps.kt|18|#1507 +skainet-backends/skainet-backend-cpu/src/commonMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOps.kt|51|#1507 +skainet-backends/skainet-backend-cpu/src/jvmMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOpsJvm.kt|48|#1507 +skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmCpuOps.kt|10|#1507 +skainet-compile/skainet-compile-dag/src/commonMain/kotlin/sk/ainet/lang/trace/TraceToGraphBuilder.kt|3|#1507 +skainet-compile/skainet-compile-hlo/src/commonMain/kotlin/sk/ainet/compile/hlo/converters/BlockQuantMatmulConverter.kt|1|#1507 +skainet-compile/skainet-compile-hlo/src/commonMain/kotlin/sk/ainet/compile/hlo/converters/ConstantOperationsConverter.kt|1|#1507 +skainet-compile/skainet-compile-opt/src/commonMain/kotlin/sk/ainet/compile/opt/passes/BlockQuantMatmulFusionPass.kt|1|#1507 +skainet-io/skainet-io-gguf/src/commonMain/kotlin/sk/ainet/io/gguf/StreamingGgufParametersLoader.kt|2|#1507 +skainet-io/skainet-io-gguf/src/commonMain/kotlin/sk/ainet/io/gguf/export/GGUFWriter.kt|2|#1507 +skainet-io/skainet-io-safetensors/src/commonMain/kotlin/sk/ainet/io/safetensors/SafeTensorsMaterializer.kt|1|#1507 +skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/IndexTensor.kt|1|#1507 +skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/ops/QuantizedMatmul.kt|9|#1507 +skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/ops/TensorSpecEncoding.kt|1|#1507 +skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/ops/TernaryMatmul.kt|3|#1507 +skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/trace/TraceSession.kt|1|#1507 +skainet-lang/skainet-lang-core/src/jvmMain/kotlin/sk/ainet/lang/tensor/StorageBenchmarks.kt|3|#1507 +skainet-test/skainet-test-groundtruth/src/commonMain/kotlin/sk/ainet/test/groundtruth/TensorAssertions.kt|1|#1507 diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index aabdb2be..7b22053b 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -30,6 +30,7 @@ ** xref:reference/operators/generated/index.adoc[Operator reference] ** xref:reference/ops-status-matrix.adoc[Operator coverage matrix] ** xref:reference/kernel-support-matrix.adoc[Kernel × platform support] +** xref:reference/benchmark-baseline.adoc[Benchmark baseline] ** xref:reference/api.adoc[API reference (Dokka)] * Explanation ** xref:explanation/skainet-for-ai.adoc[SKaiNET for AI/ML] diff --git a/docs/modules/ROOT/pages/reference/benchmark-baseline.adoc b/docs/modules/ROOT/pages/reference/benchmark-baseline.adoc new file mode 100644 index 00000000..c46aba3d --- /dev/null +++ b/docs/modules/ROOT/pages/reference/benchmark-baseline.adoc @@ -0,0 +1,77 @@ += Benchmark baseline +:description: Reference JMH numbers for the matmul, elementwise and reduction hot paths, to compare against when a change touches dispatch or storage. + +These are the numbers a change to kernel dispatch or tensor storage is compared against. +xref:skeep:003-unified-tensor-storage.adoc[SKEEP-003] sets the budget: matmul within noise, +elementwise at most 3 % slower. + +== How to compare + +. Run the suite on the commit before your change and on the commit with it, on the same machine, + with nothing else building: ++ +[source,bash] +---- +./gradlew :skainet-backends:benchmarks:jvm-cpu-jmh:jmh \ + -PjmhIncludes='sk.ainet.bench.(MatmulBench|KernelMatmulBench|QuantizedMatmulBench|ElementwiseAdd1MBench|Reductions1MBench).*' +---- ++ +The results are in `skainet-backends/benchmarks/jvm-cpu-jmh/build/results/jmh/results.txt`. +`scripts/pr-gate.sh --bench` runs the whole suite. +. Put both tables in the pull request. The table below tells you what the numbers looked like on + the reference machine; numbers from another machine are only comparable with each other. +. When a change moves a number on purpose, record a new baseline here in the same pull request. + +Not part of this baseline: `:skainet-lang:skainet-lang-core:jvmBenchmark` (`StorageBenchmarks`, +`TensorBenchmarks`, `TurboQuantBenchmarks`). With the module's default iteration settings a full +run takes hours; run it filtered per class when a change touches the storage hot paths. + +== Baseline of 2026-11-11 + +* Commit: `edadc84b` (`develop`, 0.64.0) +* Machine: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz, 12 threads, Linux 6.8.0-60, OpenJDK 21.0.13 +* Settings: JMH, fork 1, 3 warm-up and 5 measurement iterations, + `--enable-preview --add-modules jdk.incubator.vector` + +[cols="3,3,4,1,2,2,1",options="header"] +|=== +| Benchmark | Class | Parameters | Mode | Score | ± | Unit + +| `add_1M_fp32` | ElementwiseAdd1MBench | vectorEnabled=true | thrpt | 2.061 | 0.023 | ops/ms +| `add_1M_fp32` | ElementwiseAdd1MBench | vectorEnabled=false | thrpt | 1.195 | 0.019 | ops/ms +| `mean_1M_fp32` | Reductions1MBench | vectorEnabled=true | thrpt | 1.027 | 0.004 | ops/ms +| `mean_1M_fp32` | Reductions1MBench | vectorEnabled=false | thrpt | 1.027 | 0.003 | ops/ms +| `sum_1M_fp32` | Reductions1MBench | vectorEnabled=true | thrpt | 1.027 | 0.006 | ops/ms +| `sum_1M_fp32` | Reductions1MBench | vectorEnabled=false | thrpt | 1.030 | 0.005 | ops/ms +| `matmul_fp32_square` | KernelMatmulBench | provider=scalar, size=256 | avgt | 22.468 | 0.182 | ms/op +| `matmul_fp32_square` | KernelMatmulBench | provider=scalar, size=512 | avgt | 292.669 | 0.428 | ms/op +| `matmul_fp32_square` | KernelMatmulBench | provider=scalar, size=1024 | avgt | 1869.206 | 13.642 | ms/op +| `matmul_fp32_square` | KernelMatmulBench | provider=panama, size=256 | avgt | 1.257 | 0.014 | ms/op +| `matmul_fp32_square` | KernelMatmulBench | provider=panama, size=512 | avgt | 9.881 | 0.144 | ms/op +| `matmul_fp32_square` | KernelMatmulBench | provider=panama, size=1024 | avgt | 87.219 | 1.669 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=true, size=256, vectorEnabled=true | avgt | 8.548 | 0.072 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=true, size=256, vectorEnabled=false | avgt | 7.187 | 0.129 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=true, size=512, vectorEnabled=true | avgt | 67.587 | 0.494 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=true, size=512, vectorEnabled=false | avgt | 62.912 | 0.486 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=true, size=1024, vectorEnabled=true | avgt | 544.217 | 6.110 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=true, size=1024, vectorEnabled=false | avgt | 480.891 | 2.660 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=false, size=256, vectorEnabled=true | avgt | 1.269 | 0.015 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=false, size=256, vectorEnabled=false | avgt | 7.080 | 0.047 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=false, size=512, vectorEnabled=true | avgt | 10.382 | 0.181 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=false, size=512, vectorEnabled=false | avgt | 59.932 | 1.181 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=false, size=1024, vectorEnabled=true | avgt | 87.704 | 1.994 | ms/op +| `matmul_fp32_square` | MatmulBench | blasEnabled=false, size=1024, vectorEnabled=false | avgt | 480.785 | 6.122 | ms/op +| `matmul_q4k_panama` | QuantizedMatmulBench | shape=1024-1024 | avgt | 0.110 | 0.001 | ms/op +| `matmul_q4k_panama` | QuantizedMatmulBench | shape=4096-1024 | avgt | 0.365 | 0.002 | ms/op +| `matmul_q4k_panama` | QuantizedMatmulBench | shape=4096-4096 | avgt | 1.388 | 0.020 | ms/op +|=== + +`thrpt` is throughput, higher is better; `avgt` is the average time per call, lower is better. + +One thing the table shows that is not a storage matter: on this Linux machine `MatmulBench` with +`blasEnabled=true` is about seven times slower than the Panama kernel at every size +(8.5 ms against 1.3 ms at 256). BLAS routing is off by default outside macOS. + +The baseline recorded on 2026-08-22, before the storage migration, was removed with +`docs/design/` (#1106); it is in the history at commit `3c0ee71d`. It was taken on the same +machine with OpenJDK 25, so the two are not directly comparable. diff --git a/scripts/pr-gate.sh b/scripts/pr-gate.sh index 1ddd5d0b..2dc8463e 100755 --- a/scripts/pr-gate.sh +++ b/scripts/pr-gate.sh @@ -68,7 +68,7 @@ step "Android host tests" "${GRADLE[@]}" testAndroidHostTest if [[ "$mode" == "--bench" ]]; then - step "benchmarks (compare against the committed baseline before/after)" + step "benchmarks (compare with docs/modules/ROOT/pages/reference/benchmark-baseline.adoc)" "${GRADLE[@]}" :skainet-lang:skainet-lang-core:jvmBenchmark "${GRADLE[@]}" :skainet-backends:benchmarks:jvm-cpu-jmh:jmh fi diff --git a/skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt b/skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt new file mode 100644 index 00000000..e5d6a704 --- /dev/null +++ b/skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt @@ -0,0 +1,98 @@ +package sk.ainet.exec.tensor.ops + +import java.lang.management.BufferPoolMXBean +import java.lang.management.ManagementFactory +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.context.ExecutionContext +import sk.ainet.context.forwardScope +import sk.ainet.lang.memory.MemoryProbe +import sk.ainet.lang.tensor.Shape +import sk.ainet.lang.tensor.Tensor +import sk.ainet.lang.tensor.data.StorageFloatTensorData +import sk.ainet.lang.types.FP32 + +/** + * SKEEP-003 acceptance criterion (#1501): repeated forward passes under a forward scope keep + * memory flat on the JVM. This is the failure that forced the retreat to the garbage collector: + * a shared arena grew with every op output, and per-call arenas leaked per matmul. + * + * The loop is a decode step in miniature — weights outside the scope, activations inside it, + * `reset()` at the step boundary — at a size where a leak is unmistakable: one step produces + * about 3 MB of activations, so 300 steps would be about 900 MB if anything accumulated. + */ +class ForwardScopeFlatMemoryTest { + + private val n = 512 + private val steps = 300 + private val warmup = 20 + + private val directPool: BufferPoolMXBean = + ManagementFactory.getPlatformMXBeans(BufferPoolMXBean::class.java).first { it.name == "direct" } + + private fun mb(bytes: Long): String = "%.1f MB".format(bytes / (1024.0 * 1024.0)) + + private fun step(x: Tensor, w: Tensor, b: Tensor): Tensor { + val ops = x.ops + return ops.relu(ops.add(ops.matmul(x, w), b)) + } + + private fun weights(ctx: ExecutionContext): Pair, Tensor> { + val w = ctx.fromFloatArray(Shape(n, n), FP32::class, FloatArray(n * n) { ((it * 31) % 17 - 8) * 0.01f }) + val b = ctx.fromFloatArray(Shape(n), FP32::class, FloatArray(n) { (it % 5 - 2) * 0.1f }) + return w to b + } + + @Test + fun repeatedForwardPassesKeepDirectMemoryAndTheSlabFlat() { + val base = DirectCpuExecutionContext() + val (w, b) = weights(base) + val input = FloatArray(n * n) { ((it * 13) % 11 - 5) * 0.05f } + val stepBytes = 3L * n * n * Float.SIZE_BYTES // input, matmul and add outputs; relu reuses or adds one more + + base.forwardScope(slabFloats = 8 * n * n) { scoped, scope -> + var directAfterWarmup = 0L + var rssAfterWarmup: Long? = null + var peakAfterWarmup = 0 + var directPeak = 0L + + repeat(steps) { stepNo -> + val x = scoped.fromFloatArray(Shape(n, n), FP32::class, input) + val y = step(x, w, b) + assertTrue(y.data is StorageFloatTensorData<*>, "step $stepNo: activations must come from the slab") + if (stepNo == 0) assertTrue(y.data.copyToFloatArray().any { it > 0f }, "the step must compute something") + assertEquals(0L, scope.overflowBytes, "step $stepNo: nothing may spill past the slab") + scope.reset() + + val direct = directPool.memoryUsed + if (stepNo == warmup) { + directAfterWarmup = direct + rssAfterWarmup = MemoryProbe.rssBytes() + peakAfterWarmup = scope.peakFloats + } + if (stepNo > warmup) { + directPeak = maxOf(directPeak, direct) + assertEquals(peakAfterWarmup, scope.peakFloats, "step $stepNo: the slab must not grow in steady state") + } + } + + val directGrowth = directPeak - directAfterWarmup + val rssGrowth = rssAfterWarmup?.let { before -> MemoryProbe.rssBytes()?.minus(before) } + println( + "[forward-scope] ${steps - warmup} steady steps of ${mb(stepBytes)}: direct pool ${mb(directAfterWarmup)} -> peak ${mb(directPeak)}, " + + "rss growth ${rssGrowth?.let(::mb) ?: "n/a"}, slab peak ${scope.peakFloats} floats", + ) + + // Flat: less than one step's worth of direct memory appears over 280 steps. + assertTrue(directGrowth < stepBytes, "direct memory grew by ${mb(directGrowth)} over ${steps - warmup} steps") + // A leak of every activation would be about 840 MB here; allow a tenth of that for the + // JIT, the collector and allocator noise in a shared test JVM. + if (rssGrowth != null) { + val leakIfEverythingStayed = stepBytes * (steps - warmup) + assertTrue(rssGrowth < leakIfEverythingStayed / 10, "RSS grew by ${mb(rssGrowth)} over ${steps - warmup} steps") + } + } + } +} From 651e0353f87dac77a6ead27ccac479a9085294a8 Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Wed, 11 Nov 2026 15:10:02 +0100 Subject: [PATCH 3/4] refactor(lang-core): remove LogicalDType (#1502) SKEEP-003 decision 13 merged the storage-side dtype enum into DType and deprecated it. TensorStorage and StorageMemoryReport now hold a DType directly instead of bridging to it from the enum, and the LogicalDType overloads of the storage factories are gone. No production code outside the storage package used the enum. Co-Authored-By: Claude Fable 5.2 --- CHANGELOG.md | 4 + .../ainet/io/gguf/StorageIntegrationTest.kt | 6 +- .../StorageAwareSafeTensorsLoaderTest.kt | 6 +- .../api/jvm/skainet-lang-core.api | 72 ++---------- .../ainet/lang/tensor/storage/LogicalDType.kt | 108 ------------------ .../lang/tensor/storage/LogicalDTypeBridge.kt | 27 ----- .../lang/tensor/storage/PackedBlockStorage.kt | 15 +-- .../tensor/storage/StorageMemoryReport.kt | 8 +- .../lang/tensor/storage/TensorEncoding.kt | 2 +- .../lang/tensor/storage/TensorStorage.kt | 40 ++----- .../tensor/storage/TensorStorageFactory.kt | 53 ++------- .../kotlin/sk/ainet/lang/types/DType.kt | 4 +- .../tensor/storage/AcceptanceCriteriaTest.kt | 23 ++-- .../tensor/storage/BufferHandleFactoryTest.kt | 21 ++-- .../storage/CompressedKvAttentionTest.kt | 7 +- .../tensor/storage/ExplicitCopyApiTest.kt | 6 +- .../lang/tensor/storage/KvCacheStoreTest.kt | 5 +- .../tensor/storage/LogicalDTypeBridgeTest.kt | 106 ----------------- .../lang/tensor/storage/MemoryTrackerTest.kt | 12 +- .../storage/NarrowFloatStorageDecodeTest.kt | 10 +- .../storage/NonContiguousStorageTest.kt | 21 ++-- .../tensor/storage/PackedBlockStorageTest.kt | 5 +- .../tensor/storage/StorageToTensorDataTest.kt | 4 +- .../storage/TensorStorageContractTest.kt | 63 ++++------ .../lang/tensor/storage/TransferOpsTest.kt | 19 ++- .../sk/ainet/lang/types/DTypeWitnessTest.kt | 14 +-- 26 files changed, 128 insertions(+), 533 deletions(-) delete mode 100644 skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDType.kt delete mode 100644 skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridge.kt delete mode 100644 skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridgeTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 25384573..1da5a404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - **SKEEP-003 is back at `Accepted`, with an implementation-status table** (docs, #1500): an audit at 0.64.0 found the new storage model in use and the old layer (`TensorStorage`, `BufferHandle`, `TensorData` downcasts in the ops) still in place, so three of the five acceptance criteria are open. The SKEEP now says which, and what tracks each (#932). SKEEP-003a no longer lists #1148 as open, SKEEP-002's status records the Q4_K / Q6_K mapped staging of #1190 and its device measurement, `memory-model.adoc` drops a limit that #1190 removed, and the `TensorStorage` KDoc stops telling new code to target it. +### Removed + +- **`LogicalDType`** (`skainet-lang-core`, SKEEP-003 decision 13, #1502): the storage-side dtype enum, deprecated since 0.49.0, is removed together with `DType.toLogicalDType()`, `LogicalDType.fromDType` / `toDType`, the `logicalType` properties and the `LogicalDType` overloads of `TensorStorageFactory.fromRawBytes` / `fromRawBytesOwned` / `fileBacked` and `PackedBlockStorage.toTensorStorage`. `TensorStorage` and `StorageMemoryReport` hold a `DType` as their constructor property `dtype`; replace `logicalType = LogicalDType.FLOAT32` with `dtype = FP32`. `StorageMemoryReport.toString()` prints the dtype's name (`Float32`) where it printed the enum constant. + ## [0.64.0] - 2026-11-11 Headline: **every differentiable op has a backward rule, and mixed precision is finished: it trains, exports without diff --git a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StorageIntegrationTest.kt b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StorageIntegrationTest.kt index 95528655..5a354893 100644 --- a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StorageIntegrationTest.kt +++ b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StorageIntegrationTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.io.gguf import org.junit.Test @@ -109,7 +107,7 @@ class StorageIntegrationTest { // F32 tensor val f32Storage = reader.loadTensorStorage("weight_f32") - assertEquals(LogicalDType.FLOAT32, f32Storage.logicalType) + assertEquals(FP32, f32Storage.dtype) assertSame(FP32, f32Storage.dtype) assertEquals(TensorEncoding.Dense(4), f32Storage.encoding) assertEquals(Ownership.BORROWED, f32Storage.ownership) @@ -119,7 +117,7 @@ class StorageIntegrationTest { // Q8_0 tensor val q80Storage = reader.loadTensorStorage("weight_q80") - assertEquals(LogicalDType.FLOAT32, q80Storage.logicalType) // packed weights are logically FP32 + assertEquals(FP32, q80Storage.dtype) // packed weights are logically FP32 assertSame(FP32, q80Storage.dtype) assertEquals(TensorEncoding.Q8_0, q80Storage.encoding) assertEquals(Ownership.BORROWED, q80Storage.ownership) diff --git a/skainet-io/skainet-io-safetensors/src/commonTest/kotlin/sk/ainet/io/safetensors/StorageAwareSafeTensorsLoaderTest.kt b/skainet-io/skainet-io-safetensors/src/commonTest/kotlin/sk/ainet/io/safetensors/StorageAwareSafeTensorsLoaderTest.kt index 6e66fd7a..936a9544 100644 --- a/skainet-io/skainet-io-safetensors/src/commonTest/kotlin/sk/ainet/io/safetensors/StorageAwareSafeTensorsLoaderTest.kt +++ b/skainet-io/skainet-io-safetensors/src/commonTest/kotlin/sk/ainet/io/safetensors/StorageAwareSafeTensorsLoaderTest.kt @@ -1,9 +1,6 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.io.safetensors import sk.ainet.io.RandomAccessSource -import sk.ainet.lang.tensor.storage.LogicalDType import sk.ainet.lang.tensor.storage.MemoryDomain import sk.ainet.lang.tensor.storage.Ownership import kotlin.test.Test @@ -11,6 +8,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertFailsWith import kotlin.test.assertTrue +import sk.ainet.lang.types.FP32 /** * Tests for [StorageAwareSafeTensorsLoader]. @@ -105,7 +103,7 @@ class StorageAwareSafeTensorsLoaderTest { assertTrue(tensors.containsKey("weight")) val storage = tensors["weight"]!! - assertEquals(LogicalDType.FLOAT32, storage.logicalType) + assertEquals(FP32, storage.dtype) assertEquals(Ownership.BORROWED, storage.ownership) assertFalse(storage.isFileBacked) assertEquals(6L, storage.elementCount) // 2 * 3 diff --git a/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api b/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api index b305d8ca..ce1e3cbf 100644 --- a/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api +++ b/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api @@ -4885,7 +4885,6 @@ public final class sk/ainet/lang/tensor/data/BitNetB158TensorData : sk/ainet/lan public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -4919,7 +4918,6 @@ public final class sk/ainet/lang/tensor/data/BitNetPlanesTensorData : sk/ainet/l public fun set ([IF)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -4949,7 +4947,6 @@ public final class sk/ainet/lang/tensor/data/BufferPackedTensorData : sk/ainet/l public fun set ([IF)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5279,7 +5276,6 @@ public final class sk/ainet/lang/tensor/data/Q4_0BlockTensorData : sk/ainet/lang public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5345,7 +5341,6 @@ public final class sk/ainet/lang/tensor/data/Q4_KBlockTensorData : sk/ainet/lang public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5407,7 +5402,6 @@ public final class sk/ainet/lang/tensor/data/Q5_0BlockTensorData : sk/ainet/lang public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5456,7 +5450,6 @@ public final class sk/ainet/lang/tensor/data/Q5_1BlockTensorData : sk/ainet/lang public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5510,7 +5503,6 @@ public final class sk/ainet/lang/tensor/data/Q5_KBlockTensorData : sk/ainet/lang public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5579,7 +5571,6 @@ public final class sk/ainet/lang/tensor/data/Q6_KBlockTensorData : sk/ainet/lang public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5671,7 +5662,6 @@ public final class sk/ainet/lang/tensor/data/Q8_0BlockTensorData : sk/ainet/lang public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -5834,7 +5824,6 @@ public final class sk/ainet/lang/tensor/data/Ternary2BitTensorData : sk/ainet/la public fun set ([IB)V public synthetic fun set ([ILjava/lang/Object;)V public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -7433,10 +7422,6 @@ public final class sk/ainet/lang/tensor/storage/CopySourceStat { public fun toString ()Ljava/lang/String; } -public final class sk/ainet/lang/tensor/storage/DTypeBridge { - public static final fun toLogicalDType (Lsk/ainet/lang/types/DType;)Lsk/ainet/lang/tensor/storage/LogicalDType; -} - public final class sk/ainet/lang/tensor/storage/DefaultBufferResolver : sk/ainet/lang/tensor/storage/BufferResolver { public fun ()V public fun (Lkotlin/jvm/functions/Function1;)V @@ -7639,36 +7624,6 @@ public final class sk/ainet/lang/tensor/storage/KvCacheStore$DefaultImpls { public static synthetic fun valueWindow$default (Lsk/ainet/lang/tensor/storage/KvCacheStore;IIIILjava/lang/Object;)Lsk/ainet/lang/memory/WindowedKV; } -public final class sk/ainet/lang/tensor/storage/LogicalDType : java/lang/Enum { - public static final field BFLOAT16 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field Companion Lsk/ainet/lang/tensor/storage/LogicalDType$Companion; - public static final field FLOAT16 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field FLOAT32 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field FLOAT64 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field INT16 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field INT32 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field INT4 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field INT64 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field INT8 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field TERNARY Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field UINT16 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field UINT32 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field UINT64 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static final field UINT8 Lsk/ainet/lang/tensor/storage/LogicalDType; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getSizeInBits ()I - public final fun getSizeInBytes ()I - public final fun isFloatingPoint ()Z - public final fun isSigned ()Z - public final fun toDType ()Lsk/ainet/lang/types/DType; - public static fun valueOf (Ljava/lang/String;)Lsk/ainet/lang/tensor/storage/LogicalDType; - public static fun values ()[Lsk/ainet/lang/tensor/storage/LogicalDType; -} - -public final class sk/ainet/lang/tensor/storage/LogicalDType$Companion { - public final fun fromDType (Lsk/ainet/lang/types/DType;)Lsk/ainet/lang/tensor/storage/LogicalDType; -} - public final class sk/ainet/lang/tensor/storage/MemoryDomain : java/lang/Enum { public static final field DEVICE_LOCAL Lsk/ainet/lang/tensor/storage/MemoryDomain; public static final field HOST_HEAP Lsk/ainet/lang/tensor/storage/MemoryDomain; @@ -7714,9 +7669,7 @@ public abstract interface class sk/ainet/lang/tensor/storage/PackedBlockStorage public fun getPhysicalBytes ()J public abstract fun getShape ()Lsk/ainet/lang/tensor/Shape; public fun toFloatArray ()[F - public fun toTensorStorage (Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public fun toTensorStorage (Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; - public static synthetic fun toTensorStorage$default (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; public static synthetic fun toTensorStorage$default (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -7728,9 +7681,7 @@ public final class sk/ainet/lang/tensor/storage/PackedBlockStorage$DefaultImpls public static fun getPackedView (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;)Lsk/ainet/lang/memory/TensorView; public static fun getPhysicalBytes (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;)J public static fun toFloatArray (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;)[F - public static fun toTensorStorage (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public static fun toTensorStorage (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; - public static synthetic fun toTensorStorage$default (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; public static synthetic fun toTensorStorage$default (Lsk/ainet/lang/tensor/storage/PackedBlockStorage;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; } @@ -7769,10 +7720,10 @@ public final class sk/ainet/lang/tensor/storage/Requirement : java/lang/Enum { } public final class sk/ainet/lang/tensor/storage/StorageMemoryReport { - public fun (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/Ownership;Lsk/ainet/lang/tensor/storage/Placement;JJZZZ)V + public fun (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/Ownership;Lsk/ainet/lang/tensor/storage/Placement;JJZZZ)V public final fun component1 ()Lsk/ainet/lang/tensor/Shape; public final fun component10 ()Z - public final fun component2 ()Lsk/ainet/lang/tensor/storage/LogicalDType; + public final fun component2 ()Lsk/ainet/lang/types/DType; public final fun component3 ()Lsk/ainet/lang/tensor/storage/TensorEncoding; public final fun component4 ()Lsk/ainet/lang/tensor/storage/Ownership; public final fun component5 ()Lsk/ainet/lang/tensor/storage/Placement; @@ -7780,14 +7731,13 @@ public final class sk/ainet/lang/tensor/storage/StorageMemoryReport { public final fun component7 ()J public final fun component8 ()Z public final fun component9 ()Z - public final fun copy (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/Ownership;Lsk/ainet/lang/tensor/storage/Placement;JJZZZ)Lsk/ainet/lang/tensor/storage/StorageMemoryReport; - public static synthetic fun copy$default (Lsk/ainet/lang/tensor/storage/StorageMemoryReport;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/Ownership;Lsk/ainet/lang/tensor/storage/Placement;JJZZZILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/StorageMemoryReport; + public final fun copy (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/Ownership;Lsk/ainet/lang/tensor/storage/Placement;JJZZZ)Lsk/ainet/lang/tensor/storage/StorageMemoryReport; + public static synthetic fun copy$default (Lsk/ainet/lang/tensor/storage/StorageMemoryReport;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/Ownership;Lsk/ainet/lang/tensor/storage/Placement;JJZZZILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/StorageMemoryReport; public fun equals (Ljava/lang/Object;)Z public final fun getCompressionRatio ()D public final fun getDtype ()Lsk/ainet/lang/types/DType; public final fun getEncoding ()Lsk/ainet/lang/tensor/storage/TensorEncoding; public final fun getLogicalBytes ()J - public final fun getLogicalType ()Lsk/ainet/lang/tensor/storage/LogicalDType; public final fun getOwnership ()Lsk/ainet/lang/tensor/storage/Ownership; public final fun getPhysicalBytes ()J public final fun getPlacement ()Lsk/ainet/lang/tensor/storage/Placement; @@ -8012,20 +7962,18 @@ public final class sk/ainet/lang/tensor/storage/TensorEncoding$TurboQuantPolarQj } public final class sk/ainet/lang/tensor/storage/TensorStorage { - public fun (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZ)V - public synthetic fun (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZ)V public synthetic fun (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lsk/ainet/lang/tensor/Shape; - public final fun component2 ()Lsk/ainet/lang/tensor/storage/LogicalDType; + public final fun component2 ()Lsk/ainet/lang/types/DType; public final fun component3 ()Lsk/ainet/lang/tensor/storage/TensorEncoding; public final fun component4 ()Lsk/ainet/lang/tensor/storage/BufferHandle; public final fun component5 ()Lsk/ainet/lang/tensor/storage/Placement; public final fun component6 ()J public final fun component7 ()[J public final fun component8 ()Z - public final fun copy (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZ)Lsk/ainet/lang/tensor/storage/TensorStorage; - public static synthetic fun copy$default (Lsk/ainet/lang/tensor/storage/TensorStorage;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; + public final fun copy (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZ)Lsk/ainet/lang/tensor/storage/TensorStorage; + public static synthetic fun copy$default (Lsk/ainet/lang/tensor/storage/TensorStorage;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Lsk/ainet/lang/tensor/storage/BufferHandle;Lsk/ainet/lang/tensor/storage/Placement;J[JZILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun copyMaterialize ()Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun copyMaterialize (Lsk/ainet/lang/tensor/storage/BufferResolver;)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun copyToDevice (Lsk/ainet/lang/tensor/storage/DeviceKind;)Lsk/ainet/lang/tensor/storage/TensorStorage; @@ -8038,7 +7986,6 @@ public final class sk/ainet/lang/tensor/storage/TensorStorage { public final fun getElementCount ()J public final fun getEncoding ()Lsk/ainet/lang/tensor/storage/TensorEncoding; public final fun getLogicalBytes ()J - public final fun getLogicalType ()Lsk/ainet/lang/tensor/storage/LogicalDType; public final fun getOwnership ()Lsk/ainet/lang/tensor/storage/Ownership; public final fun getPhysicalBytes ()J public final fun getPlacement ()Lsk/ainet/lang/tensor/storage/Placement; @@ -8060,17 +8007,12 @@ public final class sk/ainet/lang/tensor/storage/TensorStorage { public final class sk/ainet/lang/tensor/storage/TensorStorageFactory { public static final field INSTANCE Lsk/ainet/lang/tensor/storage/TensorStorageFactory; public final fun borrowFloatArray (Lsk/ainet/lang/tensor/Shape;[F)Lsk/ainet/lang/tensor/storage/TensorStorage; - public final fun fileBacked (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Ljava/lang/String;JJ)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun fileBacked (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;Ljava/lang/String;JJ)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun fromFloatArray (Lsk/ainet/lang/tensor/Shape;[F)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun fromIntArray (Lsk/ainet/lang/tensor/Shape;[I)Lsk/ainet/lang/tensor/storage/TensorStorage; - public final fun fromRawBytes (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun fromRawBytes (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; - public static synthetic fun fromRawBytes$default (Lsk/ainet/lang/tensor/storage/TensorStorageFactory;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; public static synthetic fun fromRawBytes$default (Lsk/ainet/lang/tensor/storage/TensorStorageFactory;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; - public final fun fromRawBytesOwned (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun fromRawBytesOwned (Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;)Lsk/ainet/lang/tensor/storage/TensorStorage; - public static synthetic fun fromRawBytesOwned$default (Lsk/ainet/lang/tensor/storage/TensorStorageFactory;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/tensor/storage/LogicalDType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; public static synthetic fun fromRawBytesOwned$default (Lsk/ainet/lang/tensor/storage/TensorStorageFactory;Lsk/ainet/lang/tensor/Shape;Lsk/ainet/lang/types/DType;Lsk/ainet/lang/tensor/storage/TensorEncoding;[BLsk/ainet/lang/tensor/storage/Placement;ILjava/lang/Object;)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun fromTensorData (Lsk/ainet/lang/tensor/data/TensorData;)Lsk/ainet/lang/tensor/storage/TensorStorage; public final fun toTensorData (Lsk/ainet/lang/tensor/storage/TensorStorage;)Lsk/ainet/lang/tensor/data/TensorData; diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDType.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDType.kt deleted file mode 100644 index fb2d31b8..00000000 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDType.kt +++ /dev/null @@ -1,108 +0,0 @@ -@file:Suppress("DEPRECATION") // this file *is* the deprecated type - -package sk.ainet.lang.tensor.storage - -import sk.ainet.lang.types.BF16 -import sk.ainet.lang.types.DType -import sk.ainet.lang.types.FP16 -import sk.ainet.lang.types.FP32 -import sk.ainet.lang.types.FP64 -import sk.ainet.lang.types.Int16 -import sk.ainet.lang.types.Int32 -import sk.ainet.lang.types.Int4 -import sk.ainet.lang.types.Int64 -import sk.ainet.lang.types.Int8 -import sk.ainet.lang.types.UInt8 -import sk.ainet.lang.types.UInt16 -import sk.ainet.lang.types.UInt32 -import sk.ainet.lang.types.UInt64 -import sk.ainet.lang.types.Ternary - -/** - * Logical numeric type — what the tensor values mean semantically. - * - * This is intentionally separate from [TensorEncoding], which describes how - * values are physically stored. A tensor with logical type [FLOAT32] might - * be encoded as [TensorEncoding.Dense], [TensorEncoding.Q4_K], etc. - */ -@Deprecated( - message = "LogicalDType merges into DType (SKEEP-003 decision #13): use the DType objects (FP32, Int8, …) and the dtype-first constructors/overloads.", - replaceWith = ReplaceWith("DType", "sk.ainet.lang.types.DType"), -) -public enum class LogicalDType( - public val sizeInBits: Int, - public val isFloatingPoint: Boolean, - public val isSigned: Boolean -) { - TERNARY(2, isFloatingPoint = false, isSigned = true), - INT4(4, isFloatingPoint = false, isSigned = true), - INT8(8, isFloatingPoint = false, isSigned = true), - INT16(16, isFloatingPoint = false, isSigned = true), - INT32(32, isFloatingPoint = false, isSigned = true), - INT64(64, isFloatingPoint = false, isSigned = true), - UINT8(8, isFloatingPoint = false, isSigned = false), - UINT16(16, isFloatingPoint = false, isSigned = false), - UINT32(32, isFloatingPoint = false, isSigned = false), - UINT64(64, isFloatingPoint = false, isSigned = false), - FLOAT16(16, isFloatingPoint = true, isSigned = true), - BFLOAT16(16, isFloatingPoint = true, isSigned = true), - FLOAT32(32, isFloatingPoint = true, isSigned = true), - FLOAT64(64, isFloatingPoint = true, isSigned = true); - - public val sizeInBytes: Int get() = (sizeInBits + 7) / 8 - - /** - * The [DType] this logical type corresponds to — the inverse of [fromDType]. - * - * Bridge half 1 of 2 (SKEEP-003 Phase 0, decision #13): [LogicalDType] and [DType] are - * bijective (14 ↔ 14) and will merge into one sealed `DType` that carries its `KClass` - * witness; until then this is the single sanctioned way to go from a storage descriptor's - * logical type to the `DType` the tensor DSL uses. Total — every constant maps to exactly - * one `DType` object — and exhaustive by construction (no `else` branch). - * - * @see sk.ainet.lang.tensor.storage.toLogicalDType - */ - public fun toDType(): DType = when (this) { - TERNARY -> Ternary - INT4 -> Int4 - INT8 -> Int8 - INT16 -> Int16 - INT32 -> Int32 - INT64 -> Int64 - UINT8 -> UInt8 - UINT16 -> UInt16 - UINT32 -> UInt32 - UINT64 -> UInt64 - FLOAT16 -> FP16 - BFLOAT16 -> BF16 - FLOAT32 -> FP32 - FLOAT64 -> FP64 - } - - public companion object { - /** - * The [LogicalDType] for a [DType]. Inverse of [toDType]; prefer the extension - * [sk.ainet.lang.tensor.storage.toLogicalDType] at call sites. - */ - @Deprecated( - message = "LogicalDType merges into DType (SKEEP-003 decision #13); the DType object itself is the logical type.", - replaceWith = ReplaceWith("dtype"), - ) - public fun fromDType(dtype: DType): LogicalDType = when (dtype) { - is Ternary -> TERNARY - is Int4 -> INT4 - is Int8 -> INT8 - is Int16 -> INT16 - is Int32 -> INT32 - is Int64 -> INT64 - is UInt8 -> UINT8 - is UInt16 -> UINT16 - is UInt32 -> UINT32 - is UInt64 -> UINT64 - is FP16 -> FLOAT16 - is BF16 -> BFLOAT16 - is FP32 -> FLOAT32 - is FP64 -> FLOAT64 - } - } -} diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridge.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridge.kt deleted file mode 100644 index 7bbfb329..00000000 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridge.kt +++ /dev/null @@ -1,27 +0,0 @@ -@file:JvmName("DTypeBridge") -@file:Suppress("DEPRECATION") // the bridge exists to keep the deprecated type usable until removal - -package sk.ainet.lang.tensor.storage - -import sk.ainet.lang.types.DType -import kotlin.jvm.JvmName - -/** - * The [LogicalDType] describing this [DType] — bridge half 2 of 2 (SKEEP-003 Phase 0, - * decision #13). - * - * Lives in the storage package (not in `sk.ainet.lang.types`) so the type package stays a - * leaf; the mapping is total and bijective with [LogicalDType.toDType]: - * - * ``` - * for (l in LogicalDType.entries) check(l.toDType().toLogicalDType() == l) - * ``` - * - * When `LogicalDType` is deprecated (decision #13, separate slice) its `ReplaceWith` targets - * point at `DType` directly and this extension becomes a no-op shim. - */ -@Deprecated( - message = "LogicalDType merges into DType (SKEEP-003 decision #13); keep the DType itself.", - replaceWith = ReplaceWith("this"), -) -public fun DType.toLogicalDType(): LogicalDType = LogicalDType.fromDType(this) diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorage.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorage.kt index 97811780..b3062e9e 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorage.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorage.kt @@ -127,24 +127,15 @@ public interface PackedBlockStorage { return result } - /** - * Convert this packed storage to a [TensorStorage] descriptor. - */ - @Deprecated(message = "Use toTensorStorage(dtype, placement) (SKEEP-003 decision #13).", replaceWith = ReplaceWith("toTensorStorage(dtype, placement)")) + /** Convert this packed storage to a [TensorStorage] descriptor (packed weights are logically FP32). */ public fun toTensorStorage( - logicalType: LogicalDType = LogicalDType.FLOAT32, + dtype: sk.ainet.lang.types.DType = sk.ainet.lang.types.FP32, placement: Placement = Placement.CPU_HEAP ): TensorStorage = TensorStorage( shape = shape, - logicalType = logicalType, + dtype = dtype, encoding = encoding, buffer = BufferHandle.Borrowed(packedData, isMutable = false), placement = placement ) - - /** Convert this packed storage to a [TensorStorage] descriptor, dtype-first (packed weights are logically [FP32]). */ - public fun toTensorStorage( - dtype: sk.ainet.lang.types.DType, - placement: Placement = Placement.CPU_HEAP - ): TensorStorage = toTensorStorage(dtype.toLogicalDType(), placement) } diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/StorageMemoryReport.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/StorageMemoryReport.kt index 799e28e5..29d8b4e5 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/StorageMemoryReport.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/StorageMemoryReport.kt @@ -12,8 +12,7 @@ import sk.ainet.lang.tensor.Shape */ public data class StorageMemoryReport( val shape: Shape, - @Deprecated(message = "Use dtype (SKEEP-003 decision #13).", replaceWith = ReplaceWith("dtype")) - val logicalType: LogicalDType, + val dtype: sk.ainet.lang.types.DType, val encoding: TensorEncoding, val ownership: Ownership, val placement: Placement, @@ -23,9 +22,6 @@ public data class StorageMemoryReport( val isAlias: Boolean, val isMutable: Boolean ) { - /** The [sk.ainet.lang.types.DType] of [logicalType] (SKEEP-003 Phase 0 bridge). */ - val dtype: sk.ainet.lang.types.DType get() = logicalType.toDType() - /** Compression ratio: logical / physical. >1 means the encoding is smaller than dense. */ val compressionRatio: Double get() = if (physicalBytes > 0) logicalBytes.toDouble() / physicalBytes else 1.0 @@ -33,7 +29,7 @@ public data class StorageMemoryReport( override fun toString(): String = buildString { append("StorageMemoryReport(") append("shape=$shape, ") - append("logical=$logicalType, ") + append("logical=${dtype.name}, ") append("encoding=${encoding.name}, ") append("ownership=$ownership, ") append("placement=${placement.device}/${placement.domain}, ") diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorEncoding.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorEncoding.kt index 6b3bff89..ed577740 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorEncoding.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorEncoding.kt @@ -4,7 +4,7 @@ package sk.ainet.lang.tensor.storage * Physical storage encoding — how tensor data is laid out in memory. * * A [TensorEncoding] describes the byte-level format of a buffer, independent - * of the logical numeric type ([LogicalDType]). For example, a FLOAT32 tensor + * of the logical numeric type ([sk.ainet.lang.types.DType]). For example, a FLOAT32 tensor * may be stored as [Dense] (4 bytes per element) or as [Q4_K] (packed 4-bit * blocks with per-block scales). * diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt index e4af9e76..be5338ac 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorage.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // keeps the LogicalDType primary constructor working until the next major - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -12,7 +10,7 @@ import sk.ainet.lang.types.DType * tensor: the streaming GGUF and SafeTensors readers return it. It carries * enough information to handle a tensor without inspecting its bytes: * - * - **What** the values mean: [logicalType] + * - **What** the values mean: [dtype] * - **How** they are stored: [encoding] * - **Where** the bytes live: [buffer] + [placement] * - **Layout**: [shape], [byteOffset], [strides], [isContiguous] @@ -25,11 +23,7 @@ import sk.ainet.lang.types.DType */ public data class TensorStorage( val shape: Shape, - @Deprecated( - message = "Use dtype (SKEEP-003 decision #13); the LogicalDType view stays until the next major.", - replaceWith = ReplaceWith("dtype"), - ) - val logicalType: LogicalDType, + val dtype: DType, val encoding: TensorEncoding, val buffer: BufferHandle, val placement: Placement = Placement.CPU_HEAP, @@ -37,29 +31,11 @@ public data class TensorStorage( val strides: LongArray? = null, val isContiguous: Boolean = true ) { - /** - * Construct from a [DType] (SKEEP-003 Phase 0): the dtype-first form every new call site uses; - * the [LogicalDType] primary constructor stays for source compatibility until the next major. - */ - public constructor( - shape: Shape, - dtype: DType, - encoding: TensorEncoding, - buffer: BufferHandle, - placement: Placement = Placement.CPU_HEAP, - byteOffset: Long = 0, - strides: LongArray? = null, - isContiguous: Boolean = true, - ) : this(shape, dtype.toLogicalDType(), encoding, buffer, placement, byteOffset, strides, isContiguous) - - /** The [DType] of this storage — what the values mean (SKEEP-003 Phase 0 bridge over [logicalType]). */ - val dtype: DType get() = logicalType.toDType() - /** Number of logical elements in this tensor. */ val elementCount: Long get() = shape.volume.toLong() /** Logical size: number of elements x logical element size. */ - val logicalBytes: Long get() = elementCount * logicalType.sizeInBytes + val logicalBytes: Long get() = elementCount * dtype.sizeInBytes /** Physical size: actual bytes consumed in the buffer for this tensor. */ val physicalBytes: Long get() = encoding.physicalBytes(elementCount) ?: buffer.sizeInBytes @@ -82,7 +58,7 @@ public data class TensorStorage( */ public fun memoryReport(): StorageMemoryReport = StorageMemoryReport( shape = shape, - logicalType = logicalType, + dtype = dtype, encoding = encoding, ownership = ownership, placement = placement, @@ -199,7 +175,7 @@ public data class TensorStorage( * first, then quantized. The target encoding and shape are validated before the buffer is * read, with the loader's messages. * - * The result is owned packed host storage; [logicalType], shape and placement device are + * The result is owned packed host storage; [dtype], shape and placement device are * kept. [BufferHandle.FileBacked] sources need the overload that takes a [BufferResolver]. */ public fun requantizeTo(targetEncoding: TensorEncoding): TensorStorage = @@ -216,7 +192,7 @@ public data class TensorStorage( ) return TensorStorage( shape = shape, - logicalType = logicalType, + dtype = dtype, encoding = targetEncoding, buffer = BufferHandleFactory.owned(packed.packedData), placement = placement.copy(domain = MemoryDomain.HOST_HEAP), @@ -242,7 +218,7 @@ public data class TensorStorage( if (this === other) return true if (other !is TensorStorage) return false return shape == other.shape && - logicalType == other.logicalType && + dtype == other.dtype && encoding == other.encoding && buffer == other.buffer && placement == other.placement && @@ -253,7 +229,7 @@ public data class TensorStorage( override fun hashCode(): Int { var result = shape.hashCode() - result = 31 * result + logicalType.hashCode() + result = 31 * result + dtype.hashCode() result = 31 * result + encoding.hashCode() result = 31 * result + buffer.hashCode() result = 31 * result + placement.hashCode() diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorageFactory.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorageFactory.kt index 98976f47..99a1857d 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorageFactory.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/storage/TensorStorageFactory.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // the LogicalDType overloads stay as delegating shims until the next major - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -81,86 +79,55 @@ public object TensorStorageFactory { * Create storage from raw bytes with explicit encoding. * The byte array is borrowed (not copied). */ - @Deprecated(message = "Use the DType overload (SKEEP-003 decision #13).", replaceWith = ReplaceWith("fromRawBytes(shape, dtype, encoding, …)")) public fun fromRawBytes( shape: Shape, - logicalType: LogicalDType, + dtype: DType, encoding: TensorEncoding, data: ByteArray, placement: Placement = Placement.CPU_HEAP ): TensorStorage = TensorStorage( shape = shape, - logicalType = logicalType, + dtype = dtype, encoding = encoding, buffer = BufferHandleFactory.borrow(data), placement = placement ) - /** Create storage from raw bytes with explicit encoding, dtype-first (the byte array is borrowed). */ - public fun fromRawBytes( - shape: Shape, - dtype: DType, - encoding: TensorEncoding, - data: ByteArray, - placement: Placement = Placement.CPU_HEAP - ): TensorStorage = fromRawBytes(shape, dtype.toLogicalDType(), encoding, data, placement) - /** * Create storage from raw bytes with explicit encoding (owned copy). */ - @Deprecated(message = "Use the DType overload (SKEEP-003 decision #13).", replaceWith = ReplaceWith("fromRawBytesOwned(shape, dtype, encoding, …)")) public fun fromRawBytesOwned( shape: Shape, - logicalType: LogicalDType, + dtype: DType, encoding: TensorEncoding, data: ByteArray, placement: Placement = Placement.CPU_HEAP ): TensorStorage = TensorStorage( shape = shape, - logicalType = logicalType, + dtype = dtype, encoding = encoding, buffer = BufferHandleFactory.owned(data), placement = placement ) - /** Create storage from raw bytes with explicit encoding (owned copy), dtype-first. */ - public fun fromRawBytesOwned( - shape: Shape, - dtype: DType, - encoding: TensorEncoding, - data: ByteArray, - placement: Placement = Placement.CPU_HEAP - ): TensorStorage = fromRawBytesOwned(shape, dtype.toLogicalDType(), encoding, data, placement) - /** * Create file-backed storage (for memory-mapped model weights). */ - @Deprecated(message = "Use the DType overload (SKEEP-003 decision #13).", replaceWith = ReplaceWith("fileBacked(shape, dtype, encoding, …)")) public fun fileBacked( shape: Shape, - logicalType: LogicalDType, + dtype: DType, encoding: TensorEncoding, path: String, fileOffset: Long, sizeInBytes: Long ): TensorStorage = TensorStorage( shape = shape, - logicalType = logicalType, + dtype = dtype, encoding = encoding, buffer = BufferHandleFactory.fileBacked(path, fileOffset, sizeInBytes), placement = Placement.MMAP_WEIGHTS ) - /** Create file-backed storage (for memory-mapped model weights), dtype-first. */ - public fun fileBacked( - shape: Shape, - dtype: DType, - encoding: TensorEncoding, - path: String, - fileOffset: Long, - sizeInBytes: Long - ): TensorStorage = fileBacked(shape, dtype.toLogicalDType(), encoding, path, fileOffset, sizeInBytes) - /** * Bridge: create a [TensorStorage] descriptor from an existing [TensorData]. * @@ -183,25 +150,25 @@ public object TensorStorageFactory { return when (data) { is FloatArrayTensorData<*> -> TensorStorage( shape = data.shape, - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(bytesPerElement = 4), buffer = BufferHandleFactory.owned(data.buffer) ) is IntArrayTensorData<*> -> TensorStorage( shape = data.shape, - logicalType = LogicalDType.INT32, + dtype = Int32, encoding = TensorEncoding.Dense(bytesPerElement = 4), buffer = BufferHandleFactory.owned(data.buffer) ) is Q4_KTensorData -> TensorStorage( shape = data.shape, - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, buffer = BufferHandleFactory.borrow(data.packedData) ) is Q8_0TensorData -> TensorStorage( shape = data.shape, - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q8_0, buffer = BufferHandleFactory.borrow(data.packedData) ) diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/types/DType.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/types/DType.kt index 93ed4de8..aa48f5b4 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/types/DType.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/types/DType.kt @@ -8,8 +8,8 @@ import kotlin.reflect.KClass * * `DType` is sealed with exactly fourteen `object` members, so it is switchable like an enum * (`when (dtype) { FP32 -> … }`) while each member still serves as the generic witness of - * `Tensor` through [witness]. SKEEP-003 (decision #13): this single type replaces - * the storage-side `LogicalDType` enum; `Format = (DType, TensorEncoding)` has exactly one dtype + * `Tensor` through [witness]. SKEEP-003 (decision #13): this single type replaced + * the storage-side `LogicalDType` enum (removed in #1502); `Format = (DType, TensorEncoding)` has exactly one dtype * type. */ public sealed interface DType { diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/AcceptanceCriteriaTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/AcceptanceCriteriaTest.kt index 4ebce753..ed824529 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/AcceptanceCriteriaTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/AcceptanceCriteriaTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -11,6 +9,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import sk.ainet.lang.types.FP16 /** * End-to-end acceptance criteria tests for the Memory Architecture PRD. @@ -43,7 +42,7 @@ class AcceptanceCriteriaTest { fun ac2_fileBackedStoragePreservesPlacement() { val storage = TensorStorageFactory.fileBacked( shape = Shape(1024, 768), - logicalType = LogicalDType.FLOAT16, + dtype = FP16, encoding = TensorEncoding.Dense(2), path = "/model/weights.bin", fileOffset = 0, @@ -74,7 +73,7 @@ class AcceptanceCriteriaTest { // source array must be visible through the storage handle. val bytes = ByteArray(12) val storage = TensorStorageFactory.fromRawBytes( - Shape(3), LogicalDType.FLOAT32, TensorEncoding.Dense(4), bytes + Shape(3), FP32, TensorEncoding.Dense(4), bytes ) assertEquals(Ownership.BORROWED, storage.ownership) bytes[0] = 42 @@ -141,7 +140,7 @@ class AcceptanceCriteriaTest { val storage = TensorStorageFactory.fromTensorData(td) val report = storage.memoryReport() - assertEquals(LogicalDType.FLOAT32, report.logicalType) + assertEquals(FP32, report.dtype) assertEquals("Dense(4B)", report.encoding.name) assertEquals(Ownership.OWNED, report.ownership) assertEquals(24L, report.logicalBytes) @@ -155,14 +154,14 @@ class AcceptanceCriteriaTest { fun ac5_packedQ4KReportsAllFields() { val storage = TensorStorage( shape = Shape(512), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, buffer = BufferHandle.Borrowed(ByteArray(288)), // 2 blocks placement = Placement.CPU_HEAP ) val report = storage.memoryReport() - assertEquals(LogicalDType.FLOAT32, report.logicalType) + assertEquals(FP32, report.dtype) assertEquals("Q4_K", report.encoding.name) assertEquals(Ownership.BORROWED, report.ownership) assertEquals(DeviceKind.CPU, report.placement.device) @@ -176,7 +175,7 @@ class AcceptanceCriteriaTest { fun ac5_fileBackedReportsAllFields() { val storage = TensorStorage( shape = Shape(1000), - logicalType = LogicalDType.FLOAT16, + dtype = FP16, encoding = TensorEncoding.Dense(2), buffer = BufferHandle.FileBacked("/model.bin", 4096, 2000), placement = Placement.MMAP_WEIGHTS @@ -195,7 +194,7 @@ class AcceptanceCriteriaTest { fun ac6_weightsAreImmutableAndPersistent() { val weights = TensorStorage( shape = Shape(768, 768), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.FileBacked("/model.bin", 0, 768L * 768 * 4), placement = Placement.MMAP_WEIGHTS @@ -209,7 +208,7 @@ class AcceptanceCriteriaTest { fun ac6_activationsAreMutableAndTransient() { val activations = TensorStorage( shape = Shape(32, 768), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(ByteArray(32 * 768 * 4)), placement = Placement.CPU_HEAP @@ -228,13 +227,13 @@ class AcceptanceCriteriaTest { // Load two tensors — one borrowed, one owned (copy) tracker.record("borrowed_weight", TensorStorage( shape = Shape(100), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Borrowed(ByteArray(400)) )) tracker.record("copied_activation", TensorStorage( shape = Shape(100), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(ByteArray(400)) )) diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/BufferHandleFactoryTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/BufferHandleFactoryTest.kt index 6eda00ed..80f12896 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/BufferHandleFactoryTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/BufferHandleFactoryTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -13,6 +11,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import sk.ainet.lang.types.FP16 class BufferHandleFactoryTest { @@ -101,7 +100,7 @@ class TensorStorageFactoryTest { val storage = TensorStorageFactory.fromFloatArray(shape, data) assertEquals(shape, storage.shape) - assertEquals(LogicalDType.FLOAT32, storage.logicalType) + assertEquals(FP32, storage.dtype) assertEquals(TensorEncoding.Dense(4), storage.encoding) assertEquals(Ownership.OWNED, storage.ownership) assertEquals(24L, storage.logicalBytes) @@ -115,7 +114,7 @@ class TensorStorageFactoryTest { val data = intArrayOf(1, 2, 3, 4) val storage = TensorStorageFactory.fromIntArray(shape, data) - assertEquals(LogicalDType.INT32, storage.logicalType) + assertEquals(Int32, storage.dtype) assertEquals(Ownership.OWNED, storage.ownership) assertEquals(16L, storage.physicalBytes) // 4 * 4 } @@ -125,7 +124,7 @@ class TensorStorageFactoryTest { val data = ByteArray(144) val storage = TensorStorageFactory.fromRawBytes( shape = Shape(256), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, data = data ) @@ -141,7 +140,7 @@ class TensorStorageFactoryTest { fun fileBackedCreatesImmutableStorage() { val storage = TensorStorageFactory.fileBacked( shape = Shape(512, 512), - logicalType = LogicalDType.FLOAT16, + dtype = FP16, encoding = TensorEncoding.Dense(2), path = "/model.bin", fileOffset = 0, @@ -159,7 +158,7 @@ class TensorStorageFactoryTest { val tensorData = DenseFloatArrayTensorData(Shape(3), floatArrayOf(1f, 2f, 3f)) val storage = TensorStorageFactory.fromTensorData(tensorData) - assertEquals(LogicalDType.FLOAT32, storage.logicalType) + assertEquals(FP32, storage.dtype) assertEquals(TensorEncoding.Dense(4), storage.encoding) assertEquals(3L, storage.elementCount) assertEquals(12L, storage.physicalBytes) @@ -173,7 +172,7 @@ class TensorStorageFactoryTest { val tensorData = DenseIntArrayTensorData(Shape(2), intArrayOf(10, 20)) val storage = TensorStorageFactory.fromTensorData(tensorData) - assertEquals(LogicalDType.INT32, storage.logicalType) + assertEquals(Int32, storage.dtype) assertEquals(TensorEncoding.Dense(4), storage.encoding) assertEquals(Ownership.OWNED, storage.ownership) } @@ -184,7 +183,7 @@ class TensorStorageFactoryTest { val tensorData = Q4_KBlockTensorData.fromRawBytes(Shape(256), packedData) val storage = TensorStorageFactory.fromTensorData(tensorData) - assertEquals(LogicalDType.FLOAT32, storage.logicalType) + assertEquals(FP32, storage.dtype) assertEquals(TensorEncoding.Q4_K, storage.encoding) assertEquals(Ownership.BORROWED, storage.ownership) assertEquals(144L, storage.physicalBytes) @@ -209,7 +208,7 @@ class TensorStorageFactoryTest { val tensorData = Q8_0BlockTensorData.fromRawBytes(Shape(32), packedData) val storage = TensorStorageFactory.fromTensorData(tensorData) - assertEquals(LogicalDType.FLOAT32, storage.logicalType) + assertEquals(FP32, storage.dtype) assertEquals(TensorEncoding.Q8_0, storage.encoding) assertEquals(Ownership.BORROWED, storage.ownership) assertEquals(34L, storage.physicalBytes) @@ -219,7 +218,7 @@ class TensorStorageFactoryTest { fun memoryReportFromFactory() { val storage = TensorStorageFactory.fromRawBytes( shape = Shape(256), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, data = ByteArray(144) ) diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/CompressedKvAttentionTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/CompressedKvAttentionTest.kt index 3aac6a09..abe81bf0 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/CompressedKvAttentionTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/CompressedKvAttentionTest.kt @@ -1,11 +1,10 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import sk.ainet.lang.types.FP32 /** * Tests for [CompressedKvAttention] — the bridge between KvCacheStore and SDPA. @@ -72,11 +71,11 @@ class CompressedKvAttentionTest { bridge.storeKeyValue(0, FloatArray(8), FloatArray(8)) val keyStorage = bridge.loadKeyStorageRaw(0) - assertEquals(LogicalDType.FLOAT32, keyStorage.logicalType) + assertEquals(FP32, keyStorage.dtype) assertEquals(Ownership.OWNED, keyStorage.ownership) val valueStorage = bridge.loadValueStorageRaw(0) - assertEquals(LogicalDType.FLOAT32, valueStorage.logicalType) + assertEquals(FP32, valueStorage.dtype) } @Test diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/ExplicitCopyApiTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/ExplicitCopyApiTest.kt index c6fc7b38..1cf84ece 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/ExplicitCopyApiTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/ExplicitCopyApiTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -83,7 +81,7 @@ class ExplicitCopyApiTest { val rawData = ByteArray(144) // Q4_K block val storage = TensorStorageFactory.fromRawBytes( shape = Shape(256), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, data = rawData ) @@ -95,7 +93,7 @@ class ExplicitCopyApiTest { val rawData = ByteArray(144) val storage = TensorStorageFactory.fromRawBytesOwned( shape = Shape(256), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, data = rawData ) diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/KvCacheStoreTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/KvCacheStoreTest.kt index b5570e2d..94cdd483 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/KvCacheStoreTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/KvCacheStoreTest.kt @@ -1,11 +1,10 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue +import sk.ainet.lang.types.FP32 /** * Tests for [KvCacheStore] contract and [DefaultKvCacheStore] implementation. @@ -123,7 +122,7 @@ class KvCacheStoreTest { store.appendToken(0, FloatArray(8) { it.toFloat() }, FloatArray(8)) val storage = store.readKeyStorage(0) - assertEquals(LogicalDType.FLOAT32, storage.logicalType) + assertEquals(FP32, storage.dtype) assertEquals(Ownership.OWNED, storage.ownership) assertTrue(storage.isMutable) } diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridgeTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridgeTest.kt deleted file mode 100644 index ec35ec5e..00000000 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/LogicalDTypeBridgeTest.kt +++ /dev/null @@ -1,106 +0,0 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - -package sk.ainet.lang.tensor.storage - -import sk.ainet.lang.tensor.Shape -import sk.ainet.lang.types.BF16 -import sk.ainet.lang.types.DType -import sk.ainet.lang.types.FP16 -import sk.ainet.lang.types.FP32 -import sk.ainet.lang.types.FP64 -import sk.ainet.lang.types.Int16 -import sk.ainet.lang.types.Int32 -import sk.ainet.lang.types.Int4 -import sk.ainet.lang.types.Int64 -import sk.ainet.lang.types.Int8 -import sk.ainet.lang.types.Ternary -import sk.ainet.lang.types.UInt16 -import sk.ainet.lang.types.UInt32 -import sk.ainet.lang.types.UInt64 -import sk.ainet.lang.types.UInt8 -import sk.ainet.lang.types.isFloatingPoint -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertSame - -/** - * SKEEP-003 Phase 0, decision #13: the two-way `LogicalDType` <-> `DType` bridge must be total - * and bijective so that `LogicalDType` can later be merged into `DType` without a semantic gap. - */ -class LogicalDTypeBridgeTest { - - private val expectedPairs: List> = listOf( - LogicalDType.TERNARY to Ternary, - LogicalDType.INT4 to Int4, - LogicalDType.INT8 to Int8, - LogicalDType.INT16 to Int16, - LogicalDType.INT32 to Int32, - LogicalDType.INT64 to Int64, - LogicalDType.UINT8 to UInt8, - LogicalDType.UINT16 to UInt16, - LogicalDType.UINT32 to UInt32, - LogicalDType.UINT64 to UInt64, - LogicalDType.FLOAT16 to FP16, - LogicalDType.BFLOAT16 to BF16, - LogicalDType.FLOAT32 to FP32, - LogicalDType.FLOAT64 to FP64, - ) - - @Test - fun bothTypeSystemsHaveFourteenMembers() { - assertEquals(14, LogicalDType.entries.size) - assertEquals(14, DType.getAllTypes().size) - assertEquals(14, expectedPairs.size) - } - - @Test - fun explicitPairTable() { - for ((logical, dtype) in expectedPairs) { - assertSame(dtype, logical.toDType(), "toDType of $logical") - assertEquals(logical, dtype.toLogicalDType(), "toLogicalDType of ${dtype.name}") - } - } - - @Test - fun logicalToDTypeAndBackIsIdentity() { - for (logical in LogicalDType.entries) { - assertEquals(logical, logical.toDType().toLogicalDType(), "round trip of $logical") - } - } - - @Test - fun dtypeToLogicalAndBackIsIdentity() { - for (dtype in DType.getAllTypes().values) { - assertSame(dtype, dtype.toLogicalDType().toDType(), "round trip of ${dtype.name}") - } - } - - @Test - fun mappingIsBijective() { - val images = LogicalDType.entries.map { it.toDType() }.toSet() - assertEquals(LogicalDType.entries.size, images.size, "toDType must be injective") - val preImages = DType.getAllTypes().values.map { it.toLogicalDType() }.toSet() - assertEquals(DType.getAllTypes().size, preImages.size, "toLogicalDType must be injective") - } - - @Test - fun widthAndFloatnessAgreeAcrossTheBridge() { - for (logical in LogicalDType.entries) { - val dtype = logical.toDType() - assertEquals(logical.sizeInBits, dtype.sizeInBits, "sizeInBits of $logical") - assertEquals(logical.isFloatingPoint, dtype.isFloatingPoint(), "isFloatingPoint of $logical") - } - } - - @Test - fun descriptorsExposeTheDType() { - val storage = TensorStorage( - shape = Shape(2, 3), - logicalType = LogicalDType.FLOAT32, - encoding = TensorEncoding.Dense(4), - buffer = BufferHandle.Owned(ByteArray(24)), - ) - assertSame(FP32, storage.dtype) - assertSame(FP32, storage.memoryReport().dtype) - } -} diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/MemoryTrackerTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/MemoryTrackerTest.kt index f75b9b90..dfb97e34 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/MemoryTrackerTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/MemoryTrackerTest.kt @@ -1,10 +1,10 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape import kotlin.test.Test import kotlin.test.assertEquals +import sk.ainet.lang.types.FP16 +import sk.ainet.lang.types.FP32 class MemoryTrackerTest { @@ -14,13 +14,13 @@ class MemoryTrackerTest { val s1 = TensorStorage( shape = Shape(100), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(ByteArray(400)) ) val s2 = TensorStorage( shape = Shape(256), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, buffer = BufferHandle.Borrowed(ByteArray(144)) ) @@ -52,7 +52,7 @@ class MemoryTrackerTest { val tracker = MemoryTracker() tracker.record("x", TensorStorage( shape = Shape(10), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(ByteArray(40)) )) @@ -69,7 +69,7 @@ class MemoryTrackerTest { val tracker = MemoryTracker() tracker.record("mmap_weight", TensorStorage( shape = Shape(1000), - logicalType = LogicalDType.FLOAT16, + dtype = FP16, encoding = TensorEncoding.Dense(2), buffer = BufferHandle.FileBacked("/model.bin", 0, 2000), placement = Placement.MMAP_WEIGHTS diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NarrowFloatStorageDecodeTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NarrowFloatStorageDecodeTest.kt index eccae687..84d678bb 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NarrowFloatStorageDecodeTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NarrowFloatStorageDecodeTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -37,7 +35,7 @@ class NarrowFloatStorageDecodeTest { val bytes = packed(values, codec) return TensorStorage( shape = Shape(values.size), - logicalType = if (codec === Fp16Codec) LogicalDType.FLOAT16 else LogicalDType.BFLOAT16, + dtype = if (codec === Fp16Codec) FP16 else BF16, encoding = TensorEncoding.Dense(2), buffer = BufferHandle.Owned(bytes, 0, bytes.size.toLong()), ) @@ -79,14 +77,14 @@ class NarrowFloatStorageDecodeTest { @Test fun bf16_and_fp16_decode_differently_for_the_same_bytes() { - // Proves the codec is actually selected by logicalType rather than one being aliased + // Proves the codec is actually selected by dtype rather than one being aliased // to the other: the bit pattern 0x3C00 is 1.0 in fp16 but ~0.0078 in bf16. val bytes = byteArrayOf(0x00, 0x3C) val asFp16 = TensorStorageFactory.toTensorData( - TensorStorage(Shape(1), LogicalDType.FLOAT16, TensorEncoding.Dense(2), BufferHandle.Owned(bytes, 0, 2)), + TensorStorage(Shape(1), FP16, TensorEncoding.Dense(2), BufferHandle.Owned(bytes, 0, 2)), ).copyToFloatArray()[0] val asBf16 = TensorStorageFactory.toTensorData( - TensorStorage(Shape(1), LogicalDType.BFLOAT16, TensorEncoding.Dense(2), BufferHandle.Owned(bytes, 0, 2)), + TensorStorage(Shape(1), BF16, TensorEncoding.Dense(2), BufferHandle.Owned(bytes, 0, 2)), ).copyToFloatArray()[0] assertEquals(1.0f, asFp16) diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NonContiguousStorageTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NonContiguousStorageTest.kt index 921292e3..0dff522c 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NonContiguousStorageTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/NonContiguousStorageTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -9,6 +7,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertNull import kotlin.test.assertTrue +import sk.ainet.lang.types.FP32 class NonContiguousStorageTest { @@ -16,7 +15,7 @@ class NonContiguousStorageTest { fun defaultStorage_stridesNull_isContiguousTrue() { val storage = TensorStorage( shape = Shape(4, 4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(ByteArray(64)) ) @@ -29,7 +28,7 @@ class NonContiguousStorageTest { val strides = longArrayOf(768, 1) val storage = TensorStorage( shape = Shape(1024, 768), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(ByteArray(1024 * 768 * 4)), strides = strides, @@ -45,14 +44,14 @@ class NonContiguousStorageTest { val buf = BufferHandle.Owned(ByteArray(64)) val s1 = TensorStorage( shape = Shape(4, 4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = buf, strides = longArrayOf(4, 1) ) val s2 = TensorStorage( shape = Shape(4, 4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = buf, strides = longArrayOf(1, 4) // transposed strides @@ -63,16 +62,16 @@ class NonContiguousStorageTest { @Test fun equalityNullStridesMatch() { val buf = BufferHandle.Owned(ByteArray(16)) - val s1 = TensorStorage(Shape(4), LogicalDType.FLOAT32, TensorEncoding.Dense(4), buf) - val s2 = TensorStorage(Shape(4), LogicalDType.FLOAT32, TensorEncoding.Dense(4), buf) + val s1 = TensorStorage(Shape(4), FP32, TensorEncoding.Dense(4), buf) + val s2 = TensorStorage(Shape(4), FP32, TensorEncoding.Dense(4), buf) assertEquals(s1, s2) } @Test fun hashCodeDiffersWithDifferentStrides() { val buf = BufferHandle.Owned(ByteArray(64)) - val s1 = TensorStorage(Shape(4, 4), LogicalDType.FLOAT32, TensorEncoding.Dense(4), buf, strides = longArrayOf(4, 1)) - val s2 = TensorStorage(Shape(4, 4), LogicalDType.FLOAT32, TensorEncoding.Dense(4), buf, strides = longArrayOf(1, 4)) + val s1 = TensorStorage(Shape(4, 4), FP32, TensorEncoding.Dense(4), buf, strides = longArrayOf(4, 1)) + val s2 = TensorStorage(Shape(4, 4), FP32, TensorEncoding.Dense(4), buf, strides = longArrayOf(1, 4)) // Not guaranteed by contract but highly likely for different strides assertNotEquals(s1.hashCode(), s2.hashCode()) } @@ -81,7 +80,7 @@ class NonContiguousStorageTest { fun memoryReport_nonContiguous_reportsCorrectBytes() { val storage = TensorStorage( shape = Shape(8, 8), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(ByteArray(256)), strides = longArrayOf(8, 1), diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorageTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorageTest.kt index 7c0e1f80..17e00b21 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorageTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/PackedBlockStorageTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -8,6 +6,7 @@ import sk.ainet.lang.tensor.data.Q8_0BlockTensorData import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +import sk.ainet.lang.types.FP32 class PackedBlockStorageTest { @@ -87,7 +86,7 @@ class PackedBlockStorageTest { val packed = td as PackedBlockStorage val storage = packed.toTensorStorage() - assertEquals(LogicalDType.FLOAT32, storage.logicalType) + assertEquals(FP32, storage.dtype) assertEquals(TensorEncoding.Q4_K, storage.encoding) assertEquals(Ownership.BORROWED, storage.ownership) assertEquals(144L, storage.physicalBytes) diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/StorageToTensorDataTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/StorageToTensorDataTest.kt index f64afe43..8192be97 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/StorageToTensorDataTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/StorageToTensorDataTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -98,7 +96,7 @@ class StorageToTensorDataTest { val storage = TensorStorageFactory.fromRawBytes( shape = Shape(3), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), data = floatBytes ) diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TensorStorageContractTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TensorStorageContractTest.kt index 58942993..f52763ee 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TensorStorageContractTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TensorStorageContractTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -11,35 +9,24 @@ import kotlin.test.assertTrue class TensorStorageContractTest { - // --- LogicalDType --- - - @Test - fun logicalDTypeFromDTypeRoundTrips() { - assertEquals(LogicalDType.FLOAT32, LogicalDType.fromDType(FP32)) - assertEquals(LogicalDType.FLOAT16, LogicalDType.fromDType(FP16)) - assertEquals(LogicalDType.BFLOAT16, LogicalDType.fromDType(BF16)) - assertEquals(LogicalDType.INT32, LogicalDType.fromDType(Int32)) - assertEquals(LogicalDType.INT4, LogicalDType.fromDType(Int4)) - assertEquals(LogicalDType.TERNARY, LogicalDType.fromDType(Ternary)) - assertEquals(LogicalDType.UINT8, LogicalDType.fromDType(UInt8)) - } + // --- DType as the storage type --- @Test - fun logicalDTypeSizeInBytes() { - assertEquals(4, LogicalDType.FLOAT32.sizeInBytes) - assertEquals(2, LogicalDType.FLOAT16.sizeInBytes) - assertEquals(2, LogicalDType.BFLOAT16.sizeInBytes) - assertEquals(4, LogicalDType.INT32.sizeInBytes) - assertEquals(1, LogicalDType.INT8.sizeInBytes) - assertEquals(1, LogicalDType.INT4.sizeInBytes) // 4 bits rounds up to 1 byte + fun dtypeSizeInBytes() { + assertEquals(4, FP32.sizeInBytes) + assertEquals(2, FP16.sizeInBytes) + assertEquals(2, BF16.sizeInBytes) + assertEquals(4, Int32.sizeInBytes) + assertEquals(1, Int8.sizeInBytes) + assertEquals(1, Int4.sizeInBytes) // 4 bits rounds up to 1 byte } @Test - fun logicalDTypeProperties() { - assertTrue(LogicalDType.FLOAT32.isFloatingPoint) - assertTrue(LogicalDType.FLOAT32.isSigned) - assertFalse(LogicalDType.UINT8.isSigned) - assertFalse(LogicalDType.INT32.isFloatingPoint) + fun dtypeProperties() { + assertTrue(FP32.isFloatingPoint()) + assertTrue(FP32.isSigned) + assertFalse(UInt8.isSigned) + assertFalse(Int32.isFloatingPoint()) } // --- TensorEncoding --- @@ -152,7 +139,7 @@ class TensorStorageContractTest { val data = ByteArray(24) // 6 elements * 4 bytes val storage = TensorStorage( shape = shape, - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(data) ) @@ -171,7 +158,7 @@ class TensorStorageContractTest { val data = ByteArray(144) // 1 Q4_K block val storage = TensorStorage( shape = shape, - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, buffer = BufferHandle.Borrowed(data) ) @@ -187,7 +174,7 @@ class TensorStorageContractTest { val shape = Shape(1024, 768) val storage = TensorStorage( shape = shape, - logicalType = LogicalDType.FLOAT16, + dtype = FP16, encoding = TensorEncoding.Dense(2), buffer = BufferHandle.FileBacked("/model.bin", fileOffset = 4096, sizeInBytes = 1024L * 768 * 2), placement = Placement.MMAP_WEIGHTS @@ -204,12 +191,12 @@ class TensorStorageContractTest { val shape = Shape(256) val storage = TensorStorage( shape = shape, - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Q4_K, buffer = BufferHandle.Borrowed(ByteArray(144)) ) val report = storage.memoryReport() - assertEquals(LogicalDType.FLOAT32, report.logicalType) + assertEquals(FP32, report.dtype) assertEquals("Q4_K", report.encoding.name) assertEquals(Ownership.BORROWED, report.ownership) assertEquals(1024L, report.logicalBytes) @@ -225,15 +212,15 @@ class TensorStorageContractTest { fun dtypeConstructorEqualsLogicalTypeConstructor() { val shape = Shape(2, 3) val buffer = BufferHandle.Borrowed(ByteArray(24)) // one instance: BufferHandle equality is identity - val viaLogical = TensorStorage(shape, LogicalDType.FLOAT32, TensorEncoding.Dense(4), buffer) + val viaLogical = TensorStorage(shape, FP32, TensorEncoding.Dense(4), buffer) val viaDType = TensorStorage(shape, FP32, TensorEncoding.Dense(4), buffer) assertEquals(viaLogical, viaDType) assertEquals(FP32, viaDType.dtype) - assertEquals(LogicalDType.FLOAT32, viaDType.logicalType) + assertEquals(FP32, viaDType.dtype) assertEquals(viaLogical.memoryReport(), viaDType.memoryReport()) val bf16 = TensorStorage(shape, BF16, TensorEncoding.Dense(2), BufferHandle.Borrowed(ByteArray(12)), Placement.CPU_HEAP) - assertEquals(LogicalDType.BFLOAT16, bf16.logicalType) + assertEquals(BF16, bf16.dtype) assertEquals(12L, bf16.logicalBytes) } @@ -242,17 +229,17 @@ class TensorStorageContractTest { val shape = Shape(4) val bytes = ByteArray(16) // BufferHandle subclasses have identity equality, so compare the descriptor fields. - fun sig(s: TensorStorage) = listOf(s.shape, s.logicalType, s.dtype, s.encoding, s.ownership, s.placement, s.physicalBytes) + fun sig(s: TensorStorage) = listOf(s.shape, s.dtype, s.dtype, s.encoding, s.ownership, s.placement, s.physicalBytes) assertEquals( - sig(TensorStorageFactory.fromRawBytes(shape, LogicalDType.INT32, TensorEncoding.Dense(4), bytes)), + sig(TensorStorageFactory.fromRawBytes(shape, Int32, TensorEncoding.Dense(4), bytes)), sig(TensorStorageFactory.fromRawBytes(shape, Int32, TensorEncoding.Dense(4), bytes)), ) assertEquals( - sig(TensorStorageFactory.fromRawBytesOwned(shape, LogicalDType.FLOAT16, TensorEncoding.Dense(2), ByteArray(8))), + sig(TensorStorageFactory.fromRawBytesOwned(shape, FP16, TensorEncoding.Dense(2), ByteArray(8))), sig(TensorStorageFactory.fromRawBytesOwned(shape, FP16, TensorEncoding.Dense(2), ByteArray(8))), ) assertEquals( - sig(TensorStorageFactory.fileBacked(shape, LogicalDType.FLOAT32, TensorEncoding.Dense(4), "/m.gguf", 128L, 16L)), + sig(TensorStorageFactory.fileBacked(shape, FP32, TensorEncoding.Dense(4), "/m.gguf", 128L, 16L)), sig(TensorStorageFactory.fileBacked(shape, FP32, TensorEncoding.Dense(4), "/m.gguf", 128L, 16L)), ) assertEquals(FP32, TensorStorageFactory.fromFloatArray(shape, FloatArray(4)).dtype) diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TransferOpsTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TransferOpsTest.kt index 71498f8b..c70ffd10 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TransferOpsTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/tensor/storage/TransferOpsTest.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.tensor.storage import sk.ainet.lang.tensor.Shape @@ -8,12 +6,13 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNotSame import kotlin.test.assertSame +import sk.ainet.lang.types.FP32 class TransferOpsTest { private fun ownedStorage(bytes: ByteArray = ByteArray(16) { it.toByte() }) = TensorStorage( shape = Shape(4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Owned(bytes), placement = Placement.CPU_HEAP @@ -21,7 +20,7 @@ class TransferOpsTest { private fun borrowedStorage() = TensorStorage( shape = Shape(4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Borrowed(ByteArray(16) { it.toByte() }), placement = Placement(device = DeviceKind.CPU, domain = MemoryDomain.MMAP_FILE) @@ -37,7 +36,7 @@ class TransferOpsTest { assertEquals(Ownership.OWNED, copy.ownership) assertEquals(storage.shape, copy.shape) - assertEquals(storage.logicalType, copy.logicalType) + assertEquals(storage.dtype, copy.dtype) assertEquals(storage.encoding, copy.encoding) assertEquals(MemoryDomain.HOST_HEAP, copy.placement.domain) @@ -64,7 +63,7 @@ class TransferOpsTest { val parent = BufferHandle.Owned(parentBytes) val storage = TensorStorage( shape = Shape(4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.Aliased(parent, byteOffset = 8, sizeInBytes = 16), placement = Placement.CPU_HEAP @@ -90,7 +89,7 @@ class TransferOpsTest { }) val storage = TensorStorage( shape = Shape(4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.FileBacked("/model.bin", 0, 16), placement = Placement.MMAP_WEIGHTS @@ -106,7 +105,7 @@ class TransferOpsTest { fun copyMaterialize_fileBackedBuffer_withoutResolver_throwsHelpfully() { val storage = TensorStorage( shape = Shape(4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.FileBacked("/model.bin", 0, 16), placement = Placement.MMAP_WEIGHTS @@ -120,7 +119,7 @@ class TransferOpsTest { fun copyMaterialize_deviceResidentBuffer_throwsUnsupported() { val storage = TensorStorage( shape = Shape(4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.DeviceResident("gpu:0", "opaque", 16, true) ) @@ -139,7 +138,7 @@ class TransferOpsTest { }) val storage = TensorStorage( shape = Shape(4), - logicalType = LogicalDType.FLOAT32, + dtype = FP32, encoding = TensorEncoding.Dense(4), buffer = BufferHandle.FileBacked("/model.bin", 0, 16), placement = Placement.MMAP_WEIGHTS diff --git a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/types/DTypeWitnessTest.kt b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/types/DTypeWitnessTest.kt index aa89c215..4233db56 100644 --- a/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/types/DTypeWitnessTest.kt +++ b/skainet-lang/skainet-lang-core/src/commonTest/kotlin/sk/ainet/lang/types/DTypeWitnessTest.kt @@ -1,9 +1,5 @@ -@file:Suppress("DEPRECATION") // LogicalDType legacy path kept under test until removal (SKEEP-003 #1014) - package sk.ainet.lang.types -import sk.ainet.lang.tensor.storage.LogicalDType -import sk.ainet.lang.tensor.storage.toLogicalDType import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -26,10 +22,6 @@ class DTypeWitnessTest { listOf(Ternary, Int4, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, FP16, BF16, FP32, FP64), DType.entries, ) - // same order as LogicalDType, so the two enumerations line up index by index - for ((i, logical) in LogicalDType.entries.withIndex()) { - assertSame(DType.entries[i], logical.toDType(), "entries[$i] vs $logical") - } } @Test @@ -69,11 +61,9 @@ class DTypeWitnessTest { } @Test - fun signednessAndByteWidthMatchTheStorageEnum() { + fun signednessAndByteWidth() { for (d in DType.entries) { - val logical = d.toLogicalDType() - assertEquals(logical.isSigned, d.isSigned, "isSigned of ${d.name}") - assertEquals(logical.sizeInBytes, d.sizeInBytes, "sizeInBytes of ${d.name}") + assertEquals((d.sizeInBits + 7) / 8, d.sizeInBytes, "sizeInBytes of ${d.name}") } assertFalse(UInt8.isSigned); assertFalse(UInt16.isSigned); assertFalse(UInt32.isSigned); assertFalse(UInt64.isSigned) assertTrue(Int8.isSigned); assertTrue(FP32.isSigned); assertTrue(Ternary.isSigned) From 9c096ce98cc1d9c237fbda5b592c4be5e88cb8d1 Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Wed, 11 Nov 2026 15:31:05 +0100 Subject: [PATCH 4/4] test(backend-cpu): assert live heap, not RSS, in ForwardScopeFlatMemoryTest (#1501) RSS grew past the bound on a CI runner although nothing leaked: a JVM commits heap lazily, so RSS follows garbage that is already dead. The test now asserts the live heap after a collection and prints RSS. Co-Authored-By: Claude Fable 5.2 --- CHANGELOG.md | 2 +- .../tensor/ops/ForwardScopeFlatMemoryTest.kt | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25384573..eb4a5b36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- **Guards for SKEEP-003's acceptance criteria** (build, `skainet-backend-cpu`, docs, #1501): `./gradlew checkTensorDataDowncasts` counts the downcasts to concrete `TensorData` classes outside the data package (162 in 21 files today) against `config/tensor-data-downcast-allowlist.txt`, so a new one fails the build and the list can only shrink; it runs in the `stub-markers` CI leg. `ForwardScopeFlatMemoryTest` runs 300 forward passes under a forward scope on the JVM and asserts that direct memory, RSS and the slab stay flat. `reference/benchmark-baseline.adoc` records the JMH numbers for matmul, elementwise and reductions that a dispatch or storage change is compared against, replacing the baseline deleted in #1106. +- **Guards for SKEEP-003's acceptance criteria** (build, `skainet-backend-cpu`, docs, #1501): `./gradlew checkTensorDataDowncasts` counts the downcasts to concrete `TensorData` classes outside the data package (162 in 21 files today) against `config/tensor-data-downcast-allowlist.txt`, so a new one fails the build and the list can only shrink; it runs in the `stub-markers` CI leg. `ForwardScopeFlatMemoryTest` runs 300 forward passes under a forward scope on the JVM and asserts that direct memory, the live heap and the slab stay flat. `reference/benchmark-baseline.adoc` records the JMH numbers for matmul, elementwise and reductions that a dispatch or storage change is compared against, replacing the baseline deleted in #1106. ### Changed diff --git a/skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt b/skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt index e5d6a704..3f8c5a55 100644 --- a/skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt +++ b/skainet-backends/skainet-backend-cpu/src/jvmTest/kotlin/sk/ainet/exec/tensor/ops/ForwardScopeFlatMemoryTest.kt @@ -22,6 +22,7 @@ import sk.ainet.lang.types.FP32 * The loop is a decode step in miniature — weights outside the scope, activations inside it, * `reset()` at the step boundary — at a size where a leak is unmistakable: one step produces * about 3 MB of activations, so 300 steps would be about 900 MB if anything accumulated. + * Asserted: the direct buffer pool, the live heap after a collection, and the slab's peak. */ class ForwardScopeFlatMemoryTest { @@ -32,6 +33,13 @@ class ForwardScopeFlatMemoryTest { private val directPool: BufferPoolMXBean = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean::class.java).first { it.name == "direct" } + /** Live heap after a collection: what the loop still holds, not what it has churned through. */ + private fun liveHeap(): Long { + val rt = Runtime.getRuntime() + repeat(3) { System.gc(); Thread.sleep(50) } + return rt.totalMemory() - rt.freeMemory() + } + private fun mb(bytes: Long): String = "%.1f MB".format(bytes / (1024.0 * 1024.0)) private fun step(x: Tensor, w: Tensor, b: Tensor): Tensor { @@ -55,6 +63,7 @@ class ForwardScopeFlatMemoryTest { base.forwardScope(slabFloats = 8 * n * n) { scoped, scope -> var directAfterWarmup = 0L var rssAfterWarmup: Long? = null + var heapAfterWarmup = 0L var peakAfterWarmup = 0 var directPeak = 0L @@ -70,6 +79,7 @@ class ForwardScopeFlatMemoryTest { if (stepNo == warmup) { directAfterWarmup = direct rssAfterWarmup = MemoryProbe.rssBytes() + heapAfterWarmup = liveHeap() peakAfterWarmup = scope.peakFloats } if (stepNo > warmup) { @@ -79,20 +89,21 @@ class ForwardScopeFlatMemoryTest { } val directGrowth = directPeak - directAfterWarmup + val heapGrowth = liveHeap() - heapAfterWarmup val rssGrowth = rssAfterWarmup?.let { before -> MemoryProbe.rssBytes()?.minus(before) } println( "[forward-scope] ${steps - warmup} steady steps of ${mb(stepBytes)}: direct pool ${mb(directAfterWarmup)} -> peak ${mb(directPeak)}, " + - "rss growth ${rssGrowth?.let(::mb) ?: "n/a"}, slab peak ${scope.peakFloats} floats", + "live heap growth ${mb(heapGrowth)}, rss growth ${rssGrowth?.let(::mb) ?: "n/a"}, slab peak ${scope.peakFloats} floats", ) // Flat: less than one step's worth of direct memory appears over 280 steps. assertTrue(directGrowth < stepBytes, "direct memory grew by ${mb(directGrowth)} over ${steps - warmup} steps") - // A leak of every activation would be about 840 MB here; allow a tenth of that for the - // JIT, the collector and allocator noise in a shared test JVM. - if (rssGrowth != null) { - val leakIfEverythingStayed = stepBytes * (steps - warmup) - assertTrue(rssGrowth < leakIfEverythingStayed / 10, "RSS grew by ${mb(rssGrowth)} over ${steps - warmup} steps") - } + // A leak of every activation would be about 840 MB here. Live heap after a collection + // is what the loop still holds; allow a tenth of the leak for whatever else the test + // JVM keeps. RSS is printed, not asserted: a JVM grows its committed heap lazily, so + // RSS moves with garbage that is already dead (seen on a CI runner). + val leakIfEverythingStayed = stepBytes * (steps - warmup) + assertTrue(heapGrowth < leakIfEverythingStayed / 10, "live heap grew by ${mb(heapGrowth)} over ${steps - warmup} steps") } } }