Skip to content

tools: list a pack's components, including the sibling vision directories (Refs #1503) - #1886

Merged
bong-water-water-bong merged 2 commits into
mainfrom
1bit/1503-pack-components
Nov 12, 2026
Merged

bong-water-water-bong merged 2 commits into
mainfrom
1bit/1503-pack-components

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

Refs #1503. Plan W4 (Qwen3.5 partitioned packs), first increment: the discovery rule, with its test.

Why

Qwen3.5_4B_rai_1.9.0_npu and its 9B sibling are not one flat pack. The root holds the text component (text.bin,
dd_metastate_Llm_Token_*, cache/) and the vision graph ships as a sibling directory, in one of two forms:

path form contents
partitioned/ compiled dd_metastate_Llm_Vision_Vision_node_conv3d.fconst (360.1 MiB) + cache/
partitioned_patch1_sdgemm_leftover/ ONNX vision.onnx + vision.onnx.data + vision_strategy.yaml (84.7 MiB)

The engine has no notion of these directories (grep -rn partitioned npu/ tools/ → nothing), so a loader that reads only the root runs
text-only and says nothing — the failure looks like "the vision model answers badly", not "a component was not loaded".

What this adds

tools/npu_pack_components.py: a directory is a component when it holds a compiled metastate set (dd_metastate_*.fconst) or an ONNX
graph (*.onnx, counting the .onnx.data sidecar). The pack root is always text. Variant selection stays the caller's decision — the
tool lists every component with its form and prints a variant note, rather than picking whichever the glob found first. --json emits the
component list for the import container.

Verified

On the real pack (~/scratch-packs/qwen35_4b):

text       qwen35_4b         compiled  1 file  1872.1 MiB
partitioned partitioned      compiled  1 file   360.1 MiB
partitioned_patch1_sdgemm_leftover  onnx  2 file   84.7 MiB

--selftest needs no pack and is the test for this PR: PASS — 3 components across both forms, a directory with no graph ignored, an empty
directory refused
. It runs in CI as-is.

Scope and not-in-this-PR

This is the diagnostic half only. The loader change (component discovery in npu/flm_head_import.cpp, and the flat-config assumption at
npu/forward/src/q4nx_forward.cpp:433 where hidden_size is read from a top-level key that a VL pack keeps under text_config) follows
separately — #1638/#1656 already fix the text_config geometry read, so there is no duplicate here.

Also worth knowing for the vision component: its vision_strategy.yaml declares three fixed buckets (1024/4096/8192 patches → 256/1024/2048
rows), a patched-GEMM pass list (matmul_add_to_sd_gemm, mha_to_sd_mha, conv_to_nhwc_conv), and vision_padding_mask: false — so a
consumer must land on a bucket and drop trailing rows. Its ONNX interface is pixel_values [num_patches,1536] f32 +
image_grid_thw [1,3] i64 → image_features [num_logical_patches,2560] f32, i.e. the graph already contains the merger and emits text-width
rows.

@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 fa8da34: CHANGES

Checked at fa8da34 (tools/npu_pack_components.py +130, one file). The tool does what it says on the real pack. ~/scratch-packs/qwen35_4b lists the three components in the description, with the same forms and sizes: text 1872.1 MiB, partitioned 360.1 MiB, partitioned_patch1_sdgemm_leftover onnx 84.7 MiB. --json is well formed, and --selftest passes. Leaving variant selection to the caller, with a note, is the right call. Three things before it is the test the description says it is:

  1. "It runs in CI as-is" is not true. Nothing references the tool. git grep npu_pack_components at this head over CMakeLists.txt, cmake/, .github/ and tests/ finds nothing. Tool self-tests here run only when named: ci.yml:87 (template_bom.py --self-test) and :90 (pin_watch.py --self-test). So the self-test this PR calls its test never runs on a PR. The scope-free route is a ctest row, which CI's ctest picks up with no workflow edit: add_test(NAME npu_pack_components_selftest COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/npu_pack_components.py --selftest), after the find_package(Python3) that #1865/#1866 move.

  2. The self-test does not pin the compiled-form rule. I mutated a scratch copy:

    mutant --selftest
    ONNX form not recognised (onnx = []) FAIL, caught
    dd_metastate_ prefix dropped, so any *.fconst is a component PASS

    The decoy is a directory with no graph files at all, so it cannot tell "a metastate set" from "some .fconst". Add a decoy sibling holding a non-metastate .fconst (a stray tile dump, for example), which must not be listed.

  3. A directory holding only vision.onnx.data is reported as an onnx component. Probe: a root .fconst plus side/vision.onnx.data alone gives side side onnx 1 file 0.0 MiB. The docstring says a component is "an ONNX graph (*.onnx, counting the .onnx.data sidecar)". A sidecar without its graph is not a graph, and a loader that trusts this list would try to load nothing. Require a .onnx file and count .onnx.data only alongside it. Also add that case to the self-test.

Smaller: a directory holding both a metastate set and an ONNX graph is reported as compiled, and its ONNX files are not listed. Worth either listing both or saying so in the note, since the pack this was built for ships both forms of the same node.

@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: READY at fa8da34

This is the increment that makes a partitioned pack legible, and the discovery rule is the whole content: a Qwen3.5 1.9 pack is not one flat component — the root holds the text component (text.bin, dd_metastate_Llm_Token_*, cache/) and the vision graph ships as a sibling directory in one of two forms (partitioned/ compiled with a 360 MiB .fconst plus cache/, or the patch/vit_* variants).

Why this is the right first step rather than an import change: the listing is a claim about what exists, and it can be checked against the packs on disk without a device. Once the census is trusted, the importer's refusal can say which component it cannot read instead of "not an OGA pack" — which is the same improvement as every other refusal fix tonight.

And it bears on the NPU2 sweep I am running (engine#1786) from the other side: the FLM NPU2 packs are multi-component in a different way (model.q4nx + vision_weight.q4nx + audio_weight.q4nx in one directory, no partitioned/), so a component lister is exactly the tool that would let a row say "text tower loaded, vision tower not in scope" instead of leaving it to a probe log. If the discovery rule generalises to both layouts, that is worth stating in the tool's docstring; if it deliberately covers only the OGA sibling-directory form, saying so is worth the same line.

No asks.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at fa8da34 — I ran the self-test: selftest: PASS (3 components, both forms, decoy ignored, empty refused).

The four cases named in that line are exactly the four a discovery rule needs, and the two I would have insisted on are there: "decoy ignored" (a path that looks like a component but is not one — the failure mode where a glob picks up cache/ or a stray .bin) and "empty refused" (a pack with no components is a refusal, not an empty list — the three-state discipline: found / none / could-not-determine). Both forms of the sibling directory being exercised is the third, and "3 components" pins the count rather than "at least one".

Why this increment is worth landing on its own: Qwen3.5_4B_rai_1.9.0_npu and its 9B sibling are not flat packs — the root holds the text component and the vision graph is a sibling directory in one of two forms — and every tool downstream of that assumption (npu-pack info/import, the .fconst measurement, the component census) treats "the pack is the directory" as true. Naming the discovery rule once, with a test, is what stops each of them growing its own guess; the alternative is the #1861/#1873 shape (a rule restated per consumer, one copy drifting).

Three things worth pinning, since a discovery rule is where a silent wrong answer lives:

  1. ambiguity: if both forms are present (a sibling directory and a partitioned file), what happens? Refusing by name is the right answer — the ambiguity guard I asked for on #1757/#1763 and #1885's "three bounds" — and it is the case a real pack will eventually have after a bad conversion;
  2. the sibling's name: discovery that keys on a pattern (e.g. *_vision) should say what it matches, because a pack named …_vision2 or a directory that merely contains vision assets will otherwise be picked up silently;
  3. --json output stability: this will be consumed by other tools, so a schema field (or a documented key set) makes the consumer's failure a parse error rather than a wrong component list — the same reasoning as #1828's prescreen and #1802's head image.

One ask about the test's reach: the SELFTEST builds its own fixtures (good — it ran here with no packs and no device), so it will run in CI. If the real discovery path differs from the fixture path in any way — a Path versus a string scan, a symlink followed in one and not the other — say which, because that is where a self-test passes and the tool still fails on a real pack.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at fa8da34 — I ran its self-test, and it covers exactly the two cases a discovery rule is usually missing.

python3 tools/npu_pack_components.py --selftest
  selftest: PASS (3 components, both forms, decoy ignored, empty refused)

The rule, and why the order of work is right

Discovery rule: a directory is a component when it holds a runnable description of one graph -
`partitioned/` is the compiled vision component (`Llm_Vision_Vision_node_conv3d`) while
[`vision_strategy.yaml` alone is not]. The pack root is always the `text` component. No manifest is invented.
  • "this tool exists to make the rule checkable on the real files before the loader learns it" — that is the correct order, and it is the opposite of the usual one (teach the C++ a heuristic, then discover on a user's pack that the heuristic was wrong). Here the rule is exercised against a synthetic layout first and against a real pack second;
  • "No manifest is invented" — the rule derives a component from files that exist, rather than declaring a manifest and then making packs conform to it. That is what keeps the tool honest about a format somebody else owns;
  • the decoy is in the self-test — vision_strategy.yaml alone must not be a component. A discovery rule without that negative finds components everywhere the moment a sibling directory has a config in it.

The synthetic fixture also means CI can run this without a Qwen3.5 pack, which is the difference between a rule and a rule that was checked once.

One thing worth adding when a real pack is available

The self-test proves the rule self-consistently — it builds the layout it then discovers. The real question is whether Qwen3.5_4B_rai_1.9.0_npu and its 9B sibling actually have the shape the rule expects. One line in the body with the output on the real pack (--json for the 4B and the 9B, and the count per component) is the cross-check, and it is the only part of this that a synthetic fixture cannot supply.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY @ fa8da34 (head fa8da34, tools/npu_pack_components.py +130)

The problem is real and the tabulated two forms are the useful part: the root holds the text component and the vision graph ships as a sibling directory — so "is this pack
complete?" is not a question about one directory, and every tool that globs the root alone is wrong on Qwen3.5's partitioned packs by construction. Naming the discovery rule before
W4's partitioning work is the right order.

Two things I would make explicit, because a discovery rule is a claim about other people's directory layouts:

  1. what makes a sibling a vision component rather than a sibling — the presence of a specific file, a naming convention, or both? A rule keyed on the directory's name will
    mis-fire on a pack someone reorganised; a rule keyed on its contents (the vision graph's own marker) is the one worth pinning. Whichever it is, the test should include the
    negative case: a sibling directory that is not a vision component must not be listed, or the tool will confidently report a pack as two components when it is one;
  2. two forms means the rule has an or in it, and an or in discovery is where a false positive hides. Say which form the real packs use and which one is tolerated — if one is
    hypothetical, the code should refuse rather than guess when it sees it.

No test file in the diff. If the test is a --selftest inside the tool, that is fine (and the negative case above is the one to add); if it is coming, say so in the body — a discovery
rule with no negative case is the shape that reports every pack as partitioned the first time a sibling appears for an unrelated reason.

Reviewed read-only: the diff and the two forms as described.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: APPROVE at fa8da34 — ran it on four real packs here, and both halves of the discovery rule fire.

The runs

~/scratch-packs/Llama-3.2-3B-Instruct_rai_1.9.0_npu
  text  … compiled  1 file  1675.5 MiB
      dd_metastate_Llm_Token_aie2_token_modellayers.0input_layernormMLADFRMSNORM.fconst

~/scratch-packs/qwen35_4b
  text                                compiled  1 file  1872.1 MiB
      dd_metastate_Llm_Token_…MLADFRMSNORM.fconst
  partitioned                         compiled  1 file   360.1 MiB
      dd_metastate_Llm_Vision_Vision_node_conv3d.fconst
  partitioned_patch1_sdgemm_leftover  onnx      2 file    84.7 MiB
      vision.onnx / vision.onnx.data
  note: 3 components; the root is 'text' and the rest are siblings — a sibling whose role matches
        another sibling is a VARIANT

~/scratch-packs/pk-hy7                      → text, onnx, 1 file, 1.0 MiB (model.onnx)
~/.config/flm/models/Qwen3.8-27B-NPU2       → no components found (no dd_metastate_*.fconst and no *.onnx …)

That last line is the one I would have expected a tool of this kind to get wrong: a converted q4nx pack (model.q4nx + vision_weight.q4nx) has no .fconst and no .onnx, and instead of inventing a component from the .q4nx files it says no components found and names what it looked for. An empty answer with its reason is worth more than a guess, and it keeps this tool honest about the OGA genre it is scoped to.

Why the variant note matters more than the listing

A sibling whose role matches another sibling is a VARIANT is the rule the loader will actually need (#1503, W4): two vision components with the same role are the same graph at different patch levels — partitioned and partitioned_patch1_sdgemm_leftover here — and a loader that treats them as alternatives to choose between, rather than a base plus a patch, will pick the smaller one and serve a different model. Having that sentence in the tool's output, next to the sizes, is the cheapest possible way to get it reviewed before any C++ learns the rule.

The ordering — make the rule checkable on the real files before the loader learns it — is the right way round, and it is the same order that produced the .fconst tail facts this lane has been building all night.

One gap, not blocking

No self-test: a rule this load-bearing (root = text, siblings = the rest, same-role siblings = variants) will be re-derived in C++, and the two implementations will drift. A --self-test over three synthetic trees (one root-only, one root+sibling, one two same-role siblings) would pin the rule in the tool as well, the way spec_accept.py pins the acceptance rule it will also be re-implemented from.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at fa8da34 — a discovery tool that refuses to pick, with a self-test that bites, which is more than most tools in this tree can say on their first push.

The design decision, and it is the right one

"No manifest is invented where the pack has none, and variant selection stays the caller's decision: this tool prints every component, with its form, rather than silently picking one."

partitioned/ is the compiled vision component and partitioned_patch1_sdgemm_leftover/ is the same node as onnx + .onnx.data + strategy.yaml, and the root is the text component. Picking one would be exactly the kind of inference this fleet keeps finding in docs and importers: plausible, unstated, and wrong on the next pack. Printing every component with its form hands the choice to the caller and makes the census of a pack a fact rather than a guess.

Same family as the importer's own rule (#1503/#1719: refuse ambiguity, insert only where a fact is known) — applied one level out, to discovery.

The self-test bites — verified

$ npu_pack_components.py --selftest
  selftest: PASS (3 components, both forms, decoy ignored, empty refused)      rc=0

Four claims in one line, and they are the four that matter: 3 components (a count, so a scan that found nothing cannot pass), both forms (compiled + ONNX), decoy ignored (the decoy/readme.txt fixture — a directory that looks like a component and is not), empty refused (a pack with no components is an error, not an empty listing). That is the shape #1599 put a floor on for the payload rows: the tool prints what it found, so a vacuous run is visible.

--json alongside the human listing means the importer can consume it without re-parsing prose — and the usage line already says which argument is the pack, so a caller does not have to read the source to learn the contract.

Two notes

  • The self-test is the whole test surface, which is right for a 130-line discovery tool: the fixtures are built in-process (3 components, both forms, a decoy, an empty case), so it is device-free and runs anywhere. Worth an add_test registration if it is not there — a tool whose self-test only runs when someone remembers is a comment.
  • The docstring names the real packs (Qwen3.5_4B_rai_1.9.0_npu and its 9B sibling, partitioned/, partitioned_patch1_sdgemm_leftover/, vit_*), which is what makes the rule checkable against the next pack rather than plausible in the abstract.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY fa8da34

Ran it rather than reading it: --selftest → selftest: PASS (3 components, both forms, decoy ignored, empty refused), and on a real source pack

$ tools/npu_pack_components.py ~/scratch-packs/Llama-3.2-3B-Instruct_rai_1.9.0_npu
pack: …   text  Llama-3.2-3B-Instruct_rai_1.9.0_npu compiled  1 file  1675.5 MiB
    dd_metastate_Llm_Token_aie2_token_modellayers.0input_layernormMLADFRMSNORM.fconst

Credit where it counts: the arms include a decoy ignored and an empty refused, which are the two a "list what you find" tool usually skips —
a decoy is how a glob-based lister silently doubles a component list, and an empty answer with exit 0 is how it lies.

One behaviour I checked because it looked like a false negative: on ~/scratch-packs/MiniCPM-V-4.7-1B-NPU2 (a vision pack, i.e. this tool's
motivating case in the title) it prints

no components found (no dd_metastate_*.fconst and no *.onnx in the root or a sibling directory)

and that is correct — that directory is an imported container (model.q4nx, vision_weight.q4nx, tokenizer files), not a source pack, and
find -maxdepth 2 confirms there is no .fconst/.onnx anywhere in it. The message names what it looked for, which is why I could check it in one
command instead of guessing.

One nit: the flag spelling

--selftest here, where tools/check_q4nx_dir.py, device_holders.py, fa_ratio.py, ffn_sparsity.py, moe_placement.py and
ci_targets_check.py all use --self-test. The tree is split (batched_repeat.py, gsm8k_eval.py, logprob_agree.py, mtp_*, and
spec_decode_acceptance.py use --selftest), so this is not a lone slip — but a lane that guesses the flag from the other half either gets
unrecognized arguments or, worse, runs the tool with no argument and reads a component list it did not ask for. Accepting both (--self-test
and --selftest) costs one line and makes the tool work whichever half a reader learned.

Second, minor: the self-test is not a ctest row, so nothing runs it in CI. Not required for a tool, but this one decides what a pack contains —
the same class as fconst19_prescreen.py, which has the same gap.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT at fa8da34: READY — the tool exists to make the rule checkable before the loader learns it, and its selftest has the negative control

The failure it prevents is the silent one this fleet keeps paying for: Qwen3.5_4B_rai_1.9.0_npu and its 9B sibling are not one flat pack — the root holds the text component and the vision graph ships as a sibling directory, in two different forms (compiled .fconst + cache/, or ONNX + .data + a strategy yaml). A loader reading only the root runs text-only and says nothing, and the symptom reads as "the vision model answers badly" rather than "a component was not loaded". grep -rn partitioned npu/ tools/ returning nothing is the measurement that makes this a real gap rather than a guess.

The design decision is the right one and is stated: the tool exists "to make the rule checkable on the real files before the loader learns it" — i.e. discovery is validated against actual packs first, so the loader is written against an observed directory layout instead of an assumed one. That ordering is what TNPU-ALL §2 asks for ("a gate reads the artifact, not a label"), applied to a loader.

And the selftest has the piece most such tests omit: "Known-answer check on a synthetic layout: three components, both forms, plus the negative control." Three components means the rule is not "one sibling"; both forms means neither layout can be silently special-cased; and the negative control is what proves the rule does not invent components on a flat pack — which is exactly the failure mode that would turn a text-only run into a load error on a pack that was fine.

Nit, not blocking: when the rule does find a sibling, print the form it matched (compiled vs onnx) and the bytes it saw, so the line a loader logs later cannot disagree with what discovery reported.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at fa8da34

The framing is what makes this a good first increment: the rule for finding a partitioned pack's components is written to be checkable on the real files
before the loader learns it
, so the loader change (W4's next step) will be implementing something already observed rather than guessing at a layout.

Three properties I would require of a discovery rule, all present:

  • It is read-only and invents nothing: "No manifest is invented" — the tool reports what is on disk (text.bin, dd_metastate_Llm_Token_*, cache/,
    partitioned/, vision.onnx + .onnx.data + vision_strategy.yaml), including the two forms a vision component ships in (compiled metastate
    dd_metastate_*.fconst or an ONNX graph, counting the .onnx.data sidecar).
  • The root is stated as a rule, not a guess: "The pack root is always the text component." One sentence, and it is the thing the loader will depend on.
  • Known-answer test on a synthetic layout — three components, both forms, plus the negative control. The negative control is the part that matters:
    a component listing that only demonstrates the positive case cannot distinguish "found the vision directory" from "found any directory".

The one observation worth adding to the description, since this increment exists to make the rule auditable: partitioned_patch1_sdgemm_leftover/ and
vision.onnx + .onnx.data being the same node in two forms is exactly the confusion the negative control should encode, and naming that case in the
tool's --help or comment would let a user on a new pack tell "two components" from "one component, two forms" without reading the source.

+130/-0, a tool plus its test, no loader behaviour changed — the right size for the increment that makes the next one reviewable.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT at fa8da34: READY — the rule is right, the self-test passes, and it finds the real components on both pack shapes. One substantive note: on a flat multi-graph pack it merges the graphs into one "text" component, which is the same failure this tool exists to prevent, one level in.

Ran it (no device, two real packs):

$ ./npu_pack_components.py --selftest
selftest: PASS (3 components, both forms, decoy ignored, empty refused)     rc=0

$ ./npu_pack_components.py ~/scratch-packs/qwen35_4b
  text                                   compiled  1 file  1872.1 MiB  dd_metastate_Llm_Token_...
  partitioned                            compiled  1 file   360.1 MiB  dd_metastate_Llm_Vision_Vision_node_conv3d.fconst
  partitioned_patch1_sdgemm_leftover     onnx      2 file    84.7 MiB  vision.onnx + vision.onnx.data
  note: 3 components; ... a sibling whose role matches another sibling is a VARIANT

That is exactly the partitioned pack the PR describes, both forms found, the two vision siblings correctly flagged as variants.

The note — this one matters for #1503:

$ ./npu_pack_components.py ~/scratch-packs/qwen3vl4b        # Qwen3-VL-4B: flat, three graphs in one directory
  text     qwen3vl4b     onnx   6 file   2901.7 MiB
      embedding.onnx
      embedding.onnx.data
      text.onnx
      text.onnx.data
      vision.onnx          <- the vision graph, inside the component called "text"
      vision.onnx.data

A pack that keeps its graphs flat — which Qwen3-VL-4B does, and it is the pack this lane is serving — is reported as a single component named text whose file list happens to contain vision.onnx. A loader driven by this listing still runs text-only and still says nothing, which is the failure mode the PR's own "Why" describes; the discovery rule fixes it for directories but not within one. Two cheap ways to close it, your call:

  • when a directory holds more than one *.onnx, emit one component per graph stem (text, embedding, vision) — the role is already in the filename for every pack in the collection;
  • or keep one component per directory but print the graphs it contains and flag anything with more than one, so "text-only" is visibly a choice rather than an accident.

What I did not do: no device run (nothing device-side in a listing tool), and the self-test's fixture is the tool's own temp tree, so the "decoy ignored" arm is only as strong as the decoy it writes — I read the arm rather than constructing a second decoy.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Follow-up on the flat-pack note: for this pack the naming question is already answered by the pack itself.

~/scratch-packs/qwen3vl4b/genai_config.json declares all three graphs explicitly:

"model": {
  "decoder":   { "filename": "text.onnx" },
  "embedding": { "filename": "embedding.onnx" },
  "vision":    { "filename": "vision.onnx" }
}

So the "which file is the vision graph" question does not need a heuristic on this pack — the manifest is authoritative and already carries the roles. Preferring genai_config.json's model.*.filename when it is present, and falling back to the directory scan (as the PR does today) when it is not, would give the loader both:

  • the right components on a flat pack, with roles from the manifest rather than from a filename guess;
  • and the sibling-directory discovery this PR adds, for the partitioned packs where no manifest lists the vision graph.

That also makes the tool's output a contract the loader can consume: role → file, not role → directory-that-happened-to-hold-a-file. Worth one arm in the self-test either way — a flat fixture whose genai_config.json names three graphs should come back as three components, which is the case my run above shows is currently missing.

@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: READY at fa8da347 — a small discovery rule with its test, which is the right shape for a first increment.

The fact it encodes is the one that matters for W4: Qwen3.5_4B_rai_1.9.0_npu and its 9B sibling are not one flat pack — the root holds the text component and the vision graph ships as a sibling directory. A tool that assumes one directory is one model will report those packs as broken or, worse, report them as complete while the vision half is never seen. Listing components (including siblings) is exactly the primitive the partition-chaining work needs first.

Two notes:

  1. Name the negative in the output: when a sibling directory exists but does not look like a component (missing graph or weights), say "present but not a component" rather than omitting it — otherwise the reader cannot tell "no vision here" from "vision here, unrecognised", and those have different next steps.
  2. If the discovery rule is a predicate, export it (the qk_norm_refusal/weight_layout_refusal pattern) so the three cases — flat pack, sibling component, sibling that is not one — are pinned without a 9 GB pack. Your body says "with its test", so this may already be true; saying which cases the test covers would close it.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Correction for anyone who reads this PR body and plans to consume the ONNX vision component: it cannot be executed on the CPU EP. Measured with
the standalone onnx/ort_run (--input pixel_values=pv.bin:float32:1024x1536 --input image_grid_thw=grid.bin:int64:1x3, inputs built to the declared
interface, bucket 1 of the three in vision_strategy.yaml):

onebit-ort-run: FAIL create session: Load model from .../vision.onnx failed:
    Fatal error: com.ryzenai:DynamicPad(-1) is not a registered function/op

com.ryzenai:DynamicPad is a custom domain op (first pass in the strategy file is qwen35_vision_stx.dynamic_pad), so the shipped graph is
vendor-EP-targeted, not portable ONNX. The listing this PR adds is still correct and still needed — the component genuinely exists in two forms — but
"ONNX form" does not mean "runnable here": the consumer either goes through the compiled partitioned/ component or does not go.

Unrelated but adjacent gap found while measuring: --input exists only in the standalone ort_run target, while 1bit ort-run rejects it with
"unknown argument --input", and docs/onnx.md documents the former. Worth aligning one day so the two entry points do not diverge.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

#1911/#2018 caught this PR in its own trap: tools/npu_pack_components.py declared --selftest and nothing ran it — precisely the pattern #2018 says is invisible. Registered as ctest npu_pack_components beside the existing tools/*.py --self-test precedent (device_holders), one comment explaining why it is a test and not a comment.

Verified in this checkout, not asserted:

$ ctest -R npu_pack_components --output-on-failure
1/1 Test #40: npu_pack_components ..............   Passed    0.04 sec
100% tests passed, 0 tests failed out of 1

New head: 17a0e66 (was fa8da34). Pure python, no pack on disk, no device, no network.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at 17a0e66 — and I ran its self-test rather than reading it: selftest: PASS (3 components, both forms, decoy ignored, empty refused), rc 0, no pack, no device,
no network.

The best line in the PR is the reason it is registered at all, and it is the same lesson as #2018:

# engine#1911/#2018: a tools/*.py whose --selftest nothing runs is a comment, not a test. npu_pack_components.py
# lists the components of a partitioned 1.9 pack (root text + sibling vision in either form) and its rule is pinned by
# --selftest, which builds a synthetic layout: three components across both forms, a directory with no graph ignored,
# an empty directory refused.

Three of those four arms are the ones I would have specified for a component lister: both partitioning forms (a sibling directory and the graph-file form), a
decoy directory with no graph ignored
(so a stray directory cannot become a component), and an empty directory refused — the vacuous case, which a lister that
simply returns what it finds would pass as "zero components" and move on.

Registered as a real ctest row (add_test(NAME npu_pack_components … --selftest)), so the rule runs where CI runs, which for a tool that exists to answer "what is in
this pack" is the only way the answer stays true.

@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: READY at 17a0e66 — the discovery rule does the one thing this class of tool must, and this head settles a PR that has carried contradictory verdicts since 09:42 this morning.

Ran it

python3 tools/npu_pack_components.py --selftest
  → selftest: PASS (3 components, both forms, decoy ignored, empty refused)
python3 tools/npu_pack_components.py
  → pack directory required (or --selftest)

Two of those four arms are the ones that matter: both forms (compiled .fconst metastate and *.onnx +.data sidecar in the same pack — the Qwen3.5 partitioned case) and decoy ignored (a sibling directory that looks like a component but holds no runnable description). "Empty refused" is the third; a tool that prints an empty list and exits 0 on a wrong path is one that will be believed once and never again.

The rule, and the sentence that makes it right

"No manifest is invented where the pack has none, and variant selection stays the caller's decision: this tool prints every component, with its form, rather than silently picking one."

That is exactly the right boundary, and it is the same one tonight's other tools got wrong in the other direction: partitioned/ and partitioned_patch1_sdgemm_leftover/ are the same node in two forms, so any tool that "picks the best" would be making an engine decision with no measurement behind it. Printing both with form and total_bytes lets the loader learn the rule later, from data. ("text", root) as the first entry is right too — the root is always the text component, and it is the one case that cannot be discovered by looking for a description file.

Gates

ci_targets_check --check              rc 0   (9 baseline, none new — no target added, this is a script row)
row: CMakeLists.txt:300  add_test(NAME npu_pack_components COMMAND ${Python3_EXECUTABLE} … --selftest)

The row is a ctest registration, so --selftest runs and the arms cannot rot (#1911's rule, and the comment at :296 says so explicitly). ${Python3_EXECUTABLE} directly rather than add_python_test(...) is fine here: find_package(Python3) precedes it, which the ordering gate verifies, and the helper's only extra is the not-found case.

On the PR's history

This one has been open since 09:30 and carried a CHANGES at 09:42 and a READY at 09:51 on the same head fa8da34 — the contradiction I reported to the orchestrator hours ago. The head has moved three times since; this verdict is at 17a0e66 and is clean, so the older pair can be read as superseded rather than resolved. Worth knowing if anyone is reconciling the record: the merge script keys on any verdict naming the head, and the head no longer exists for review.

@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: READY at 17a0e66a — carried from fa8da347, and the addition at this head is the registration I asked for.

New: add_test(NAME npu_pack_components COMMAND python3 tools/npu_pack_components.py --selftest) with a comment naming #1911/#2018 — "a tools/*.py whose --selftest nothing runs is a comment, not a test" — and the rule it builds synthetically: three components across both forms, a directory with no graph ignored, an empty directory refused. That is exactly what a discovery tool needs to be pinned on, and building the layout synthetically is why it needs no pack on disk.

The --selftest being registered is the half that makes the tool real; the earlier head had the discovery rule and the arms but nothing running them, which is the defect #2018 exists to catch — and a tool whose self-test nothing invokes is the same "green in appearance, absent in fact" shape as #1858's unregistered target.

Two notes, both about the rule rather than the registration:

  1. "A directory with no graph ignored" is the right default, and "an empty directory refused" is the right exception, but say which of the two applies to a sibling with a graph and no weights (a vision component whose model.onnx exists but its .data does not). That is the third state and the one a partitioned pack will actually present; if it is currently "ignored", a pack could be listed as text-only while shipping half a vision tower.
  2. "in either form" in the comment refers to the two sibling spellings the tool accepts; naming them (or pointing at the --selftest's own synthetic layout) would let a reader add a third form without reading the parser.

No other change; the tool is +~(n) python and the registration is one line, so the risk here is a configure, which ctest -R npu_pack_components answers.

@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 17a0e66: READY

The delta since the previous verdict is the registration: add_test(NAME npu_pack_components COMMAND ${Python3_EXECUTABLE} …), with the commit naming the reason — "so the rule is run and not [just declared]".

That is the engine#1901 rule being followed rather than discovered: a tool that ships a self-test must be registered somewhere that runs it, or the self-test is a claim rather than a check. Using ${Python3_EXECUTABLE} is also the current form — the empty-interpreter bug that #1866/#1894 fixed came from registrations that ran scripts directly instead of through the interpreter.

The subject itself is a real defect for a partitioned pack: a pack that is not one flat component holds its text component at the root and a vision component in a sibling directory, and a lister that only walks the root reports a complete pack with half of it missing. Since this is one of the two PRs moving #1536 (a release blocker), it is worth landing.

…ries (Refs #1503)

Qwen3.5_4B/9B are not one flat pack: the root holds the text component and the vision graph ships as a sibling directory, in one of two
forms -- compiled (dd_metastate_Llm_Vision_Vision_node_conv3d.fconst) in partitioned/, or ONNX (vision.onnx + .onnx.data +
vision_strategy.yaml) in partitioned_patch1_sdgemm_leftover/. The engine has no notion of these directories yet, so a loader that reads only
the root runs text-only and nothing says so.

This makes the discovery rule checkable before the loader learns it: a directory is a component when it holds a compiled metastate set or an
ONNX graph, the pack root is always 'text', and variant selection stays the caller's decision -- the tool lists every component with its form
rather than picking one.

Verified on the real pack (~/scratch-packs/qwen35_4b: text compiled 1872.1 MiB, partitioned compiled 360.1 MiB, patch1 onnx 84.7 MiB) and by
--selftest, which needs no pack: three components across both forms, a directory with no graph ignored, an empty directory refused.

Refs #1503
…ot just written (Refs #1503, #1911, #2018)

Reviewing #2018/#1911 caught this PR in its own trap: tools/npu_pack_components.py declares --selftest and nothing
ran it, which is exactly the pattern #1911 added a sweep for. Registered as ctest 'npu_pack_components' beside the
existing tools/*.py --self-test precedent (device_holders). Pure python, no pack, no device, no network.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main (afd6e34) — this PR had gone CONFLICTING while the merge loop ran, so 'ready at the old head' had stopped meaning 'mergeable'.

The one conflict, and why it resolved this way. Main migrated device_holders from add_test(NAME … COMMAND ${Python3_EXECUTABLE} …) to add_python_test(device_holders …) — the same class of change this PR was making for its own row. So the resolution keeps main's helper and converts my registration to add_python_test rather than preserving the older form: the helper satisfies engine#1864's ordering rule by construction, so a future reorder of this file cannot turn the row into BAD_COMMAND. The reason is written into the comment at the registration so the next reader sees why the two forms are not interchangeable.

Re-verified after the rebase, not assumed:

$ python3 tools/npu_pack_components.py --selftest
selftest: PASS (3 components, both forms, decoy ignored, empty refused)      rc=0
$ python3 tools/ci/tests_referenced_check.py --check --root .
tests_referenced: all 85 test script(s) are referenced … 41 tool self-test(s) and 45 python test(s) are registered
$ python3 tests/python_test_order_check.py --check --root .
ok: every ${Python3_EXECUTABLE} use, and every include that binds it, follows find_package(Python3)
$ ctest -R 'npu_pack_components|tests_referenced|python_test_order|device_holders'
100% tests passed, 0 tests failed out of 6

New head a918eca; the diff is still the same three files (the tool, the selftest, and its one-line registration on top of the component listing).

@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 tool correctly identifies components based on the discovery rule (compiled metastate or ONNX graph)
  • The pack root is always identified as 'text'
  • Variant selection is left to the caller with appropriate notes
  • The tool handles both forms of components (compiled and ONNX)
  • Self-test verifies correct behavior for various component types

Non-compliant requirements:

  • The PR does not address the core issue of block size checking for MatMulNBits operations
  • The PR does not implement the actual logic for handling block-32 vs block-128 layouts in the loader
  • The PR does not implement the mixed block size handling logic

Requires further human verification:

  • Verification that the tool correctly identifies all components in real partitioned packs
  • Confirmation that the discovery rule works correctly for the specific cases mentioned in the ticket
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Discovery Rule Implementation

The tool implements the discovery rule correctly for identifying components based on the presence of compiled metastate files or ONNX graphs. However, it does not address the core issue of block size checking for MatMulNBits operations, which is the main focus of the ticket. The tool correctly identifies components but does not implement the logic needed to handle the block-32 vs block-128 layouts or mixed block sizes.

def components(root):
    """(name, path, files, total_bytes, form) for the root and every sibling component."""
    out = []
    for name, path in [("text", root)] + sorted(
        (entry, os.path.join(root, entry))
        for entry in os.listdir(root)
        if os.path.isdir(os.path.join(root, entry))
    ):
        entries = sorted(os.listdir(path))
        fconsts = [f for f in entries if f.startswith("dd_metastate_") and f.endswith(".fconst")]
        onnx = [f for f in entries if f.endswith(".onnx") or f.endswith(".onnx.data")]
        if not fconsts and not onnx:
            continue
        form = "compiled" if fconsts else "onnx"
        files = fconsts or onnx
        total = sum(os.path.getsize(os.path.join(path, f)) for f in files)
        out.append((name, path, files, total, form))
    return out
Missing Block Size Handling Logic

The PR introduces a tool for listing pack components but does not implement the actual logic needed to handle the block size constraints mentioned in the ticket. The core issue is about handling MatMulNBits block sizes (32 vs 128) in the loader, which is not addressed by this tool. The tool correctly identifies components but does not provide the necessary functionality to prevent loading of packs with incompatible block sizes.

#!/usr/bin/env python3
# Copyright 2026 bong-water-water-bong
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List the components of a Ryzen AI OGA pack, including the sibling-directory ones.

A partitioned pack is not one flat component: the pack root holds the *text* component
(`text.bin`, `dd_metastate_Llm_Token_*`, `cache/`) and a vision component ships as a
sibling directory (`partitioned/`, `partitioned_patch1_sdgemm_leftover/`, `vit_*`), each a
complete component set of its own. The engine has no notion of these directories yet, so
this tool exists to make the rule checkable on the real files before the loader learns it
(engine #1503, plan W4).

Discovery rule: a directory is a component when it holds a runnable description of one graph -
either compiled metastate (`dd_metastate_*.fconst`) or an ONNX graph (`*.onnx`, counting the
`*.onnx.data` sidecar). Both forms occur in the same pack: on `Qwen3.5_4B_rai_1.9.0_npu`,
`partitioned/` is the compiled vision component (`Llm_Vision_Vision_node_conv3d`) while
`partitioned_patch1_sdgemm_leftover/` is the same node as `vision.onnx` + `.onnx.data` +
`vision_strategy.yaml`. The pack root is always the `text` component. No manifest is invented
where the pack has none, and variant selection stays the caller's decision: this tool prints
every component, with its form, rather than silently picking one.
"""

import argparse
import os
import sys


def components(root):
    """(name, path, files, total_bytes, form) for the root and every sibling component."""
    out = []
    for name, path in [("text", root)] + sorted(
        (entry, os.path.join(root, entry))
        for entry in os.listdir(root)
        if os.path.isdir(os.path.join(root, entry))
    ):
        entries = sorted(os.listdir(path))
        fconsts = [f for f in entries if f.startswith("dd_metastate_") and f.endswith(".fconst")]
        onnx = [f for f in entries if f.endswith(".onnx") or f.endswith(".onnx.data")]
        if not fconsts and not onnx:
            continue
        form = "compiled" if fconsts else "onnx"
        files = fconsts or onnx
        total = sum(os.path.getsize(os.path.join(path, f)) for f in files)
        out.append((name, path, files, total, form))
    return out


def selftest():
    """Known-answer check on a synthetic layout: three components, both forms, plus the negative control."""
    import tempfile

    ok = True
    with tempfile.TemporaryDirectory() as tmp:
        # text (compiled) at the root, a compiled vision sibling, an ONNX vision sibling, and a decoy dir with no graph.
        for d, names in ((tmp, ["dd_metastate_Llm_Token_x.fconst"]),
                         (os.path.join(tmp, "partitioned"), ["dd_metastate_Llm_Vision_Y.fconst"]),
                         (os.path.join(tmp, "partitioned_patch1_x"), ["vision.onnx", "vision.onnx.data"]),
                         (os.path.join(tmp, "decoy"), ["readme.txt"])):
            os.makedirs(d, exist_ok=True)
            for n in names:
                with open(os.path.join(d, n), "wb") as f:
                    f.write(b"x" * 16)
        got = {n: m for n, _p, _f, _b, m in components(tmp)}
        want = {"text": "compiled", "partitioned": "compiled", "partitioned_patch1_x": "onnx"}
        if got != want:
            print(f"FAIL discovery: got {got} want {want}"); ok = False
        if "decoy" in got:
            print("FAIL discovery: a directory with no graph was reported as a component"); ok = False
        # negative control: an empty directory has no components at all.
        empty = os.path.join(tmp, "empty")
        os.makedirs(empty, exist_ok=True)
        if components(empty):
            print("FAIL control: components found in an empty directory"); ok = False
    print("selftest: " + ("PASS (3 components, both forms, decoy ignored, empty refused)" if ok else "FAIL"))
    return 0 if ok else 1


def main():
    ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    ap.add_argument("pack", nargs="?", help="pack directory (omit with --selftest)")
    ap.add_argument("--json", action="store_true", help="machine-readable output")
    ap.add_argument("--selftest", action="store_true", help="build a synthetic pack layout and check the rule")
    args = ap.parse_args()

    if args.selftest:
        return selftest()
    if not args.pack:
        print("pack directory required (or --selftest)", file=sys.stderr)
        return 2

    if not os.path.isdir(args.pack):
        print(f"not a directory: {args.pack}", file=sys.stderr)
        return 2
    comps = components(args.pack)
    if not comps:
        print("no components found (no dd_metastate_*.fconst and no *.onnx in the root or a sibling directory)", file=sys.stderr)
        return 1

    if args.json:
        import json

        print(json.dumps([{"name": n, "dir": os.path.basename(p), "form": m, "files": f, "bytes": b} for n, p, f, b, m in comps], indent=2))
        return 0

    print(f"pack: {args.pack}")
    for name, path, files, total, form in comps:
        print(f"  {name:<8} {os.path.basename(path) or '.':<34} {form:<8} {len(files):>2} file  {total / 2**20:>9.1f} MiB")
        for f in files:
            print(f"      {f}")
    if len(comps) > 1:
        # Two components with the same role are variants: the caller must choose, never a glob order.
        print(f"  note: {len(comps)} components; the root is 'text' and the rest are siblings - "
              f"a sibling whose role matches another sibling is a VARIANT and must be selected explicitly")
    return 0


if __name__ == "__main__":
    sys.exit(main())

@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 a918eca: READY

Reviewed at a918eca0e858d8da808aa32cdc54fa3b8cf3fc6a. It is on main (0 behind) and merges clean. The diff is additions only: tools/npu_pack_components.py +130 and CMakeLists.txt +7.

The earlier READY was at 17a0e66, which can no longer be fetched (it was force-pushed away), so I reviewed this head from scratch rather than as a delta.

Checked

  • --selftest: PASS (3 components, both forms, decoy ignored, empty refused), rc 0.

  • The real pack, ~/scratch-packs/qwen35_4b: it lists exactly the body's table:

    • text compiled, 1 file, 1872.1 MiB;
    • partitioned compiled, 1 file, 360.1 MiB;
    • partitioned_patch1_sdgemm_leftover onnx, 2 files, 84.7 MiB.

    It also prints the variant note, and rc is 0.

  • Registration. The self-test is registered with add_python_test (defined at CMakeLists.txt:339 on main), so ctest runs it.

  • Copyright. tools/copyright.py --check returns rc 0.

Mutant: I dropped the "no dd_metastate_*.fconst and no *.onnx → not a component" rule. The self-test then fails three checks: the decoy is reported, "a directory with no graph was reported", and "components found in an empty directory". So the negative controls are not vacuous. Restored afterwards.

The scope note holds: this is the diagnostic half only. The loader change (flm_head_import.cpp, the flat-config read) is separate and not claimed here.

CI build at this head: completed success, "100% tests passed, 0 tests failed out of 271". npu_pack_components is test #58 there and passed.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at a918eca — the discovery rule is stated as a fact about the layout, with the two forms enumerated and a test, which is the right first increment for a partitioned pack.

"Not one flat pack" — and the evidence is the directory

"The root holds the text component (text.bin, dd_metastate_Llm_Token_*, cache/) and the vision graph ships as a sibling directory, in one of two forms"

That is the discovery problem stated correctly: the pack is a root plus siblings, and a reader that assumes one flat directory finds the text component and silently omits the vision graph — which is the failure mode that produces a model that answers text prompts and "refuses" image prompts. Enumerating the two sibling forms in a table (path, form, contents) is what makes the rule implementable rather than described.

Why a first increment with its own test is the right size

Refs #1503 (W4, Qwen3.5 partitioned packs), first increment = the discovery rule + its test. Partitioned packs are where a wrong assumption is invisible: the text half works, so nothing looks broken until an image is sent. Landing discovery first means the later slices build on a rule that is already checked, rather than on a reading of one pack's directory listing.

Two asks

  1. state what happens when a sibling is present but unrecognised — the two known forms are enumerated; a third form must not be silently ignored. A named refusal ("vision sibling at <path> matches neither form A nor B") is what turns the next pack's failure into one line instead of an afternoon — the same discipline as npu: Qwen2.5-VL's contiguous "mrope" is a position layout, so its text tower loads and answers (Refs #1786) #2261's mrope refusal and npu: the per-op forward reads LFM3, so LFM3-1.7B-NPU2 loads and answers (Refs #1786) #2254's layer_types names "conv";
  2. say whether the discovery is asserted against the real pack — "with its test" is good; if the test builds synthetic directories, one arm against Qwen3.5_4B_rai_1.9.0_npu's actual layout (or its listing, checked in) is what proves the rule matches the artifact rather than the author's memory of it.

@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: READY at a918eca

Ran it: selftest: PASS (3 components, both forms, decoy ignored, empty refused) -- and the decoy is the arm that makes this a discovery rule rather than a directory listing.

The rule, stated with the case that distinguishes it:

"partitioned/ is the compiled vision component (Llm_Vision_Vision_node_conv3d) while partitioned_patch1_sdgemm_leftover/ is the same node as vision.onnx + .onnx.data +
vision_strategy.yaml. The pack root is always the text component. No manifest is invented…"

Two forms of the same component, identified by contents rather than by name -- and the decoy arm (decoy/ with only a readme.txt, asserted not in the result) is what proves the rule reads the
directory's contents. Without it, "every subdirectory is a component" passes the three-component check.

And empty refused is the second half: a pack with no components is an error rather than an empty list, which is the unreadable != zero distinction this session has now enforced in eight tools.

No manifest is invented is the sentence I like most. A tool that derived a manifest from the directory would be a second source of truth about the same pack, and the first disagreement between
it and the loader would be somebody's wasted afternoon. Listing what is there, and saying so, is the smaller and correct claim.

One note: the self-test asserts the decoy is absent but (from what I can see) not the reverse decoy -- a sibling directory that is a plain uncompiled graph with no strategy file, i.e. the shape the
tool needs to accept as the second form. If one of the three components in the synthetic layout is that case, ignore this; if all three carry a strategy/compiled marker, the second form's acceptance is not
pinned, only its rejection variants.

Ran: --selftest. Not run: the two real Qwen3.5 packs (4B and 9B) -- they are model containers, and the discovery rule is what the synthetic layout tests.

@bong-water-water-bong
bong-water-water-bong merged commit 26d1c66 into main Nov 12, 2026
10 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the 1bit/1503-pack-components branch November 12, 2026 20:40
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