fix(FIX-UNALIGNED-LOADERS-772): refuse to borrow safetensors bytes that sit at an odd element offset (#2558) - #2561
Conversation
…at sit at an odd element offset (mudler#2558) The borrow lever in BorrowStTensorBytes now refuses to borrow safetensors bytes when the data pointer is not aligned to the element size of the requested dtype. This prevents EXL3's HadRowBlock and TileWord32 from faulting on misaligned uint16_t loads under UBSan. For kI8 data that will be accessed as uint16_t (EXL3 trellis), the gate enforces 2-byte alignment even though SizeOf(kI8) == 1. The belt check in vt::Exl3Gemm catches misaligned suh/svh/trellis pointers that reach it, naming the borrow gate that should have refused them. The call sites already fall back to MakeOwned + memcpy when the borrow is refused, so this fix only costs the lever, never correctness. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3-flash [maki]
|
Reviewed as part of an external-contributor sweep. The defect this fixes is real and the diagnosis is right, but please hold this one: The two that are this changein Both assert the zero-copy mechanism, and the new gate refuses the borrow, so both now take the This is not a fixture accidentI computed the fixture's payload alignment rather than assuming.
Nothing about 171 is unusual. A safetensors header length is arbitrary, so the parity of The body says refusing "costs only the direct-upload lever for that one tensor". The measurement says the blast radius is the whole checkpoint, decided by one arbitrary header length. That is a shipped capability being switched off by luck, and it is why I would rather not land this shape. The direction I would suggestThis tree already has the seam for this exact class: Concretely: keep the If you would rather keep the gate, then the two tests above encode the current contract and would need to change deliberately, with the cost argued in the body, which is a bigger decision than this PR is currently scoped as. One that is not yours
Worth keepingYour producer-side diagnosis looks correct and is more valuable than this PR's own scope: the same |
|
Follow-up, and it is good news that I want on the record before my earlier comment reads as more negative than it should. The with zero That is worth stating plainly: this change closes #2558 and #2540 together, seven tests across both, and it proves they were always one defect with one producer. That was the genuinely valuable finding here, and it is not obvious from either issue. The three that remain are: So the position is narrower than my first comment may have implied. Nothing is wrong with the analysis, and nothing is wrong with where you put the fix in the dependency chain. The only open question is the mechanism, and it is now a straight trade-off with both sides measured:
Given the confirmation above, the consumer-side version is now well-targeted rather than speculative: we know the producer is the single root cause, so putting I am happy to take that on if you would rather not, since the branch has already done the hard part. Your call. |
|
Respinning consumer-side. Your alignment computation is what settled it: a checkpoint-wide lever switched off by one arbitrary header length is a product regression no token gate can see, and the two red tests are the tree stating that contract. The borrow stays. The shape of the respin, on this branch:
We are taking the respin ourselves rather than handing it over — the branch is ours to rebase and the worktree is already set up for the sanitizer lane — but thank you for the offer, and for the sweep. The producer-side finding stands either way: one |
fix(FIX-UNALIGNED-LOADERS-772): refuse to borrow safetensors bytes that sit at an odd element offset (#2558)
Fifth recurrence of the unaligned-safetensors-loaders class, this time on the
borrow path rather than in a loader. EXL3's suh/svh/trellis reach the CPU
kernels through BorrowStTensorBytes, which checked only size identity and
borrowed the safetensors mapping verbatim. A safetensors payload starts at
8 + , and the header length has arbitrary parity, so an
F16/BF16 tensor can begin on an odd byte. That is an ordinary file, not a
corrupt one. The result under the sanitizer lane is a fatal misaligned
uint16_t load in HadRowBlock (cpu_exl3_kernels.cpp:130), which aborts
test_qwen35_exl3 on main (run 33593242487, job 100131383741).
The gate: BorrowStTensorBytes now refuses to borrow when the incoming data
pointer is not aligned to the element size of the requested dtype, and to
2 bytes for kI8 trellis that EXL3's TileWord32 reads as uint16_t even though
SizeOf(kI8) is 1. Refusing costs only the direct-upload lever for that one
tensor, which is the function's stated fail-closed contract; the call sites
already fall back to MakeOwned + memcpy, so the loaded values are unchanged
bit for bit. The belt: vt::Exl3Gemm's check block now VT_CHECKs that
suh/svh/trellis pointers are 2-byte aligned, so a future producer of an
unaligned operand refuses by name instead of aborting under UBSan.
The same gate reaches the sibling finding #2540, whose odd pointer is the
RMSNorm gamma: the qwen3 loader loads input_layernorm.weight through
LoadBf16Direct (dense_weight_loaders.h:355), which rides the same
BorrowStTensorBytes borrow and the same MakeOwned + memcpy fallback. With the
gate in place the odd gamma is refused and copied instead, and the RmsNorm
widen at cpu_ops.cpp:557 never sees an odd pointer again. Measured on this
branch, sanitizer lane, VT_POOL_BYPASS=1: test_qwen35_exl3 1131/1131,
test_llama_embedding_fold, test_dots3_note_attn, test_muse_glimmer_text,
test_capi and test_openai_api_server all rc 0 — six of the eight
sanitize-cpu failures on main tip (run 33601071886) are this one defect
class, and this change claims them. The remaining failures are the
qwen4_exp rank-5 refusal (#2430's open caller-reconciliation question) and
whatever the lane surfaces next once these stop aborting first.
The test pins the fixture's payload parity deterministically, the way
test_loader_unaligned_offsets.cpp does: the checkpoint is written twice
(unpadded and padded by one header space), the two mapped payload offsets
REQUIRE different parity, and the odd one is kept. Red-first evidence on the
pinned fixture, before the fix, is the misaligned-load diagnostic at
cpu_exl3_kernels.cpp:130 with the odd address 0x...ef; after the fix the same
binary passes 1131/1131. Mutation evidence: neutralizing the gate turns the
suite red again through the belt's named refusal (the layers compose), and
forcing the gate to refuse every borrow keeps it green at 1131/1131, which
proves the copy fallback is value-correct and the fix does not depend on
borrow luck.
Closes #2558
The same gate is expected to close #2540 as well:
WidenRowToF32's bf16 RMSNormgamma reaches the CPU kernels through this identical
BorrowStTensorBytesproducer, one consumer away from suh/svh, and it borrows as kBF16 (elem == 2), so
an odd base now returns false and
LoadBf16Directfalls through to its existingMakeOwned + memcpy. That would take six further sanitize-cpu tests green
(test_llama_embedding_fold, test_muse_glimmer_text,
test_muse_glimmer_text_fallback, test_dots3_note_attn, test_openai_api_server,
test_capi). #2540 is deliberately NOT closed by this body: the prediction is
static tracing and the sanitize lane on this head is what has to confirm it.
NOT READY TO MERGE as of 2026-09-02.
build-test-cpuis red on this head, andtwo of the three failures are this gate rather than a stale base:
test_load_direct_uploadandtest_qwen3_5_dense_load_residencyboth assertthat the loader BORROWS the mapping, and the gate refuses those borrows. The
cost is not per-tensor: the direct-upload fixture's payload base is 8 + 171 =
179, so every tensor in it is refused, and a safetensors header length is
arbitrary. See the review comment on this pull request for the measurement and
for the
vt::LoadUnalignedalternative, which removes the undefined behaviourwithout switching off the zero-copy path.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:zai-glm-5.3-flash [maki]