BF16 dispatch chain (Phase 1/3): add Bf16TensorData + Bf16DenseTensorData - #610
Merged
Merged
Conversation
Phase 1 of the three-phase BF16 dispatch chain. Foundation only — no
loader or dispatch changes here; those follow in Phase 2 / Phase 3.
`Bf16TensorData : TensorData<DType, Float>` is the recognition surface
for "this weight is packed BF16 bytes" so the upcoming dispatch in
`DefaultCpuOpsJvm.chooseQuantizedMatmul` (Phase 3) can route via the
`Bf16MatmulKernel` SPI without dequant-to-FP32 at load.
Surface area:
- `Bf16TensorData` interface (commonMain) — exposes `packedData:
ByteArray` (2 bytes per element, little-endian) for zero-copy
hand-off to SIMD matmul kernels, plus `Bf16TensorData.Companion
.floatToBf16Bits` / `bf16BitsToFloat` static helpers.
- `Bf16DenseTensorData` concrete impl — backed by a packed ByteArray.
`get(*indices): Float` decodes BF16 → FP32 on read; `set` truncates
FP32 → BF16 (lossy by construction). Bulk `copyToFloatArray()` for
consumers that just want all values.
- `Bf16DenseTensorData.fromFloatArray(shape, FloatArray)` factory for
tests and offline round-tripping.
- `Bf16TensorData.toFloatArray()` extension for dequant fallback.
11 unit tests in commonTest cover: round-trip within BF16 precision
(1e-2 abs), raw byte-order check (FP32 1.0 = BF16 0x3F80 = bytes
[0x80, 0x3F]), get/set primitives, signed zero preservation, 2D /
3D shape strides, bulk copyToFloatArray parity with element-by-
element decode, undersized-buffer / out-of-bounds / wrong-rank
rejections, and bit-identity for FP32 values that have zero in the
low 16 bits.
Passes jvmTest + linuxX64Test on the lang-core module. Refs #609.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
📖 Documentation Preview The documentation has been built successfully for this PR. Generated Files:
Artifacts:
This comment will be updated automatically when the PR is updated. |
This was referenced May 16, 2026
MacOS
pushed a commit
to MacOS/SKaiNET
that referenced
this pull request
Jul 10, 2026
…T-developers#611) Phase 2 of the three-phase BF16 dispatch chain. Follow-up to SKaiNET-developers#610 (Bf16TensorData merged). Today's loader unconditionally dequants BF16 → FP32 at load (`SafeTensorsParametersLoader.kt` line 87, `dequantBF16(bytes)`), which means even with the new Bf16TensorData type in place no SafeTensors-loaded weight ever reaches it. This PR adds an opt-in policy so consumers that want native BF16 (Gemma-3n is the obvious first one) get a Bf16DenseTensorData-backed tensor; everyone else stays on the dequant path with zero behavioural change. Surface: - new `Bf16LoadPolicy` enum (commonMain) with `DEQUANT_TO_FP32` (default) and `KEEP_NATIVE` cases. Documents the trade-off: memory halved vs. per-element decode cost on non-matmul ops. - new constructor parameter `bf16Policy: Bf16LoadPolicy = Bf16LoadPolicy.DEQUANT_TO_FP32`. Default preserves source + bytecode compat for every existing Kotlin caller. - new branch in the `DataType.BFLOAT16` case: when policy is `KEEP_NATIVE`, wrap the on-disk bytes in `Bf16DenseTensorData` and emit via `ctx.fromData(...)`. The consumer-visible dtype stays `FP32::class` (same pattern as Q4_K / Q8_0 tensors — quantised storage, FP32 dtype tag); only `tensor.data` differs. 4 new tests in `commonTest`: - DEQUANT_TO_FP32 path produces FloatArrayTensorData with values within BF16 precision. - KEEP_NATIVE path produces Bf16DenseTensorData whose packedData byte array matches the on-disk bytes verbatim. - Decoded values from both paths are bit-identical (both apply the same `bf16_bits << 16` math; only WHEN differs). - Mixed BF16+FP32 file under KEEP_NATIVE — BF16 becomes Bf16DenseTensorData, FP32 stays FloatArrayTensorData. Refs SKaiNET-developers#611. Full `:skainet-io:skainet-io-safetensors:jvmTest` suite passes on linux-x86_64 / JDK 21. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MacOS
pushed a commit
to MacOS/SKaiNET
that referenced
this pull request
Jul 10, 2026
…KaiNET-developers#613) Final phase of the three-phase BF16 dispatch chain. Follow-ups to SKaiNET-developers#610 (Bf16TensorData) and SKaiNET-developers#612 (loader KEEP_NATIVE policy) — both merged. After this PR, a consumer that flips `bf16Policy = KEEP_NATIVE` on SafeTensorsParametersLoader (or constructs a `Bf16DenseTensorData` directly) gets the SIMD-vectorised BF16 matmul path with zero other code changes. Native FFM kernel (priority 100) wins when the bundled libskainet_kernels.so is loaded; falls through to Panama Vector (50) then to the scalar SPI reference (0). Implementation: - New `bf16MatmulKernel: Bf16MatmulKernel` lazy in DefaultCpuOpsJvm. Non-null with `ScalarBf16MatmulKernel` floor — mirrors `fp32MatmulKernel`'s pattern rather than the nullable `q4kMatmulKernel` / `q8_0MatmulKernel` pattern (which exist because Q4_K / Q8_0 have legacy non-SPI fallbacks via `JvmQuantizedVectorKernels`; BF16 has no such legacy). - New `is Bf16TensorData ->` branch in `chooseQuantizedMatmul`'s `when (bData)` block. The BF16 SPI kernel is a full SGEMM `(m, n, k)` with byte-strides on the B operand — no per-batch matvec loop like Q4_K/Q8_0/Q6_K need. 3 integration tests in `Bf16MatmulDispatchTest`: - single-batch matmul (`[1, k] × [k, n]` BF16) matches scalar reference within `1e-2 * k`. - multi-batch matmul (`m=3, k=256, n=32`) — exercises a 2D output. - LLM-typical 512² attention projection. Refs SKaiNET-developers#613. Full `:skainet-backends:skainet-backend-cpu:jvmTest` and `:skainet-backends:skainet-backend-native-cpu:jvmTest` suites pass on linux-x86_64 / JDK 21 with `--add-modules jdk.incubator.vector`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #609. First of three follow-ups to #605 (BF16 matmul kernels).
Why
The BF16 matmul kernels landed in #605 (scalar / Panama / native) are plumbed but can't be reached from
ops.matmultoday — the SafeTensors loader unconditionally dequants BF16 weights to FP32 at load, so by the timeDefaultCpuOps.matmulsees the operand it's aFloatArrayTensorDatarather than anything that the newBf16MatmulKernelrecognises.This PR adds the missing TensorData type so the rest of the chain has something to dispatch on. No loader or dispatch changes here — those are Phase 2 and Phase 3.
What
Bf16TensorData : TensorData<DType, Float>plus a concreteBf16DenseTensorData(shape, ByteArray)incommonMain. Surface area:Bf16TensorData.packedData: ByteArrayget(*indices): Floatset(*indices, value: Float)copyToFloatArray()Bf16TensorData.floatToBf16Bits/bf16BitsToFloat(companion)bf16_matmul.candPanamaVectorBf16MatmulKernelBf16DenseTensorData.fromFloatArray(shape, FloatArray)Mirrors the structure of
Q8_0BlockTensorDataminus the block accessors (BF16 is dense, no per-block scale).Tests
11 unit tests in
Bf16TensorDataTest(commonTest):copyToFloatArray()parity with element-by-elementget.All pass on
jvmTestandlinuxX64Test. (The unrelatedkotlinWasmStoreYarnLockfailure is pre-existing on develop and not touched by this PR.)Phase 2 / 3 (follow-up PRs)
Bf16DenseTensorDatainstead. Adds a precision-policy knob so existing consumers stay unaffected.DefaultCpuOpsJvm.chooseQuantizedMatmuldispatch forBf16TensorDatavia theBf16MatmulKernelSPI. Mirrors the Q8_0 dispatch wiring (#608).SKaiNET-transformers.🤖 Generated with Claude Code