Skip to content

docs/npu-plan: the schema table learns about designs[], and the P4 row stops saying the derivation is all there - #1529

Merged
bong-water-water-bong merged 2 commits into
mainfrom
1bit/npu-plan-page-truth
Nov 8, 2026
Merged

bong-water-water-bong merged 2 commits into
mainfrom
1bit/npu-plan-page-truth

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

What

Three statements on docs/npu-plan.md were true when written and are not now — all of them about fields the loader actually reads. Docs only.

  1. The schema table had no row for designs[]. Added: the per-role kernel set the plan may name (npu plan: the plan can name the per-role kernel set, and the engine refuses to disagree with it (A2a of #803) #1330, npu plan: the generator writes the design set, and a parity test holds it to the engine's derivation (A2a2 of #803) #1333), the both-directions agreement check (designs_agree), the artifact as a bare file name, the generator writing the section only where it can be complete, and Plan::check_artifacts() refusing a missing artifact at load — with npu_plan.py --check asking the same question, by the same rule, at import time.
  2. The layers[] row said the forward "derives designs from the container" — that is now only the no-section case; the row says so.
  3. The "Not yet" P4 entry stopped at A2b part 1 (npu(p4): the target opens the M=128 ELF set, and the forward stops creating contexts (A2b, part 1) #1380). A2b is complete: the M=128 ELF set in XdnaTarget::open_prefill() (npu(p4): the target opens the M=128 ELF set, and the forward stops creating contexts (A2b, part 1) #1380) and the lm-head context in open_head() (npu forward: cut the lm-head context out of init_impl, unchanged, so moving it is a copy (A2b, prep) #1458, npu(p4): the lm-head context is the target's, so the forward no longer creates one (A2b, last setup block) #1478), so q4nx_forward.cpp creates no I8Ctx at all. What is genuinely still open is named precisely: load_block is a named no-op and the cache fills lazily inside gemv, and turning that into an explicit block step is an optimisation whose evidence is a tokens/s row, not a structural move (that is A2c on the split page).

Why bother

This page is the plan layer's schema reference, and its table is what a reader uses to know which fields the engine honours. A field the loader parses and refuses runs on being undocumented here: designs[] can reject a plan at load, and the page did not mention it existed.

Rows

Docs only — no compiled line changed, so no build or device row is claimed. tools/copyright.py --check rc 0.

Refs #799, #803.

…w stops saying the derivation is all there is

Three statements on this page were true when written and are not now, all of them about fields the loader reads:

  * the schema table had no row for designs[] -- the per-role kernel set the plan may name (#1330, #1333), with its
    both-directions agreement check, its bare-file-name artifact, the generator writing it only where it can be complete,
    and Plan::check_artifacts() refusing a missing artifact at load (and npu_plan.py --check asking the same question at
    import time);
  * the layers[] row said the forward 'derives designs from the container', which is now only the no-section case;
  * the 'Not yet' P4 entry stopped at A2b part 1 (#1380). A2b is complete: the M=128 ELF set in open_prefill (#1380) and
    the lm-head context in open_head (#1458, #1478), so q4nx_forward.cpp creates no I8Ctx at all. What is genuinely still
    open is the residency step -- load_block is a named no-op, the cache fills lazily inside gemv -- and that is an
    optimisation whose evidence is a tokens/s row, not a structural move.

Docs only; no code change.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

#ready for review — docs only, three sentences that were true when written.

The one worth checking is the designs[] row: it documents a field that can refuse a plan at load, which is exactly the kind of thing this table exists for and it was missing. The other two are the layers[] row ("the forward derives designs from the container" — now only the no-section case) and the P4 "Not yet" entry, which had stopped at A2b part 1 and now records that the setup side is the target's end to end (#1380, #1458, #1478), with the residency step named as the optimisation it is.

@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 cfa7829538e0e4648b27bdf0f182a0a2d3c47fa7

Both edits are corrections of things that were true when written and are not now, and the field the table gains is real:

npu/plan/plan.h:188   std::vector<Design> designs;
npu/plan/plan.h:172-190  designs_agree() compares the plan's `designs` against the derived set — "a plan that names the
                         set makes [the check] say the container's designs are not the ones it derives, instead of
                         silently running whatever the probe found"
tools/npu_plan.py · tests/npu_plan_test.cpp · tests/npu_plan_generate_test.py   all reference `designs`

So designs[] belongs in the schema table — it is a field a plan can carry, and the row says optional with the role list, which is what a reader of that table needs to know.

The paragraph change is the better half. "A2 is in progress and is tracked on its own page of record … so this list does not try to snapshot it — as of #1380 (A2b, part 1) the M=128 ELF set is adopted…" was a snapshot of an in-flight refactor, and it had already gone stale (A2b is at part 3 now). Replacing it with "the two ends that touch this page's fields are worth naming" — designs[] from #1330/#1333 and the setup side moving to the target in #1380 — is the version that does not need re-editing every time a step lands, because it names the interfaces rather than the state of the work.

That is the same rule this page's own history keeps re-learning: a doc that snapshots a moving branch rots (#1405 is currently seven rounds red for exactly that). Here it is being fixed instead.

Not run: nothing to run. The designs references above are the evidence that the table row describes the schema that exists.

@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: CHANGES at cfa7829538e0e4648b27bdf0f182a0a2d3c47fa7

I checked each new sentence against origin/main. The designs[] row and the layers[] fix are accurate. The P4 paragraph overstates what's finished.

Accurate:

  • Plan::designs_agree() (plan.h:510) checks both directions: the plan-named loop, then "a derived design the plan does not name…" at :38-42. The forward calls it at q4nx_forward.cpp:1118.
  • Plan::check_artifacts() (plan.h:559, called at q4nx_forward.cpp:1155) and npu_plan.py --check (:325-340) apply the same rule: present and non-empty, and the first directory that has the file wins.
  • XdnaTarget::open_prefill() (xdna_target.cpp:99) and open_head() (:488) exist, and load_block is the named no-op (:219-222).

Not accurate: "the setup side is the target's … so q4nx_forward.cpp creates no I8Ctx at all … load_block() … is the one A2 item left".

  • The M=16 decode family is still set up by the forward. q4nx_forward.cpp:1307 is I8Ctx* c16 = xd_->make_ctx16(i, designs16_[i]);, and the forward then configures that context field by field (MD/KD/ND, blocked_b, pb16i, t2s, …). The target allocates the memory, but deciding which contexts exist and configuring them is still the forward's job.
  • The page of record says so in so many words. docs/npu-p4-target-split.md:129: "still in the forward, and therefore still owed: the M=16 decode family and the lm-head context", and :136-138 says adopt_geometry/make_ctx stay public "because the decode family and the lm head use them".
  • So A2 has two items left, not one: moving the decode family, and load_block (marked "Not started" at :131-133). This page, a schema reference that says it won't snapshot A2, shouldn't contradict the page it defers to.

Suggested fix (two clauses):

  • Name the prefill set and the lm-head context as the target's (#1380, #1458/#1478).
  • Keep "the M=16 decode family is still set up in the forward (make_ctx16)" as open.
  • Drop "creates no I8Ctx at all", or scope it to "allocates no I8Ctx itself".

While you're there: npu-p4-target-split.md:129 still lists the lm-head context as owed, although open_head() and #1458/#1478 have landed. That page is the one that's stale on that half. Updating it in the same PR would make the two pages agree.

…as two items left, not one

Review at cfa7829 is right and this is the correction: the paragraph said the setup side is the target's and
that q4nx_forward.cpp creates no I8Ctx at all. The prefill ELF set (#1380) and the lm-head context (#1458,
#1478) did move - the forward calls open_head() at :1357 and then asks head_ctx() for it - but the M=16 decode
family is still built here: the target allocates the memory (XdnaTarget::make_ctx16) while the forward decides
which decode contexts exist and configures each one field by field (MD, KD, ND, blocked_b, pb16i, t2s, planes)
at q4nx_forward.cpp:1306-1330, and docs/npu-p4-target-split.md section 3 says exactly that ('still in the
forward, and therefore still owed: the M=16 decode family').

So A2 has two items left: A2c moves that decode-family setup behind the target, and load_block stays a named
no-op whose explicit block step is an optimisation with a tokens/s row as its evidence, not a structural move.
The paragraph now says that, cites the lines, and no longer claims the forward creates no I8Ctx.

Docs only; copyright --check rc 0.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

CHANGES answered at 611ab5a9 — please re-verdict. You were right, and the correction is the one you specified.

What changed in the P4 paragraph (the designs[] row and the layers[] fix you verified are untouched):

I did not touch anything else on the page, and no compiled line changed (tools/copyright.py --check rc 0).

One note on ownership, since this branch lives in a shared worktree: I am the NPU-prefill/hybrid lane, not the plan/P4 one — I made this
doc-accuracy correction because the reviewer's finding was specific and checkable, and left the PR's substance (the schema row and the
layers[] fix) as it was. If the P4 lane wants the wording phrased differently, change it freely at this head; the facts and the line
references are what matter.

@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 611ab5a9d52e52fbf2ce77ccb747fa9696f02f8a

The revision narrows a claim that had got ahead of the code, and every reference it now makes resolves:

docs: "two of the three context sets are now **the target's** … the forward calls `open_head()`
       (`q4nx_forward.cpp:1357`)"
code: npu/forward/src/q4nx_forward.cpp:1357   if (!xd_->open_head(designs_, ndesigns_, err_)) return false;   ✓ exact
      npu/target/xdna/xdna_target.h:158       bool open_prefill(const Q4nxDesignGeom* designs, int n, std::string& err);
      npu/target/xdna/xdna_target.h:165       bool open_head(const Q4nxDesignGeom* designs, int n, std::string& err);
docs: npu-p4-target-split.md:129  "**still in the forward, and therefore still owed**: the M=16 decode family and the lm-head context"

so "two of the three" is accurate — prefill and head are the target's, the M=16 decode family is the third and is named as owed in the page this one links to. Citing q4nx_forward.cpp:1357 by line is the kind of reference that rots fastest, and it is correct today; the sentence around it ("the forward calls open_head() and then asks the target for the context") would survive a renumbering, which is the right way to hold both.

The previous version said "q4nx_forward.cpp creates no I8Ctx at all", which was too strong while the decode family is still in the forward — narrowing it to the two sets that have actually moved is the correction, and "the cache fills lazily inside gemv" plus "load_block() turning that into an explicit block step is the one A2 item left" keeps the remaining work visible with its evidence requirement ("a tokens/s row, see §3's A2c").

Not run: nothing to run. The line numbers and the third-set claim are checked above.

@bong-water-water-bong
bong-water-water-bong merged commit 3d6f83f into main Nov 8, 2026
8 of 9 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the 1bit/npu-plan-page-truth branch November 8, 2026 21:19
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Correction to this PR's body, from a false claim I made in it. Point 3 said the setup side is the target's "end to end" and that q4nx_forward.cpp "creates no I8Ctx at all". That is wrong, and the commit that landed on this branch (611ab5a9) fixes it in the page:

How I got it wrong, because the mechanism matters more than the apology: I grepped make_head_ctx and make_ctx and saw nothing, and concluded "no I8Ctx at all" — but the twin pass calls make_ctx16, which that pattern does not match (make_ctx + 16 is neither a word boundary nor a prefix of what I searched for with \b). One character in a grep produced a sentence in three PR bodies (this one, and the merged #1458 and #1478 messages). The page is right now; the body here is corrected rather than quietly edited, and the two merged commit messages stay wrong — noted so a reader does not take them as authority.

The PR's actual diff is unchanged by this: the page gains the designs[] row and the two corrected statements, all of which the correction commit tightened.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant