Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,14 @@ internal class DefaultCpuOpsJvm(
val inputBuffer: FloatArray = when (aData) {
is FloatArrayTensorData<*> -> aData.buffer
is MemorySegmentBackedData -> aData.copyToFloatArray()
else -> return null
// Any other *dense* FP32 activation — a slab-backed StorageFloatTensorData from a
// ScopedExecutionContext forward (#1145/#1146), a view-only wrapper — copies out
// through its own (offset-aware) copyToFloatArray. Bailing out here instead used to
// drop these activations to matmulGeneric, whose per-element get() on a quantized
// weight returns raw codes, not values — silently wrong logits, the #993 class of bug.
// A packed/encoded *activation* (encoding != null) still bails: this chooser's
// kernels want dense FP32 input.
else -> if (aData.encoding == null) aData.copyToFloatArray() else return null
}

return when (bData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,49 @@ class QuantizedMemSegMatmulTest {
arena.close()
}

@OptIn(sk.ainet.lang.memory.ExperimentalMemoryApi::class)
@Test
fun `Q8 matmul with a slab-backed scoped activation matches the dense-activation result`() {
val arena = Arena.ofConfined()
val inputDim = 32
val outputDim = 2

val weightValues = Array(outputDim) { row ->
FloatArray(inputDim) { col -> 0.3f * (row + 1) * ((col % 5) - 2) / 5f }
}
var weightBytes = ByteArray(0)
for (row in weightValues) weightBytes += encodeQ8_0Block(row)
val weight = q8Tensor(Shape(outputDim, inputDim), weightBytes, arena)

val inputValues = FloatArray(inputDim) { (it + 1).toFloat() / inputDim }
val expected = ops.matmulWeightTransposed(fpTensor(Shape(1, inputDim), inputValues), weight)
.data.copyToFloatArray()

// The forward-scope shape of the same activation: a StorageFloatTensorData over a slab
// slice at a NONZERO offset — what a ScopedExecutionContext hands every op mid-step
// (#1145/#1146). Before chooseQuantizedMatmul2D grew its dense-FP32 fallback this fell
// through to matmulGeneric, whose per-element get() on the Q8 weight returns raw codes —
// silently wrong results, not an error.
sk.ainet.lang.memory.ForwardScope(1024).use { scope ->
scope.allocateFloats(7) // push the next allocation off offset 0
val st = scope.allocateFloats(inputDim)
inputValues.copyInto(st.floats!!, st.arrayOffset, 0, inputDim)
@Suppress("UNCHECKED_CAST")
val slabData = sk.ainet.lang.tensor.data.StorageFloatTensorData<FP32>(Shape(1, inputDim), st)
val scoped: Tensor<FP32, Float> = VoidOpsTensor(slabData as TensorData<FP32, Float>, FP32::class)
val actual = ops.matmulWeightTransposed(scoped, weight).data.copyToFloatArray()

assertEquals(expected.size, actual.size)
for (i in expected.indices) {
assertEquals(
expected[i], actual[i], 0f,
"scoped-activation mismatch at $i: dense=${expected[i]} scoped=${actual[i]}"
)
}
}
arena.close()
}

// ── Batched Matmul Test ─────────────────────────────────────────────────

@Test
Expand Down
Loading