From 9ada6f8885a2c8a815f60992f1062ea3df0a390c Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Wed, 26 Aug 2026 11:17:28 +0200 Subject: [PATCH] =?UTF-8?q?refactor(io):=20remove=20the=20legacy=20loader?= =?UTF-8?q?=20axes=20=E2=80=94=20QuantPolicy,=20StagingPolicy,=20WeightOri?= =?UTF-8?q?entation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the resolver arc landed, WeightForm (plus per-tensor weightFormFor) is the loader's whole configuration surface; the three deprecated axes were pure duplication held together by a precedence rule. All three had carried deprecation notices since #1109/#1115 naming their WeightForm counterparts. StreamingGgufParametersLoader loses the three parameters and the legacy→form synthesis; the default is WeightForm.AS_STORED_ON_HEAP, the loader's historical behaviour, so a caller who passed nothing sees no change. AndroidGguf.loader takes a WeightForm (default mapped residency, the Android behaviour). The RAW_BYTES eager rejection goes with the enum that carried it. The flags-vs-form parity matrix retires with the flags; the behavioural coverage it carried — mapped ≡ heap values, streaming-dequant parity, shape orientation — is re-expressed directly in WeightForm terms. Remaining prose references now name WeightResidency/EncodingRequest/ WeightShapeOrientation; the historical 'why one type, not three flags' note in WeightForm's KDoc stays as history. Closes #1159. Co-Authored-By: Claude Fable 5 --- .../ainet/backend/api/kernel/PackedWeights.kt | 2 +- .../backend/api/kernel/PackedWeightsTest.kt | 2 +- .../sk/ainet/io/AndroidRandomAccessSource.kt | 2 +- .../kotlin/sk/ainet/io/RandomAccessSource.kt | 2 +- .../kotlin/sk/ainet/io/model/QuantPolicy.kt | 30 ------ .../kotlin/sk/ainet/io/model/StagingPolicy.kt | 33 ------- .../sk/ainet/io/model/WeightOrientation.kt | 38 ------- .../sk/ainet/io/JvmRandomAccessSource.kt | 2 +- .../ainet/io/PosixPreadRandomAccessSource.kt | 2 +- .../io/gguf/AndroidGgufLoadingHostTest.kt | 5 +- .../sk/ainet/io/gguf/AndroidGgufLoading.kt | 20 ++-- .../io/gguf/StreamingGgufParametersLoader.kt | 98 +++---------------- .../sk/ainet/io/gguf/MappedGgufWeights.kt | 4 +- .../sk/ainet/io/gguf/DequantHeapUsageTest.kt | 11 ++- .../sk/ainet/io/gguf/GgufTernaryLoadTest.kt | 11 ++- .../ainet/io/gguf/StagingPolicyParityTest.kt | 41 ++++---- .../gguf/StreamingDequantPolicyParityTest.kt | 27 ++--- .../io/gguf/WeightFormLoaderParityTest.kt | 81 +-------------- .../sk/ainet/io/gguf/WeightOrientationTest.kt | 15 +-- .../sk/ainet/lang/memory/plan/DeviceFit.kt | 4 +- .../ainet/lang/memory/plan/PlannerProfile.kt | 2 +- 21 files changed, 87 insertions(+), 345 deletions(-) delete mode 100644 skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/QuantPolicy.kt delete mode 100644 skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/StagingPolicy.kt delete mode 100644 skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/WeightOrientation.kt diff --git a/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/PackedWeights.kt b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/PackedWeights.kt index 178a2722e..aa33f16dd 100644 --- a/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/PackedWeights.kt +++ b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/PackedWeights.kt @@ -115,7 +115,7 @@ public object PackedWeights { require(inputAligned || !rowsAligned) { "weight [$rows, $inputDim] looks like [in, out]: ${encoding.name} tiles the *input* dimension in " + "blocks of $blockSize, and $inputDim is not a multiple of it while $rows is. A GGUF's ne order " + - "produces exactly this — load with WeightOrientation.OUT_IN, or transpose the label before " + + "produces exactly this — load with WeightShapeOrientation.OUT_IN, or transpose the label before " + "relayouting (#973, the Packed weight layout page in the docs site (explanation/packed-weight-layout))." } require(inputAligned) { diff --git a/skainet-backends/skainet-backend-api/src/commonTest/kotlin/sk/ainet/backend/api/kernel/PackedWeightsTest.kt b/skainet-backends/skainet-backend-api/src/commonTest/kotlin/sk/ainet/backend/api/kernel/PackedWeightsTest.kt index 8952612c1..8b9b7bcf0 100644 --- a/skainet-backends/skainet-backend-api/src/commonTest/kotlin/sk/ainet/backend/api/kernel/PackedWeightsTest.kt +++ b/skainet-backends/skainet-backend-api/src/commonTest/kotlin/sk/ainet/backend/api/kernel/PackedWeightsTest.kt @@ -116,7 +116,7 @@ class PackedWeightsTest { PackedWeights.requireOutIn(rows = 128, inputDim = 3, encoding = TensorEncoding.Q8_0) } assertTrue(failure.message!!.contains("looks like [in, out]"), failure.message!!) - assertTrue(failure.message!!.contains("WeightOrientation.OUT_IN"), "and says how to fix it") + assertTrue(failure.message!!.contains("WeightShapeOrientation.OUT_IN"), "and says how to fix it") PackedWeights.requireOutIn(rows = 3, inputDim = 128, encoding = TensorEncoding.Q8_0) } diff --git a/skainet-io/skainet-io-core/src/androidMain/kotlin/sk/ainet/io/AndroidRandomAccessSource.kt b/skainet-io/skainet-io-core/src/androidMain/kotlin/sk/ainet/io/AndroidRandomAccessSource.kt index 25f9a2ac4..2f060dd16 100644 --- a/skainet-io/skainet-io-core/src/androidMain/kotlin/sk/ainet/io/AndroidRandomAccessSource.kt +++ b/skainet-io/skainet-io-core/src/androidMain/kotlin/sk/ainet/io/AndroidRandomAccessSource.kt @@ -27,7 +27,7 @@ public class AndroidRandomAccessSource private constructor( private val channel: FileChannel, private val raf: RandomAccessFile, override val size: Long, - /** The file these bytes come from — what `StagingPolicy.MAPPED` maps (#1037). */ + /** The file these bytes come from — what `WeightResidency.MAPPED` maps (#1037, #1159). */ override val filePath: String? = null, ) : RandomAccessSource { diff --git a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/RandomAccessSource.kt b/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/RandomAccessSource.kt index 24b0a3191..d16541697 100644 --- a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/RandomAccessSource.kt +++ b/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/RandomAccessSource.kt @@ -62,7 +62,7 @@ public interface RandomAccessSource : AutoCloseable { * The path these bytes came from, when they came from a file — `null` for a Blob, a network * stream or an in-memory source. * - * This is what lets a loader honour `StagingPolicy.MAPPED` (#1037): the same source that reads + * This is what lets a loader honour `WeightResidency.MAPPED` (#1037, #1159): the same source that reads * the header positionally can name the file to map for the tensor payloads. Defaulted, so no * existing implementation has to change. */ diff --git a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/QuantPolicy.kt b/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/QuantPolicy.kt deleted file mode 100644 index 55f669cb2..000000000 --- a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/QuantPolicy.kt +++ /dev/null @@ -1,30 +0,0 @@ -package sk.ainet.io.model - -/** - * Controls how quantized tensors are handled during weight loading. - * - * Shared across all weight loaders (LLaMA, Gemma, etc.). - */ -@Deprecated( - "One of the three axes WeightForm replaces (#1109): this is the encoding axis. No ReplaceWith, " + - "because EncodingRequest is not a drop-in — NATIVE_OPTIMIZED becomes KeepAsStored, " + - "DEQUANTIZE_TO_FP32 becomes DequantizeTo(FP32), and RAW_BYTES has no counterpart because " + - "no loader ever supported it. A wrong ReplaceWith would be worse than none.", - level = DeprecationLevel.WARNING, -) -public enum class QuantPolicy { - /** Keep quantized payloads as raw bytes (Int8 tensor) with quantized shape. */ - RAW_BYTES, - - /** Dequantize to FP32 on load. */ - DEQUANTIZE_TO_FP32, - - /** - * Mixed mode: dequantize F32/F16/BF16 tensors to FP32, but keep quantized - * weight tensors (Q4_0, Q8_0, etc.) as raw bytes for native kernel consumption. - * - * This allows loading with dtype=FP32 while preserving quantized weights - * for platform-specific optimized kernels (e.g. MemorySegment-backed SIMD). - */ - NATIVE_OPTIMIZED, -} diff --git a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/StagingPolicy.kt b/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/StagingPolicy.kt deleted file mode 100644 index 5df2362ed..000000000 --- a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/StagingPolicy.kt +++ /dev/null @@ -1,33 +0,0 @@ -package sk.ainet.io.model - -/** - * Where a loader puts tensor bytes on their way from the file into a tensor (SKEEP-003 §7, #1037). - * - * Orthogonal to [QuantPolicy], which decides *what* the values are: staging decides *where the - * bytes live*. The two are the axes of one loader — `quantPolicy × staging` — instead of the - * separate code paths ("streaming loader" vs "mapped weights helper") they used to be. - */ -@Deprecated( - "One of the three axes WeightForm replaces (#1109): this is the residency axis. Same two " + - "values, resolved from the device's PlannerProfile rather than set by the caller.", - ReplaceWith( - "WeightResidency", - "sk.ainet.lang.memory.plan.WeightResidency", - ), - DeprecationLevel.WARNING, -) -public enum class StagingPolicy { - /** Read tensor bytes onto the heap. The historical behaviour, and the only option in a browser. */ - HEAP, - - /** - * Map the file and serve tensors from file-backed pages: dense FP32 tensors become zero-heap - * views the OS pages in on demand and evicts under pressure — the difference between fitting a - * model on a 2 GB device and not (#921, #922). - * - * Falls back to [HEAP] when the platform cannot map (JS, Wasm), when the source is not a file, - * or for tensor types whose kernels still consume heap `ByteArray`s (every packed format, until - * the packed kernels take views — #973). - */ - MAPPED, -} diff --git a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/WeightOrientation.kt b/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/WeightOrientation.kt deleted file mode 100644 index c13d5f7f9..000000000 --- a/skainet-io/skainet-io-core/src/commonMain/kotlin/sk/ainet/io/model/WeightOrientation.kt +++ /dev/null @@ -1,38 +0,0 @@ -package sk.ainet.io.model - -/** - * Which way round a loaded 2-D weight's shape is (#973 census contradiction #6; #1098). - * - * GGUF stores dimensions in `ne` order — fastest-varying first — so a weight the rest of the world - * calls `[out, in]` is written as `ne = [in, out]`. The bytes are the same either way: row-major - * with the input dimension fastest, which *is* `[out, in]` row-major. Only the label differs. - * - * That label matters, because everything downstream of the loader assumes `[out, in]`: the block - * grid of a packed weight is `out × blocksPerRow`, so a relayout driven by an `[in, out]` shape - * computes the wrong permutation — or refuses, when `out` is not a multiple of the block size. Both - * failures are in the census. - */ -@Deprecated( - "One of the three axes WeightForm replaces (#1109): this is the shape axis. Same two values, " + - "asked as part of one resolved decision instead of a separate flag.", - ReplaceWith( - "WeightShapeOrientation", - "sk.ainet.lang.memory.plan.WeightShapeOrientation", - ), - DeprecationLevel.WARNING, -) -public enum class WeightOrientation { - /** - * The file's own order, unreversed — GGUF `ne`, so `[in, out]` for a 2-D weight. What the - * streaming loader has always produced, and the default, because changing it changes every - * consumer's idea of a tensor's shape. - */ - AS_STORED, - - /** - * Logical `[out, in]`: the convention the engine, HF checkpoints and every kernel assume, and - * the one the block relayout needs. Reverses a 2-D weight's dimensions at the load boundary; - * the bytes are untouched, because they already are `[out, in]` row-major. - */ - OUT_IN, -} diff --git a/skainet-io/skainet-io-core/src/jvmMain/kotlin/sk/ainet/io/JvmRandomAccessSource.kt b/skainet-io/skainet-io-core/src/jvmMain/kotlin/sk/ainet/io/JvmRandomAccessSource.kt index 85d56aebd..5322513a8 100644 --- a/skainet-io/skainet-io-core/src/jvmMain/kotlin/sk/ainet/io/JvmRandomAccessSource.kt +++ b/skainet-io/skainet-io-core/src/jvmMain/kotlin/sk/ainet/io/JvmRandomAccessSource.kt @@ -23,7 +23,7 @@ public class JvmRandomAccessSource private constructor( private val channel: FileChannel, private val raf: RandomAccessFile, override val size: Long, - /** The file these bytes come from — what `StagingPolicy.MAPPED` maps (#1037). */ + /** The file these bytes come from — what `WeightResidency.MAPPED` maps (#1037, #1159). */ override val filePath: String? = null, ) : RandomAccessSource { diff --git a/skainet-io/skainet-io-core/src/native64Main/kotlin/sk/ainet/io/PosixPreadRandomAccessSource.kt b/skainet-io/skainet-io-core/src/native64Main/kotlin/sk/ainet/io/PosixPreadRandomAccessSource.kt index 34aae21a5..c924bb8bf 100644 --- a/skainet-io/skainet-io-core/src/native64Main/kotlin/sk/ainet/io/PosixPreadRandomAccessSource.kt +++ b/skainet-io/skainet-io-core/src/native64Main/kotlin/sk/ainet/io/PosixPreadRandomAccessSource.kt @@ -31,7 +31,7 @@ import platform.posix.strerror public class PosixPreadRandomAccessSource private constructor( private val fd: Int, override val size: Long, - /** The file these bytes come from — what `StagingPolicy.MAPPED` would map (#1037). */ + /** The file these bytes come from — what `WeightResidency.MAPPED` would map (#1037, #1159). */ override val filePath: String? = null, ) : RandomAccessSource { diff --git a/skainet-io/skainet-io-gguf/src/androidHostTest/kotlin/sk/ainet/io/gguf/AndroidGgufLoadingHostTest.kt b/skainet-io/skainet-io-gguf/src/androidHostTest/kotlin/sk/ainet/io/gguf/AndroidGgufLoadingHostTest.kt index 1e64d5319..11d56a8ec 100644 --- a/skainet-io/skainet-io-gguf/src/androidHostTest/kotlin/sk/ainet/io/gguf/AndroidGgufLoadingHostTest.kt +++ b/skainet-io/skainet-io-gguf/src/androidHostTest/kotlin/sk/ainet/io/gguf/AndroidGgufLoadingHostTest.kt @@ -2,8 +2,7 @@ package sk.ainet.io.gguf import kotlinx.coroutines.runBlocking import sk.ainet.context.DefaultDataExecutionContext -import sk.ainet.io.model.QuantPolicy -import sk.ainet.io.model.StagingPolicy +import sk.ainet.lang.memory.plan.WeightForm import sk.ainet.lang.memory.ExperimentalMemoryApi import sk.ainet.lang.memory.plan.DeviceMemory import sk.ainet.lang.memory.plan.PlannerProfile @@ -87,7 +86,7 @@ class AndroidGgufLoadingHostTest { "dense F32 must come from file-backed pages on Android, got ${mapped.getValue("w_f32").data::class.simpleName}", ) // and the heap path is still reachable, producing the same numbers - val onHeap = load(AndroidGguf.loader(f.absolutePath, staging = StagingPolicy.HEAP)) + val onHeap = load(AndroidGguf.loader(f.absolutePath, weightForm = WeightForm())) assertTrue(onHeap.getValue("w_f32").data is FloatArrayTensorData<*>) assertContentEquals( onHeap.getValue("w_f32").data.copyToFloatArray(), diff --git a/skainet-io/skainet-io-gguf/src/androidMain/kotlin/sk/ainet/io/gguf/AndroidGgufLoading.kt b/skainet-io/skainet-io-gguf/src/androidMain/kotlin/sk/ainet/io/gguf/AndroidGgufLoading.kt index dcf2c9ef3..9782b1bbd 100644 --- a/skainet-io/skainet-io-gguf/src/androidMain/kotlin/sk/ainet/io/gguf/AndroidGgufLoading.kt +++ b/skainet-io/skainet-io-gguf/src/androidMain/kotlin/sk/ainet/io/gguf/AndroidGgufLoading.kt @@ -4,8 +4,8 @@ import android.app.ActivityManager import android.content.Context import sk.ainet.io.RandomAccessSource import sk.ainet.io.openRandomAccessSource -import sk.ainet.io.model.QuantPolicy -import sk.ainet.io.model.StagingPolicy +import sk.ainet.lang.memory.plan.WeightForm +import sk.ainet.lang.memory.plan.WeightResidency import sk.ainet.lang.memory.ExperimentalMemoryApi import sk.ainet.lang.memory.plan.Budget import sk.ainet.lang.memory.plan.DeviceFit @@ -22,7 +22,7 @@ import sk.ainet.lang.memory.plan.fitOn * * The managed heap is the binding constraint on a phone — hard-capped at 256 MB (512 MB with * `largeHeap`) no matter how much RAM the device has — so the Android configuration of the loader - * is `staging = MAPPED`: weights come from file-backed pages the OS pages in on demand and evicts + * asks for `WeightResidency.MAPPED`: weights come from file-backed pages the OS pages in on demand and evicts * under pressure, and never count against the cap. * * What is *not* solved yet: packed (quantized) tensors still arrive as heap arrays, because the @@ -36,20 +36,18 @@ public object AndroidGguf { /** * The loader Android should use: positional reads for the metadata, mapped pages for tensor - * payloads. [quantPolicy] is the caller's choice as usual; [staging] defaults to - * [StagingPolicy.MAPPED] and is a parameter only so a test or a benchmark can ask for the - * heap path explicitly. + * payloads. [weightForm] defaults to mapped residency — the managed heap is the binding + * constraint on a phone — and is a parameter only so a test or a benchmark can ask for the + * heap path (or a dequantizing form) explicitly. */ public fun loader( filePath: String, - quantPolicy: QuantPolicy = QuantPolicy.NATIVE_OPTIMIZED, - staging: StagingPolicy = StagingPolicy.MAPPED, + weightForm: WeightForm = WeightForm(residency = WeightResidency.MAPPED), onProgress: (current: Long, total: Long, message: String?) -> Unit = { _, _, _ -> }, ): StreamingGgufParametersLoader = StreamingGgufParametersLoader( sourceProvider = { openSource(filePath) }, onProgress = onProgress, - quantPolicy = quantPolicy, - staging = staging, + weightForm = weightForm, ) /** @@ -88,7 +86,7 @@ public object AndroidGguf { * Will this model load on this device? Checks the header-derived plan against both pools — * managed heap and physical RAM — before a byte of payload is read. * - * @param weightsMapped whether the load will use [StagingPolicy.MAPPED] (what [loader] does) + * @param weightsMapped whether the load maps the file (`WeightResidency.MAPPED`, what [loader] does) */ public fun fits(context: Context, filePath: String, ctx: Int, weightsMapped: Boolean = true): DeviceFit = fits(deviceMemory(context), filePath, ctx, weightsMapped) diff --git a/skainet-io/skainet-io-gguf/src/commonMain/kotlin/sk/ainet/io/gguf/StreamingGgufParametersLoader.kt b/skainet-io/skainet-io-gguf/src/commonMain/kotlin/sk/ainet/io/gguf/StreamingGgufParametersLoader.kt index 7ec59daf9..cd8ffef04 100644 --- a/skainet-io/skainet-io-gguf/src/commonMain/kotlin/sk/ainet/io/gguf/StreamingGgufParametersLoader.kt +++ b/skainet-io/skainet-io-gguf/src/commonMain/kotlin/sk/ainet/io/gguf/StreamingGgufParametersLoader.kt @@ -15,9 +15,6 @@ import sk.ainet.lang.memory.plan.WeightByteOrder import sk.ainet.lang.memory.plan.WeightForm import sk.ainet.lang.memory.plan.WeightResidency import sk.ainet.lang.memory.plan.WeightShapeOrientation -import sk.ainet.io.model.QuantPolicy -import sk.ainet.io.model.StagingPolicy -import sk.ainet.io.model.WeightOrientation import sk.ainet.io.openMappedFile import sk.ainet.lang.tensor.Shape import sk.ainet.lang.tensor.Tensor @@ -71,64 +68,23 @@ public class StreamingGgufParametersLoader( */ private val keepBf16Native: Boolean = false, /** - * How quantized tensors are materialized (#782). + * The form weights should take in memory — encoding × byte order × shape × residency as one + * decision (#1109, #1115, #1159). * - * - [QuantPolicy.NATIVE_OPTIMIZED] (default — the loader's historical behavior): - * quantized tensors are delivered as packed block [TensorData]; F32/F16/BF16 - * are dense FP32 (subject to [keepF16Native]/[keepBf16Native]). - * - [QuantPolicy.DEQUANTIZE_TO_FP32]: quantized tensors are dequantized - * *streaming, per tensor, block-by-block into the destination `FloatArray`*, - * which is then wrapped zero-copy. Peak transient memory per tensor is the - * packed source bytes only — there is no full-size intermediate copy. - * - [QuantPolicy.RAW_BYTES] is not supported by this loader (it preserves - * packed block storage instead) and is rejected eagerly. - */ - private val quantPolicy: QuantPolicy = QuantPolicy.NATIVE_OPTIMIZED, - /** - * Where tensor bytes live on their way into a tensor (#1037). [QuantPolicy] says *what* the - * values are; this says *where the bytes are* — the two axes of one loader. - * - * - [StagingPolicy.HEAP] (default — today's behaviour): every tensor is read onto the heap. - * - [StagingPolicy.MAPPED]: the file is mapped once and dense F32 tensors are served as - * zero-heap views over its pages (what `MappedGgufWeights` did as a separate helper). Packed - * tensors still come through as heap arrays, because that is what their kernels take until - * #973; and the whole thing falls back to heap staging when the platform cannot map or the - * source is not a file, so a browser build behaves exactly as before. - */ - private val staging: StagingPolicy = StagingPolicy.HEAP, - /** - * Which way round a 2-D weight's shape comes out (#1098, #973 census contradiction #6). - * - * GGUF writes dimensions in `ne` order, so a weight the rest of the engine calls `[out, in]` - * arrives labelled `[in, out]` — while its *bytes* are already `[out, in]` row-major. Nothing - * about the data changes here; only the label. [WeightOrientation.OUT_IN] fixes the label, - * which is what the packed block relayout needs to compute the right permutation. - * - * Defaults to [WeightOrientation.AS_STORED], today's behaviour, because reversing shapes - * changes what every consumer sees. New code should ask for `OUT_IN`. - */ - private val weightOrientation: WeightOrientation = WeightOrientation.AS_STORED, - /** - * The form weights should take in memory, as one decision instead of three (#1109, #1115). - * - * [quantPolicy], [staging] and [weightOrientation] are the same three axes asked separately, - * and asked of the *caller* — who has to answer for a device they may not be building for. - * `WeightFormResolver.resolve(stored, profile, capabilities)` answers instead, from what the - * file holds, what the device is, and what the backend's kernels can feed. - * - * `null` (the default) means "use the three parameters", so every existing caller is - * byte-identical. Passing a form while also setting any of the three is rejected rather than - * silently resolved, so nobody loses a setting they thought they had. + * `null` (the default) means [WeightForm.AS_STORED_ON_HEAP]: the file's bytes, its order, on + * the heap — the loader's historical behaviour. Callers who know their device pass a form; + * callers who don't let `WeightFormResolver`/[ResolvedGguf] resolve one from what the file + * holds, what the device is, and what the backend's kernels can feed. */ private val weightForm: WeightForm? = null, /** * Per-tensor forms — the *user wins* channel (#1144). * - * Precedence, explicit and documented: **this function > [weightForm] > the three legacy - * parameters > nothing**. Whatever you return for a tensor outranks every resolver and every - * profile — including the deliberately blunt `WeightForm(DequantizeTo(FP32), residency = HEAP)` + * Precedence, explicit and documented: **this function > [weightForm] > the as-stored-on-heap + * default**. Whatever you return for a tensor outranks every resolver and every profile — + * including the deliberately blunt `WeightForm(DequantizeTo(FP32), residency = HEAP)` * ("everything dense, on the managed heap"). Return `null` for tensors you have no opinion on; - * they fall through to [weightForm] (or the legacy axes). + * they fall through to [weightForm]. * * The intended producer is `WeightFormResolver`/`resolveWeightForms` via [ResolvedGguf], which * resolves per tensor from the file × profile × kernel capability — but the contract is the @@ -181,22 +137,8 @@ public class StreamingGgufParametersLoader( /** The dense FP32 size of [elements] — what every widening in this loader converts to. */ private fun denseFp32Bytes(elements: Long): Long = elements * 4 - /** The three axes as one value: [weightForm] if given, otherwise what the three parameters say. */ - private val form: WeightForm = weightForm ?: WeightForm( - encoding = when (quantPolicy) { - QuantPolicy.DEQUANTIZE_TO_FP32 -> EncodingRequest.DequantizeTo(FP32) - else -> EncodingRequest.KeepAsStored - }, - order = WeightByteOrder.AS_STORED, - shape = when (weightOrientation) { - WeightOrientation.OUT_IN -> WeightShapeOrientation.OUT_IN - else -> WeightShapeOrientation.AS_STORED - }, - residency = when (staging) { - StagingPolicy.MAPPED -> WeightResidency.MAPPED - else -> WeightResidency.HEAP - }, - ) + /** The uniform form: [weightForm] if given, else the historical as-stored-on-heap default. */ + private val form: WeightForm = weightForm ?: WeightForm.AS_STORED_ON_HEAP /** * The form [tensorName] loads under — the precedence order of [weightFormFor], validated the @@ -209,22 +151,6 @@ public class StreamingGgufParametersLoader( } init { - require(quantPolicy != QuantPolicy.RAW_BYTES) { - "StreamingGgufParametersLoader does not support QuantPolicy.RAW_BYTES — quantized " + - "tensors are preserved as packed block TensorData (NATIVE_OPTIMIZED) or " + - "dequantized to dense FP32 (DEQUANTIZE_TO_FP32)." - } - if (weightForm != null || weightFormFor != null) { - require( - quantPolicy == QuantPolicy.NATIVE_OPTIMIZED && - staging == StagingPolicy.HEAP && - weightOrientation == WeightOrientation.AS_STORED, - ) { - "a WeightForm (or weightFormFor) and the quantPolicy/staging/weightOrientation " + - "parameters were both set. They are the same three axes, so one of them would " + - "have been silently ignored; pass only the form." - } - } validateForm(form, "weightForm") } diff --git a/skainet-io/skainet-io-gguf/src/jvmAndroidMain/kotlin/sk/ainet/io/gguf/MappedGgufWeights.kt b/skainet-io/skainet-io-gguf/src/jvmAndroidMain/kotlin/sk/ainet/io/gguf/MappedGgufWeights.kt index c7eb8cc74..fb08885e5 100644 --- a/skainet-io/skainet-io-gguf/src/jvmAndroidMain/kotlin/sk/ainet/io/gguf/MappedGgufWeights.kt +++ b/skainet-io/skainet-io-gguf/src/jvmAndroidMain/kotlin/sk/ainet/io/gguf/MappedGgufWeights.kt @@ -11,8 +11,8 @@ import java.nio.channels.FileChannel /** * Memory-mapped GGUF weight access for the JVM and Android (#921). * - * Since #1037 this is the **per-tensor** face of `StagingPolicy.MAPPED`: to load a whole model - * from mapped pages, pass `staging = StagingPolicy.MAPPED` to [StreamingGgufParametersLoader] and + * Since #1037 this is the **per-tensor** face of `WeightResidency.MAPPED`: to load a whole model + * from mapped pages, pass `WeightForm(residency = WeightResidency.MAPPED)` to [StreamingGgufParametersLoader] and * get the same file-backed tensors through the ordinary loader. This class stays for callers that * want to reach individual tensors (or their `TensorStorage` descriptors) without loading a model. * diff --git a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/DequantHeapUsageTest.kt b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/DequantHeapUsageTest.kt index 478f17e7b..4e4c9d407 100644 --- a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/DequantHeapUsageTest.kt +++ b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/DequantHeapUsageTest.kt @@ -5,7 +5,8 @@ import kotlinx.io.buffered import sk.ainet.context.DefaultDataExecutionContext import sk.ainet.io.JvmRandomAccessSource import sk.ainet.io.gguf.dequant.DequantOps -import sk.ainet.io.model.QuantPolicy +import sk.ainet.lang.memory.plan.EncodingRequest +import sk.ainet.lang.memory.plan.WeightForm import sk.ainet.lang.tensor.Shape import sk.ainet.lang.tensor.Tensor import sk.ainet.lang.types.FP32 @@ -57,11 +58,11 @@ class DequantHeapUsageTest { val warmup = SyntheticGguf.write( SyntheticGguf.tensor("w.q4k", GGMLQuantizationType.Q4_K, elements = 512), ) - loadAll(warmup, QuantPolicy.DEQUANTIZE_TO_FP32) + loadAll(warmup, WeightForm(encoding = EncodingRequest.DequantizeTo(FP32))) warmup.delete() val before = allocatedBytes() - val loaded = loadAll(file, QuantPolicy.DEQUANTIZE_TO_FP32) + val loaded = loadAll(file, WeightForm(encoding = EncodingRequest.DequantizeTo(FP32))) val allocated = allocatedBytes() - before // Keep the result alive so the resident set is real. @@ -190,13 +191,13 @@ class DequantHeapUsageTest { private fun sourceOf(file: File): kotlinx.io.Source = kotlinx.io.files.SystemFileSystem.source(kotlinx.io.files.Path(file.absolutePath)).buffered() - private fun loadAll(file: File, policy: QuantPolicy): Map> { + private fun loadAll(file: File, form: WeightForm): Map> { val ctx = DefaultDataExecutionContext() val loaded = mutableMapOf>() runBlocking { StreamingGgufParametersLoader( sourceProvider = { JvmRandomAccessSource.open(file) }, - quantPolicy = policy, + weightForm = form, ).load(ctx, FP32::class) { name, tensor -> loaded[name] = tensor } } return loaded diff --git a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/GgufTernaryLoadTest.kt b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/GgufTernaryLoadTest.kt index 18c946f3d..5e8692fe7 100644 --- a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/GgufTernaryLoadTest.kt +++ b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/GgufTernaryLoadTest.kt @@ -3,7 +3,8 @@ package sk.ainet.io.gguf import kotlinx.coroutines.runBlocking import sk.ainet.context.DefaultDataExecutionContext import sk.ainet.io.JvmRandomAccessSource -import sk.ainet.io.model.QuantPolicy +import sk.ainet.lang.memory.plan.EncodingRequest +import sk.ainet.lang.memory.plan.WeightForm import sk.ainet.lang.memory.ExperimentalMemoryApi import sk.ainet.lang.memory.TernaryCodec import sk.ainet.lang.memory.blockSpec @@ -33,7 +34,7 @@ class GgufTernaryLoadTest { val (tq2, _, tq2Values) = SyntheticGguf.ternary("w_tq2", GGMLQuantizationType.TQ2_0, elements = 512) val file = SyntheticGguf.write(tq1, tq2) try { - val loaded = load(file, QuantPolicy.DEQUANTIZE_TO_FP32) + val loaded = load(file, WeightForm(encoding = EncodingRequest.DequantizeTo(FP32))) assertEquals(setOf("w_tq1", "w_tq2"), loaded.keys) for ((name, expected) in listOf("w_tq1" to tq1Values, "w_tq2" to tq2Values)) { val data = loaded.getValue(name).data @@ -72,7 +73,7 @@ class GgufTernaryLoadTest { val (tq2, bytes, values) = SyntheticGguf.ternary("w", GGMLQuantizationType.TQ2_0, elements = 256 * 3) val file = SyntheticGguf.write(tq2) try { - val loaded = load(file, QuantPolicy.DEQUANTIZE_TO_FP32).getValue("w").data + val loaded = load(file, WeightForm(encoding = EncodingRequest.DequantizeTo(FP32))).getValue("w").data val actual = (loaded as FloatArrayTensorData<*>).buffer.copyOf(values.size) assertContentEquals(TernaryCodec.decodeTq2_0(bytes, values.size), actual, "loader vs reference codec") assertContentEquals(values, actual) @@ -81,13 +82,13 @@ class GgufTernaryLoadTest { } } - private fun load(file: File, policy: QuantPolicy): Map> { + private fun load(file: File, form: WeightForm): Map> { val ctx = DefaultDataExecutionContext() val loaded = mutableMapOf>() runBlocking { StreamingGgufParametersLoader( sourceProvider = { JvmRandomAccessSource.open(file) }, - quantPolicy = policy, + weightForm = form, ).load(ctx, FP32::class) { name, tensor -> loaded[name] = tensor } } return loaded diff --git a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StagingPolicyParityTest.kt b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StagingPolicyParityTest.kt index c089038ee..abbfcaf08 100644 --- a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StagingPolicyParityTest.kt +++ b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StagingPolicyParityTest.kt @@ -3,8 +3,9 @@ package sk.ainet.io.gguf import kotlinx.coroutines.runBlocking import sk.ainet.context.DefaultDataExecutionContext import sk.ainet.io.JvmRandomAccessSource -import sk.ainet.io.model.QuantPolicy -import sk.ainet.io.model.StagingPolicy +import sk.ainet.lang.memory.plan.EncodingRequest +import sk.ainet.lang.memory.plan.WeightForm +import sk.ainet.lang.memory.plan.WeightResidency import sk.ainet.lang.tensor.Tensor import sk.ainet.lang.tensor.data.FloatArrayTensorData import sk.ainet.lang.tensor.data.MmapFloatTensorData @@ -16,8 +17,8 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue /** - * #1037: the streaming-dequant path (#782) and the mapped path are two configurations of **one** - * loader — `quantPolicy × staging` — not two code paths that can drift. + * #1037/#1159: the streaming-dequant path (#782) and the mapped path are two configurations of + * **one** loader — `WeightForm.encoding × WeightForm.residency` — not two code paths that can drift. * * Every combination must produce the same numbers; only *where the bytes live* differs. That is * the whole claim, so it is asserted directly: four loads of the same file, compared element by @@ -32,37 +33,39 @@ class StagingPolicyParityTest { SyntheticGguf.tensor("w_f16", GGMLQuantizationType.F16, elements = 1024), ) - private fun load(f: File, quant: QuantPolicy, staging: StagingPolicy): Map> { + private fun load(f: File, form: WeightForm): Map> { val ctx = DefaultDataExecutionContext() val loaded = LinkedHashMap>() runBlocking { StreamingGgufParametersLoader( sourceProvider = { JvmRandomAccessSource.open(f) }, - quantPolicy = quant, - staging = staging, + weightForm = form, ).load(ctx, FP32::class) { name, tensor -> loaded[name] = tensor } } return loaded } + private val keep = EncodingRequest.KeepAsStored + private val dequant = EncodingRequest.DequantizeTo(FP32) + private fun values(t: Tensor): FloatArray = t.data.copyToFloatArray() @Test fun `staging never changes the numbers, for either quant policy`() { val f = file() try { - // The claim of #1037: staging decides *where the bytes live*, quantPolicy decides *what - // the values are*. So HEAP and MAPPED must agree element for element under each policy. + // The claim of #1037: residency decides *where the bytes live*, encoding decides *what + // the values are*. So HEAP and MAPPED must agree element for element under each encoding. // (Across policies they legitimately differ: a packed tensor's own `get` returns codes, // which is what StreamingDequantPolicyParityTest covers.) - for (quant in listOf(QuantPolicy.NATIVE_OPTIMIZED, QuantPolicy.DEQUANTIZE_TO_FP32)) { - val heap = load(f, quant, StagingPolicy.HEAP) - val mapped = load(f, quant, StagingPolicy.MAPPED) - assertEquals(heap.keys, mapped.keys, "$quant: tensor set") + for (encoding in listOf(keep, dequant)) { + val heap = load(f, WeightForm(encoding = encoding)) + val mapped = load(f, WeightForm(encoding = encoding, residency = WeightResidency.MAPPED)) + assertEquals(heap.keys, mapped.keys, "$encoding: tensor set") assertTrue(heap.isNotEmpty()) for ((name, tensor) in mapped) { - assertContentEquals(values(heap.getValue(name)), values(tensor), "$quant: values of $name") - assertEquals(heap.getValue(name).shape, tensor.shape, "$quant: shape of $name") + assertContentEquals(values(heap.getValue(name)), values(tensor), "$encoding: values of $name") + assertEquals(heap.getValue(name).shape, tensor.shape, "$encoding: shape of $name") } } } finally { @@ -74,8 +77,8 @@ class StagingPolicyParityTest { fun `mapped staging serves dense F32 tensors from file-backed pages`() { val f = file() try { - val heap = load(f, QuantPolicy.NATIVE_OPTIMIZED, StagingPolicy.HEAP) - val mapped = load(f, QuantPolicy.NATIVE_OPTIMIZED, StagingPolicy.MAPPED) + val heap = load(f, WeightForm()) + val mapped = load(f, WeightForm(residency = WeightResidency.MAPPED)) assertTrue( heap.getValue("w_f32").data is FloatArrayTensorData<*>, @@ -106,12 +109,12 @@ class StagingPolicyParityTest { runBlocking { StreamingGgufParametersLoader( sourceProvider = { PathlessSource(JvmRandomAccessSource.open(f)) }, - staging = StagingPolicy.MAPPED, + weightForm = WeightForm(residency = WeightResidency.MAPPED), ).load(ctx, FP32::class) { name, tensor -> loaded[name] = tensor } } assertTrue(loaded.getValue("w_f32").data is FloatArrayTensorData<*>, "no path to map: stays on the heap") assertContentEquals( - values(load(f, QuantPolicy.NATIVE_OPTIMIZED, StagingPolicy.HEAP).getValue("w_f32")), + values(load(f, WeightForm()).getValue("w_f32")), values(loaded.getValue("w_f32")), ) } finally { diff --git a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StreamingDequantPolicyParityTest.kt b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StreamingDequantPolicyParityTest.kt index 7263c8910..049c4eade 100644 --- a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StreamingDequantPolicyParityTest.kt +++ b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/StreamingDequantPolicyParityTest.kt @@ -3,7 +3,8 @@ package sk.ainet.io.gguf import kotlinx.coroutines.runBlocking import sk.ainet.context.DefaultDataExecutionContext import sk.ainet.io.JvmRandomAccessSource -import sk.ainet.io.model.QuantPolicy +import sk.ainet.lang.memory.plan.EncodingRequest +import sk.ainet.lang.memory.plan.WeightForm import sk.ainet.lang.tensor.Tensor import sk.ainet.lang.tensor.data.FloatArrayTensorData import sk.ainet.lang.tensor.storage.PackedBlockStorage @@ -11,12 +12,11 @@ import sk.ainet.lang.types.FP32 import java.io.File import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertFailsWith import kotlin.test.assertTrue /** * Parity gate for the #782 fix: loading a GGUF with - * [QuantPolicy.DEQUANTIZE_TO_FP32] (streaming per-tensor dequant into the + * `EncodingRequest.DequantizeTo(FP32)` (streaming per-tensor dequant into the * destination array, wrapped zero-copy) must produce bit-identical values to * the packed-block path that the loader has always used. * @@ -41,8 +41,8 @@ class StreamingDequantPolicyParityTest { SyntheticGguf.tensor("w_f32", GGMLQuantizationType.F32, elements = 1024), ) try { - val packedLoad = load(file, QuantPolicy.NATIVE_OPTIMIZED) - val dequantLoad = load(file, QuantPolicy.DEQUANTIZE_TO_FP32) + val packedLoad = load(file, WeightForm()) + val dequantLoad = load(file, WeightForm(encoding = EncodingRequest.DequantizeTo(FP32))) assertEquals(packedLoad.keys, dequantLoad.keys) for ((name, dequantTensor) in dequantLoad) { @@ -92,31 +92,20 @@ class StreamingDequantPolicyParityTest { SyntheticGguf.tensor("w_q4k", GGMLQuantizationType.Q4_K, elements = 512), ) try { - val loaded = load(file, QuantPolicy.NATIVE_OPTIMIZED) + val loaded = load(file, WeightForm()) assertTrue(loaded.getValue("w_q4k").data is PackedBlockStorage) } finally { file.delete() } } - @Test - fun `RAW_BYTES policy is rejected eagerly`() { - val e = assertFailsWith { - StreamingGgufParametersLoader( - sourceProvider = { error("must not be opened") }, - quantPolicy = QuantPolicy.RAW_BYTES, - ) - } - assertTrue("RAW_BYTES" in (e.message ?: "")) - } - - private fun load(file: File, policy: QuantPolicy): Map> { + private fun load(file: File, form: WeightForm): Map> { val ctx = DefaultDataExecutionContext() val loaded = mutableMapOf>() runBlocking { StreamingGgufParametersLoader( sourceProvider = { JvmRandomAccessSource.open(file) }, - quantPolicy = policy, + weightForm = form, ).load(ctx, FP32::class) { name, tensor -> loaded[name] = tensor } } return loaded diff --git a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightFormLoaderParityTest.kt b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightFormLoaderParityTest.kt index f79490fbb..821a5ba34 100644 --- a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightFormLoaderParityTest.kt +++ b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightFormLoaderParityTest.kt @@ -3,9 +3,6 @@ package sk.ainet.io.gguf import kotlinx.coroutines.runBlocking import sk.ainet.context.DefaultDataExecutionContext import sk.ainet.io.JvmRandomAccessSource -import sk.ainet.io.model.QuantPolicy -import sk.ainet.io.model.StagingPolicy -import sk.ainet.io.model.WeightOrientation import sk.ainet.lang.memory.ExperimentalMemoryApi import sk.ainet.lang.memory.plan.EncodingRequest import sk.ainet.lang.memory.plan.WeightByteOrder @@ -22,15 +19,10 @@ import kotlin.test.assertFailsWith import kotlin.test.assertTrue /** - * #1115: the loader takes one `WeightForm` where it took three flags, and the change is a change of - * *spelling only*. - * - * That is the claim worth testing, because it is the one that can quietly be false. Every - * combination of the three deprecated parameters is loaded twice — once through them, once through - * the `WeightForm` they map to — and the two must agree on shapes and on every element. If the - * mapping is wrong anywhere, some cell of that product disagrees. + * #1115/#1159: `WeightForm` is the loader's whole configuration surface. The flags-vs-form parity + * matrix this file used to hold retired with the flags themselves; what remains pins the form + * axes the loader validates and honours. */ -@Suppress("DEPRECATION") @OptIn(ExperimentalMemoryApi::class) class WeightFormLoaderParityTest { @@ -54,60 +46,6 @@ class WeightFormLoaderParityTest { return loaded } - @Test - fun `every combination of the three flags loads identically through the form it maps to`() { - val f = file() - try { - for (quant in listOf(QuantPolicy.NATIVE_OPTIMIZED, QuantPolicy.DEQUANTIZE_TO_FP32)) { - for (staging in listOf(StagingPolicy.HEAP, StagingPolicy.MAPPED)) { - for (orientation in listOf(WeightOrientation.AS_STORED, WeightOrientation.OUT_IN)) { - val viaFlags = loadVia(f) { src -> - StreamingGgufParametersLoader( - sourceProvider = src, - quantPolicy = quant, - staging = staging, - weightOrientation = orientation, - ) - } - val viaForm = loadVia(f) { src -> - StreamingGgufParametersLoader( - sourceProvider = src, - weightForm = WeightForm( - encoding = when (quant) { - QuantPolicy.DEQUANTIZE_TO_FP32 -> EncodingRequest.DequantizeTo(FP32) - else -> EncodingRequest.KeepAsStored - }, - shape = when (orientation) { - WeightOrientation.OUT_IN -> WeightShapeOrientation.OUT_IN - else -> WeightShapeOrientation.AS_STORED - }, - residency = when (staging) { - StagingPolicy.MAPPED -> WeightResidency.MAPPED - else -> WeightResidency.HEAP - }, - ), - ) - } - - val label = "$quant/$staging/$orientation" - assertEquals(viaFlags.keys, viaForm.keys, "$label: different tensors came out") - for ((name, flagsTensor) in viaFlags) { - val formTensor = viaForm.getValue(name) - assertEquals(flagsTensor.shape, formTensor.shape, "$label: $name shape") - assertContentEquals( - flagsTensor.data.copyToFloatArray(), - formTensor.data.copyToFloatArray(), - "$label: $name values", - ) - } - } - } - } - } finally { - f.delete() - } - } - @Test fun `the default loader is the default form`() { val f = file() @@ -126,19 +64,6 @@ class WeightFormLoaderParityTest { } } - @Test - fun `setting both a form and a flag is refused rather than silently resolved`() { - // One of the two would have to lose, and a caller who set a flag believes it is in effect. - val failure = assertFailsWith { - StreamingGgufParametersLoader( - sourceProvider = { JvmRandomAccessSource.open(file()) }, - staging = StagingPolicy.MAPPED, - weightForm = WeightForm.AS_STORED_ON_HEAP, - ) - } - assertTrue(failure.message!!.contains("pass only the form"), failure.message!!) - } - @Test fun `KERNEL_FEED is accepted now that packed storage can declare its order`() { // This slice refused it: packed TensorData read packedData as canonical row-major, so diff --git a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightOrientationTest.kt b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightOrientationTest.kt index c38d83346..1adf006d4 100644 --- a/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightOrientationTest.kt +++ b/skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/WeightOrientationTest.kt @@ -3,7 +3,8 @@ package sk.ainet.io.gguf import kotlinx.coroutines.runBlocking import sk.ainet.context.DefaultDataExecutionContext import sk.ainet.io.JvmRandomAccessSource -import sk.ainet.io.model.WeightOrientation +import sk.ainet.lang.memory.plan.WeightForm +import sk.ainet.lang.memory.plan.WeightShapeOrientation import sk.ainet.lang.memory.ExperimentalMemoryApi import sk.ainet.lang.tensor.Shape import sk.ainet.lang.tensor.Tensor @@ -30,13 +31,13 @@ class WeightOrientationTest { SyntheticGguf.tensor("blk.0.attn_q.weight", GGMLQuantizationType.Q8_0, elements = 384), ) - private fun load(f: File, orientation: WeightOrientation): Map> { + private fun load(f: File, form: WeightForm): Map> { val ctx = DefaultDataExecutionContext() val out = LinkedHashMap>() runBlocking { StreamingGgufParametersLoader( sourceProvider = { JvmRandomAccessSource.open(f) }, - weightOrientation = orientation, + weightForm = form, ).load(ctx, FP32::class) { name, t -> out[name] = t } } return out @@ -47,8 +48,8 @@ class WeightOrientationTest { val f = SyntheticGguf.write(SyntheticGguf.tensor("w", GGMLQuantizationType.F32, elements = 12)) try { // a 1-D tensor has no orientation to get wrong, and nothing changes for it either way - assertEquals(Shape(12), load(f, WeightOrientation.AS_STORED).getValue("w").shape) - assertEquals(Shape(12), load(f, WeightOrientation.OUT_IN).getValue("w").shape) + assertEquals(Shape(12), load(f, WeightForm()).getValue("w").shape) + assertEquals(Shape(12), load(f, WeightForm(shape = WeightShapeOrientation.OUT_IN)).getValue("w").shape) } finally { f.delete() } @@ -58,8 +59,8 @@ class WeightOrientationTest { fun `OUT_IN reverses a 2-D weight's label and nothing else`() { val f = twoDimensionalFile() try { - val asStored = load(f, WeightOrientation.AS_STORED).getValue("w") - val outIn = load(f, WeightOrientation.OUT_IN).getValue("w") + val asStored = load(f, WeightForm()).getValue("w") + val outIn = load(f, WeightForm(shape = WeightShapeOrientation.OUT_IN)).getValue("w") assertEquals(Shape(64, 4), asStored.shape, "ne order: [in, out]") assertEquals(Shape(4, 64), outIn.shape, "logical order: [out, in]") assertContentEquals( diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/DeviceFit.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/DeviceFit.kt index 2f595c65b..e72c6427b 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/DeviceFit.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/DeviceFit.kt @@ -61,7 +61,7 @@ public data class PoolFit(val name: String, val neededBytes: Long, val budgetByt * A [MemoryPlan] checked against a real device, pool by pool (M2-A5): the answer to "will this * model load on this phone", and when it will not, which pool ran out and what to do about it. * - * @property weightsMapped whether the weights are loaded through `StagingPolicy.MAPPED`, i.e. from + * @property weightsMapped whether the weights are loaded through `WeightResidency.MAPPED`, i.e. from * file-backed pages that never count against the managed heap. */ @ExperimentalMemoryApi @@ -111,7 +111,7 @@ public data class DeviceFit( * under a 512 MB cap and unremarkable when mapped. Physical RAM carries everything, mapped pages * included: they are evictable, not free. * - * @param weightsMapped weights come from file-backed pages (`StagingPolicy.MAPPED`) + * @param weightsMapped weights come from file-backed pages (`WeightResidency.MAPPED`) */ @ExperimentalMemoryApi public fun MemoryPlan.fitOn(device: DeviceMemory, weightsMapped: Boolean): DeviceFit { diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/PlannerProfile.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/PlannerProfile.kt index e77d26be0..f62a4c001 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/PlannerProfile.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/memory/plan/PlannerProfile.kt @@ -21,7 +21,7 @@ import sk.ainet.lang.tensor.storage.MemoryDomain * @property dequantWarnFraction dispatcher-inserted dequantization above this share of the bytes a * decode step reads is worth a warning — it means a kernel is missing for the format on disk * @property strict turn those warnings into failures - * @property weightsMapped weights are expected to load through `StagingPolicy.MAPPED` + * @property weightsMapped weights are expected to load through `WeightResidency.MAPPED` */ @ExperimentalMemoryApi public data class PlannerProfile(