Skip to content

npu-pack: a 1.8 hybrid pack's [0] MatMulNBits placeholders are not weights; refuse by name and point at model.bin (Refs #1503) - #2179

Merged
bong-water-water-bong merged 1 commit into
mainfrom
cl1077/1503-external-init
Nov 11, 2026
Merged

bong-water-water-bong merged 1 commit into
mainfrom
cl1077/1503-external-init

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

Refs #1503. This PR does not import the 1.8 hybrid packs. It stops npu-pack from claiming that it can.

The defect, measured

On Llama-3.2-1B-Instruct_rai_1.8.0_hybrid, every .MatMulNBits.* initializer is a [0] placeholder with no data: 162 of 162 qweight, scales, qzeros and bias tensors, plus their .packed siblings, all dims [0], with no location and no raw bytes. The weights are in the pack's 1.3 GB model.bin. Main's walker only knew the 1.9 spelling of a placeholder (<w>.empty beside <w>.packed). It kept the 1.8 node, which names <w> itself, as a real weight.

on main this PR
npu-pack info: rc 0, every projection refusal: "", 2 gaps (layer 0's norms) rc 1, refused by name, before any per-tensor work
npu-pack import: died mid-way on model.layers.0.attn.qkv_proj.MatMulNBits.bias: no data the same refusal as info, and nothing written

The refusal is the existing #1687 rule, now also reached by this spelling. When the pack has a model.bin, one sentence is added saying where the weights are:

lm_head.MatMulNBits.qweight (its lm_head.MatMulNBits.qweight.packed sibling carries no data either), and the pack carries
no Token .fconst either (...): these weights are genuinely absent from the pack's external data, so this pack cannot be
imported -- not a tile-layout or block-size problem. This pack's weights are in its model.bin, which this importer does
not read (engine#1503)

SmolLM2-135M rai_1.8.0 hybrid had the same wrong picture on main (rc 0, 2 norm gaps), and gets the same refusal here.

Regression check

I ran npu-pack info with a main-only build and with this PR on every pack on disk (12). On 11 packs the output is byte-identical (cmp), and so is the exit code: the 1.7.1 hybrids (phi3-mini, Phi-3.5, qwen2.5-0.5b), smollm2-135m, qwen3-1.7b-awq, phi-4 1.9, Qwen2.5-0.5B 1.9, gpt-oss-20b 1.9, Qwen3-VL-4B 1.9, and the two golden directories. The only one that changed is smollm2-135m-1.8, as described above.

Test

npu_pack_import_test, arm 4 (the 1.8 spelling): a [0] <w> beside an empty <w>.packed. With a model.bin beside them, plan() refuses, naming both tensors and model.bin. Without a model.bin, the refusal does not claim one. A mutant that restores main's .empty-only rule fails both arms (rc 1). The whole test passes (rc 0).

What still has to happen for #1503 (not in this PR)

A reader for the 1.8 model.bin (with model.pb.bin, which names the GQO, SSMLP and MatMulNBits nodes). That decision is logged as an ORCH line in my lane's state file.

🤖 Generated with Claude Code

…ights; refuse by name and point at model.bin (Refs #1503)

The walker knew only the 1.9 spelling (<w>.empty beside <w>.packed). The 1.8
hybrid line names <w> itself as the [0] placeholder beside an empty
<w>.packed, so the node was kept as a real weight: info reported every
projection importable (2 norm gaps only) and import died on
...MatMulNBits.bias: no data. Measured on Llama-3.2-1B-Instruct and
SmolLM2-135M rai_1.8.0_hybrid. Now both refuse up front, naming model.bin.

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

ready for review at aaeccca

@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

1503 - Partially compliant

Compliant requirements:

  • The PR correctly identifies and handles the 1.8 hybrid pack case where .MatMulNBits.* initializers are placeholders with dims [0] and no data, and the actual weights are in model.bin
  • The PR adds logic to detect the 1.8 hybrid spelling of placeholders (the node names <w> itself as the [0] placeholder beside <w>.packed) and refuses them appropriately
  • The PR provides a clear refusal message that points to the model.bin location when it exists
  • The PR includes tests for the new behavior

Non-compliant requirements:

  • The PR does not address the block-32 K block size issue directly, which is the core problem described in the ticket

Requires further human verification:

  • Verification that the 1.8 hybrid packs are indeed correctly refused and that the refusal message is clear and actionable
  • Verification that the 1.9 block-32 issue is handled separately (not in this PR)
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Incorrect Placeholder Detection

The PR introduces logic to detect 1.8 hybrid pack placeholders, but it may not cover all variations of the 1.8 placeholder format. Specifically, if a placeholder tensor has a name that does not end in .packed but still represents a [0] placeholder, it might not be correctly identified and refused. This could lead to incorrect import behavior or missed refusals.

else
    packed += ".packed";
const OnnxTensor* pw = find_init(packed);
Incomplete Refusal Message

The refusal message added in the PR includes a note about weights being in model.bin, but it only checks for the existence of model.bin and does not verify that the weights are actually present in that file. This could lead to misleading refusal messages if model.bin exists but does not contain the expected weights.

std::string(fs::exists(pack_dir + "/model.bin")
                ? ". This pack's weights are in its model.bin, which this importer does not read (engine#1503)"
                : ""));

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT at aaecccc: READY

It merges cleanly onto main, and CI build at aaecccc is a success.

Checked at this head (review build at the head):

  • npu_pack_import_test: rc 0. Arm 4 prints the refusal by name: rai18_qweight (its rai18_qweight.packed sibling carries no data either) ....
  • Mutant: I put main's .empty-only rule back in the walker (else { kept.push_back(mm); continue; }). The test fails with rc 1 and 2 FAIL lines, so the arm guards the fix. After restoring the file the tree is clean.
  • npu-pack info, comparing the PR build with that mutant build (same tree, only this line differs), on the packs on this box:
    • smollm2-135m-1.8: mutant rc 0, PR rc 1, with the refusal naming the placeholder plus the model.bin sentence. This is the claimed change.
    • smollm2-135m, phi3mini-1.7.1, qwen2.5-0.5b-1.7.1, qwen3-1.7b-awq-onnx: rc 0 and byte-identical output (cmp).
  • The Llama-3.2-1B 1.8 hybrid snapshot is no longer on disk (only refs/ is left), so I did not re-run that row.

Code: real weights are still kept before the new branch (w && !no_bytes(*w)), and a node with no <w>.packed sibling is still kept as it was. So the new spelling only reaches weightless nodes, and on a pack without an .fconst the refusal still comes from #1687's rule in plan().

Non-blocking: the change also opens one path that no test covers. A weightless <w> whose <w>.packed sibling has data is now retargeted to the sibling (packed_siblings++). On main it was kept naming <w> and failed later. That is probably the right reading, but no pack or arm here exercises it.

@bong-water-water-bong
bong-water-water-bong merged commit e824fd4 into main Nov 11, 2026
9 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the cl1077/1503-external-init branch November 11, 2026 13:42
bong-water-water-bong added a commit that referenced this pull request Nov 11, 2026
…ack to use instead (Fixes #1503) (#2206)

The 1.8 hybrid packs keep every projection in model.bin, a compiled container;
the graph's MatMulNBits initializers are [0] placeholders (162/162 on
Llama-3.2-1B-Instruct). #2179 refuses them by name. Decoding model.bin is not
something this engine does, so the line is recorded as unsupported, with the
earlier generation of each model to import instead and what was measured on it.

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant