Row: -
Owed by .agents/specs/unaligned-safetensors-consumers.md under ## Owed.
What changed, and what it cost
#2581 repaired four CPU consumers that formed a const uint16_t* over a borrowed safetensors payload at an odd address. Fixing the EXL3 pair meant the trellis cursor became a byte cursor, so five signatures widened their weight-side operand from const uint16_t* to const void*:
| Signature |
Declared in |
vt::Exl3TileCodeword(const void* tile, ...) |
include/vt/ops.h |
vt::Exl3DecodeTile(const void* tile, ...) |
include/vt/ops.h |
vt::Exl3ReconstructInner(const void* trellis, ...) |
include/vt/ops.h |
vt::Exl3DequantLinear(const void* trellis, const void* suh, const void* svh, ...) |
include/vt/ops.h |
MoeGemm(const uint16_t* a_had, const void* trellis, ...) |
src/vt/cpu/cpu_exl3_kernels.cpp |
HadRowBlock(HadIo, const void* in, void* out, const void* pre, const void* post, ...) in the same file widened its pre and post scale operands for the same reason.
Why this is a hazard and not a defect
const void* accepts ANY pointer type without a diagnostic. Before the change, passing an f32 scale array, a byte length where a word count belongs, or an unrelated tensor to Exl3DequantLinear was a compile error. Now it compiles and decodes garbage. The stride argument that used to be supplied by the pointer type is carried explicitly instead, so a caller that forgets the factor of two reads the wrong half of every word and still type-checks.
Nothing is wrong TODAY. Every existing caller passes the int16 words as stored, a uint16_t* converts implicitly, and no call site outside the repaired files changed. test_exl3_gemm, test_qwen35_exl3 and test_exl3_dequant are green under address,undefined, and #2581's new case compares the odd-address decode BYTE-FOR-BYTE against the even-address one, so a lost factor of two reds it. The exposure is to the NEXT caller, which has no compiler telling it what the parameter means.
Fix shape
A one-member wrapper -- struct Exl3Trellis { const void* words; } or an equivalent vt::ByteSpan -- restores the diagnostic without restoring the alignment requirement, because the wrapper's own alignment is 1. It must NOT be a re-typed const uint16_t*: that is the undefined load #2558 and #2578 name, and reverting to it reintroduces the abort under -fsanitize=alignment.
The change is mechanical but it crosses the public ABI in include/vt/ops.h, so it wants its own row, its own spec, and the EXL3 suites green beside it rather than riding in a fix for a different defect. Filed rather than swept.
Found during the fresh review of #2581 (finding 2, rated non-blocking).
Row:
-Owed by
.agents/specs/unaligned-safetensors-consumers.mdunder## Owed.What changed, and what it cost
#2581 repaired four CPU consumers that formed a
const uint16_t*over a borrowed safetensors payload at an odd address. Fixing the EXL3 pair meant the trellis cursor became a byte cursor, so five signatures widened their weight-side operand fromconst uint16_t*toconst void*:vt::Exl3TileCodeword(const void* tile, ...)include/vt/ops.hvt::Exl3DecodeTile(const void* tile, ...)include/vt/ops.hvt::Exl3ReconstructInner(const void* trellis, ...)include/vt/ops.hvt::Exl3DequantLinear(const void* trellis, const void* suh, const void* svh, ...)include/vt/ops.hMoeGemm(const uint16_t* a_had, const void* trellis, ...)src/vt/cpu/cpu_exl3_kernels.cppHadRowBlock(HadIo, const void* in, void* out, const void* pre, const void* post, ...)in the same file widened itspreandpostscale operands for the same reason.Why this is a hazard and not a defect
const void*accepts ANY pointer type without a diagnostic. Before the change, passing anf32scale array, a byte length where a word count belongs, or an unrelated tensor toExl3DequantLinearwas a compile error. Now it compiles and decodes garbage. The stride argument that used to be supplied by the pointer type is carried explicitly instead, so a caller that forgets the factor of two reads the wrong half of every word and still type-checks.Nothing is wrong TODAY. Every existing caller passes the int16 words as stored, a
uint16_t*converts implicitly, and no call site outside the repaired files changed.test_exl3_gemm,test_qwen35_exl3andtest_exl3_dequantare green underaddress,undefined, and #2581's new case compares the odd-address decode BYTE-FOR-BYTE against the even-address one, so a lost factor of two reds it. The exposure is to the NEXT caller, which has no compiler telling it what the parameter means.Fix shape
A one-member wrapper --
struct Exl3Trellis { const void* words; }or an equivalentvt::ByteSpan-- restores the diagnostic without restoring the alignment requirement, because the wrapper's own alignment is 1. It must NOT be a re-typedconst uint16_t*: that is the undefined load #2558 and #2578 name, and reverting to it reintroduces the abort under-fsanitize=alignment.The change is mechanical but it crosses the public ABI in
include/vt/ops.h, so it wants its own row, its own spec, and the EXL3 suites green beside it rather than riding in a fix for a different defect. Filed rather than swept.Found during the fresh review of #2581 (finding 2, rated non-blocking).