Skip to content
Closed
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
25 changes: 9 additions & 16 deletions npu/pack_import.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2508,18 +2508,6 @@ std::string Plan::manifest() const {
return j.dump(2);
}

// engine#1889: where the per-head QK gains sit when the attention section carries them as the 640 B between the last
// qkv tile and o_proj. Split out and exposed because this is the arithmetic a wrong answer silently mis-rotates every
// Hunyuan pack with: a right offset reads a gain (~1.0-2.0), a wrong one reads tile bytes as bf16 and the gains come out
// wild, and the test drives both shapes without a pack.
QkGainOffsets qk_gain_offsets(int64_t gap_start, int64_t head_dim, int64_t gap_bytes) {
const int64_t per_head = head_dim * 2;
if (gap_bytes != 2 * (kNormOff + per_head)) return QkGainOffsets{};
QkGainOffsets o;
o.k_norm = gap_start + kNormOff; // block 0 is k, as the separate-section family
o.q_norm = gap_start + (kNormOff + per_head) + kNormOff; // block 1 is q
return o;
}

std::string qk_norm_refusal(bool declared, bool found) {
if (declared && !found)
Expand Down Expand Up @@ -2848,10 +2836,15 @@ Plan plan(const std::string& pack_dir) {
// use_qk_norm, gains not located") into an import; the values are the check (a wrong offset
// reads tile bytes as bf16 and the gains come out wild, a right one gives the 1.0-2.0 a norm
// gain has).
const QkGainOffsets qkg =
qk_gain_offsets(attn_off + p.attn_tiles * tile_bytes, int64_t(d.head_dim), rem);
p.knorm_off = qkg.k_norm;
p.qnorm_off = qkg.q_norm;
// engine#1934: NOT claimed. The pack's own per-op metadata names this gap as ONE tensor --
// `attn.q_norm.layernorm.weight.qk_rms_packed_26_0`, size 640, shape [320] -- and #1928's
// two-window split read the SAME bytes twice (the structure repeats with a period of 320 B),
// so it wrote two copies of one vector as `q_norm`/`k_norm`. Neither is the pack's gain, and
// a value check cannot tell: both windows are plausible gains, which is exactly the trap
// #1889 warned about. Until the `qk_rms_packed` packing is known the honest state is to read
// nothing here; a pack that DECLARES the gains then meets #1895's refusal by name instead of
// importing two tensors that are not them.
p.qk_packed_gap_bytes = rem;
static bool said2 = false;
if (!said2) {
said2 = true;
Expand Down
8 changes: 4 additions & 4 deletions npu/pack_import.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ struct Dims {
// REFUSAL (importing without the gains is the silent-garbage path), found-and-not-declared is allowed only with the
// measurement behind it (Qwen3 packs ship the sections without the key), and neither is the ordinary case. Exposed
// so the three can be pinned without a 4 GB pack.
struct QkGainOffsets {
int64_t k_norm = 0, q_norm = 0; // 0,0 = the gap is not the gains' shape
};
QkGainOffsets qk_gain_offsets(int64_t gap_start, int64_t head_dim, int64_t gap_bytes);
std::string qk_norm_refusal(bool declared, bool found);

// A hybrid pack's embedding table lives in its own graph (`embedding.onnx` + `.data`, gathered with
Expand Down Expand Up @@ -138,6 +134,10 @@ struct Plan {
// none. Identified and verified byte-exact against the public Qwen3-1.7B/4B checkpoints (0 of 128
// values differ on either pack, and the swap differs on 127-128 of 128).
int64_t knorm_off = 0, qnorm_off = 0;
// engine#1934: the bytes between the last qkv tile and o_proj when they are the per-head gains in the PACKED form
// (`…q_norm.layernorm.weight.qk_rms_packed_26_0`, one bf16 tensor of 320 values). Recorded, not claimed: the two-window
// split #1928 used read one window twice, and a value check cannot tell the difference.
int64_t qk_packed_gap_bytes = 0;
int64_t mlp_off = 0; // the MLP section starts just past the block's post-attention norm
// What the pack's *graph* carries, reported so `npu-pack info` can answer a pack's genre without a
// third-party parser: the rai_1.3/1.6/1.7.1 line ships its 4-bit weights as ordinary ONNX initializers, while
Expand Down
19 changes: 0 additions & 19 deletions tests/npu_pack_import_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3856,27 +3856,8 @@ void test_qk_norm_refusal() {
std::printf(" qk norm: 7 arms (declared+not-found refuses; found-only and neither are allowed)\n");
}

// engine#1889: the arithmetic that claims the per-head QK gains out of the 640 B Hunyuan-7B carries between its last qkv
// tile and o_proj. A wrong offset there reads tile bytes as bf16 and mis-rotates every pack of the family, so both the
// right shape (2 * (64 + head_dim*2) = 640 at head_dim 128) and the neighbours that must NOT be taken for it are driven.
void test_qk_gain_offsets() {
const pi::QkGainOffsets o = pi::qk_gain_offsets(1000, 128, 640);
check(o.k_norm == 1064, "qk gains: block 0 starts one kNormOff (64 B) after the gap");
check(o.q_norm == 1384, "qk gains: block 1 starts after block 0 (64 B header + a 256 B bf16 [128] gain)");
check(o.q_norm - o.k_norm == 320, "qk gains: the two blocks are 320 B apart, the same stride as the separate family");
const pi::QkGainOffsets n1 = pi::qk_gain_offsets(1000, 128, 639);
const pi::QkGainOffsets n2 = pi::qk_gain_offsets(1000, 128, 641);
const pi::QkGainOffsets n3 = pi::qk_gain_offsets(1000, 128, 512); // just the two gains, no headers
const pi::QkGainOffsets n4 = pi::qk_gain_offsets(1000, 256, 640); // the right byte count at the wrong head_dim
check(n1.k_norm == 0 && n1.q_norm == 0, "qk gains: a gap one byte short is not the gains' shape");
check(n2.k_norm == 0 && n2.q_norm == 0, "qk gains: a gap one byte long is not either");
check(n3.k_norm == 0 && n3.q_norm == 0, "qk gains: two bare gains with no headers are not this shape");
check(n4.k_norm == 0 && n4.q_norm == 0, "qk gains: the byte count alone is not enough, head_dim has to agree");
}

int main(int argc, char** argv) {
test_qk_norm_refusal();
test_qk_gain_offsets();
test_tail_head_placement();
const std::string golden_dir = argc > 1 ? argv[1] : "";
const std::string pack = argc > 2 ? argv[2] : "";
Expand Down
Loading