Skip to content

EXL3 decoders take a const void* weight operand, so a wrongly typed argument no longer fails to compile #2597

Description

@localai-org-maint-bot

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions