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
37 changes: 29 additions & 8 deletions npu/pack_import.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,15 @@ const Fconst19Rule* select_rule_in(const Fconst19Rule* rules, int count, int hid
// The header a 1.9 block carries: 00 00 7f ff once per head. Its length being the head count is what makes
// it a test rather than a magic number. 0x00007fff is the big-endian reading of those four bytes; the bytes
// are what is compared, because the little-endian reading of the same bytes is 0xff7f0000.
} // namespace (closed so prefer_residue_headers has external linkage: the arm drives it directly, engine#1986)
bool prefer_residue_headers(int64_t vote_h1, size_t vote_count, const std::vector<int64_t>& residue) {
if (vote_count == 0 || residue.empty()) return false;
if (residue.size() <= vote_count) return false; // the vote already has the longer list
return std::find(residue.begin(), residue.end(), vote_h1) != residue.end();
}

namespace {

std::vector<uint8_t> fconst19_header(int heads) {
std::vector<uint8_t> h(size_t(heads) * 4, 0);
for (int i = 0; i < heads; ++i) {
Expand Down Expand Up @@ -1809,21 +1818,33 @@ FconstGeom locate_fconst_with_header(const std::string& tok, const std::string&
// engine#1831: only when the vote did NOT record the block's headers. When it did, its list is the measurement
// (`plan()` reads 2-header vs 4-header from `hdr.size()`, and `post_attn_hdr()` is `hdr.back()`), and rewriting it
// to one entry turned every Qwen3 4-header 1.9 block into a 2-header one whose "post-attention" header was k_norm's.
// engine#1985: a vote that succeeded can still be SHORT, and the two witnesses answer different questions -- the vote
// settles the STRIDE (it needs a dominance test), the residue settles the COUNT (a real section boundary repeats in
// every block). So the residues are consulted first: when they contain the vote's offset and say there are more, they
// win (`prefer_residue_headers`, seven arms in the test). When they do NOT settle it, main's answer from engine#1995
// stands rather than a guess: a pack that declares per-head q/k gains cannot have a 2-header block, so a one-header
// vote on such a pack is refused naming both witnesses.
const std::vector<int64_t> pos = eps_header_positions(tok, size_t(1) << 30, dims.eps_bits);
const std::vector<int64_t> residue = fconst_block_headers(pos, stride8, 3);
if (!g.hdr.empty()) {
/* engine#1985: the vote's list is a measurement, but it is not the only witness. A pack that declares per-head q/k
* gains cannot have a 2-header block, so a one-header vote on such a pack is the #1831 defect coming back through
* the vote instead of through the residue step -- measured: Qwen3-1.7B read as 2-header, qk_norm true -> false, 280
* refused projections, where main imported it with 0 the day before. Refuse naming both rather than pick. */
if (const std::string why = header_witness_refusal(int(g.hdr.size()), dims.use_qk_norm); !why.empty()) {
g.why = why;
if (prefer_residue_headers(g.h1, g.hdr.size(), residue)) {
FconstGeom better = g;
if (fconst_set_block_headers(better, residue)) {
std::fprintf(stderr,
"note: the epsilon vote recorded %d in-block header(s) at %lld B stride, the anchor residues "
"repeat at %d; using the residue list (%d headers per block)\n",
int(g.hdr.size()), (long long)g.stride, int(residue.size()), better.period);
g = better;
}
} else if (const std::string why = header_witness_refusal(int(g.hdr.size()), dims.use_qk_norm); !why.empty()) {
g.why = why; // engine#1995's answer when the residues do not settle it
g.stride = 0;
}
return g;
}
const std::vector<int64_t> pos = eps_header_positions(tok, size_t(1) << 30, dims.eps_bits);
// The scan window is the first GiB (about 8 blocks at h3584), so a real section boundary repeats ~8 times against
// 1-2 for a payload coincidence; 3 separates them (requiring `layers/2` was too strict and dropped the real one).
const std::vector<int64_t> inner = fconst_block_headers(pos, stride8, 3);
const std::vector<int64_t>& inner = residue;
if (!fconst_set_block_headers(g, inner)) {
g.why = "the 1.9 header gives stride " + std::to_string(stride8) + ", but the epsilon anchors repeat at " +
std::to_string(inner.size()) + " in-block offset(s), and a layer block has 1 (2-header) or 3 (4-header); "
Expand Down
7 changes: 7 additions & 0 deletions npu/pack_import.h
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,13 @@ std::vector<int64_t> fconst_block_headers(const std::vector<int64_t>& pos, int64
// engine#1831: set `hdr`/`h1`/`period` from that list the way locate_fconst() does -- one inner header is a 2-header block,
// three is a 4-header block. Returns false, and changes nothing, for any other count.
bool fconst_set_block_headers(FconstGeom& g, const std::vector<int64_t>& inner);
// engine#1985: which in-block header list to keep, given the epsilon vote's and the anchor residues'. The two witnesses answer
// DIFFERENT questions -- the vote settles the STRIDE (it needs a dominance test), the residue settles the COUNT (a real section
// boundary repeats in every block) -- so a vote that found only part of the list is not wrong, just short, and the longer
// consistent list wins. `residue` wins only when it CONTAINS the vote's first offset and reports more; an outright disagreement
// is not a licence to take the longer list, so the vote stands. Pure and exported so this decision table is pinned with no pack,
// no file and no configure (engine#1986 review).
bool prefer_residue_headers(int64_t vote_h1, size_t vote_count, const std::vector<int64_t>& residue);

// One measured Ryzen AI 1.9 Token .fconst block shape (engine#1503). Every field is measured against the 1.8 pack of
// the same model by tools/fconst19_measure.py; none of it is derivable from the pack, which is why a pack whose shape
Expand Down
23 changes: 23 additions & 0 deletions tests/npu_pack_import_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5196,6 +5196,28 @@ void test_fconst_variable_stride(const std::string& root) {
// engine#1740 (W1a): the tail-head placement arithmetic, pinned without a pack, a device or a fixture. The rows, the
// tile size and the envelope are the hidden-896 pack's, so the numbers here are the real ones: 1,856 B envelope,
// 2,374 tiles of 122,880 B, tail 1,856 + 2,374 x 122,880 = 291,653,952 B.
// engine#1986 review: the arbitration between the two witnesses, pinned as a decision table. Twenty-four lines of "which list
// wins" had no arm, and both directions matter: taking the residue list too eagerly is a wrong layout on a pack the vote got
// right, and never taking it is engine#1985 (Qwen3-1.7B refused with 64 tiles where the block has 96).
void test_prefer_residue_headers() {
using V = std::vector<int64_t>;
// the vote found ONE in-block offset at 640 B; the residues repeat at three -- engine#1985's shape
check(pi::prefer_residue_headers(640, 1, V{640, 1280, 1920}), "prefer residue: a short vote yields to the residue list");
// ...and the same three offsets with the vote's one among them in a different position
check(pi::prefer_residue_headers(1920, 1, V{640, 1280, 1920}), "prefer residue: position in the list does not matter");
// equal lists: nothing to arbitrate, and no churn in the note
check(!pi::prefer_residue_headers(640, 3, V{640, 1280, 1920}), "prefer residue: an equal-length list keeps the vote");
// the vote found MORE than the residues: the vote wins, because the residues lost a boundary
check(!pi::prefer_residue_headers(640, 4, V{640, 1280, 1920}), "prefer residue: a longer vote keeps the vote");
// OUTRIGHT DISAGREEMENT: the residue never mentions the vote's offset, so this is not "short", it is "different"
check(!pi::prefer_residue_headers(704, 1, V{640, 1280, 1920}),
"prefer residue: a residue list that does not contain the vote's offset keeps the vote");
// degenerate inputs: an empty vote is handled by the caller's own branch, an empty residue never wins
check(!pi::prefer_residue_headers(640, 0, V{640, 1280, 1920}), "prefer residue: an empty vote never prefers the residue");
check(!pi::prefer_residue_headers(640, 1, V{}), "prefer residue: an empty residue list never wins");
std::printf(" prefer residue: 7 arms (short vote yields, equal/longer/disagreeing keep the vote, degenerate inputs)\n");
}

void test_tail_head_placement() {
// The real hidden-896 geometry: group_bytes(896, 64) = 8 x 68 x 64 = 34,816 B per head tile, V/64 = 2,374 tiles,
// 1,856 B envelope, so the tail is 1,856 + 2,374 x 34,816 = 82,655,040 B -- which is the 0.5B pack's measured tail
Expand Down Expand Up @@ -5446,6 +5468,7 @@ int main(int argc, char** argv) {
test_q4_writer_code_range();
test_qk_norm_refusal();
test_qk_gain_offsets();
test_prefer_residue_headers();
test_tail_head_placement();
const std::string golden_dir = argc > 1 ? argv[1] : "";
const std::string pack = argc > 2 ? argv[2] : "";
Expand Down
Loading