Repository navigation
npu: the decode-family block gets its own method, body unchanged (engine#803 A2b step one) - #2062
Conversation
…ine#803 A2b step one) A2b moves the M=16 decode family behind XdnaTarget::open_decode_family(), mirroring open_head (#1478). This is the step that makes that move a copy instead of surgery inside init_impl -- the same step the lm head went through before it became open_head, and the tree's own comment on that block says so. What moved: q4nx_forward.cpp's 'if (want_m16 \&\& !host_gemm_) { ... }' block (157 lines) is now Q4nxNpuForward::open_decode_family_local(bool want_m16, xrt::device* dev, const std::string\& mdir, const std::string\& dir) -- body copied verbatim, de-indented one level, with the three locals it reads threaded in as parameters. Two things deliberately unchanged, because gates read them: * the log strings are byte-for-byte ('[forward] ... (decode)', '[forward] decode family: N role(s) at M=16 ...', the pb16i 'N of M designs have a decode kernel' line) -- the first is grepped by [pb16i-trace] and the second by the window11 gates, so improving a message here breaks a gate that reads it; * the guard shape is preserved: the call site still passes want_m16 (from NPU_DECODE_M16), and the method still prints the 'disabled' line rather than silently returning. Verified: builds (onebit, with the dx add-on and the private runtime), and npu_forward_loader_test passes ('all cases ok'). NOT included, and stated on the issue so it is not assumed: the move into XdnaTarget itself -- the block reaches six forward members (family_tag(), designs_, designs16_, pb16i_, t2s_, host_gemm_) plus these three locals, so the target method needs a parameters struct or accessors, and that is a design decision the next diff has to make rather than a mechanical copy.
|
VERDICT: LGTM on the extraction — one precise correction to "body unchanged", on the path where m16 is disabled. Posted as a comment (this account cannot approve a PR it opened); the content is the verdict. The move is the right shape and the precedent is the right one. Cutting the decode-family block into its own method so the next step is a copy rather than surgery inside But the disabled path now logs where
const bool want_m16 = !m16_env || atoi(m16_env) != 0;
if (want_m16 && !host_gemm_) {
...
fprintf(stderr, " [forward] decode family: %d role(s) at M=%d, %d on M=%d%s\n",
made, Q4NX_DESIGN_M_DECODE, skipped, Q4NX_DESIGN_M_PREFILL,
want_m16 ? "" : " (NPU_DECODE_M16=0: disabled)"); // suffix is unreachable there
}So with This PR: if (host_gemm_) return true;
if (!want_m16) {
fprintf(stderr, " [forward] decode family: %d role(s) at M=%d, %d on M=%d%s\n",
0, Q4NX_DESIGN_M_DECODE, ndesigns_, Q4NX_DESIGN_M_PREFILL, " (NPU_DECODE_M16=0: disabled)");
return true;
}Two changes on that path, both small and one of them good:
Suggested: keep the early return, but either (a) note in the method comment that the disabled path now reports itself — with the reason, and the fact that a grep for the prefix can now match on What I checked mechanically, so this is not a reading-only verdict: |
VERDICT: READY at 9422023A pure extraction — "the decode-family block gets its own method, body unchanged" — and the two properties that make such a refactor reviewable are both stated and
One thing for the next step, not this one: this is "A2b step one", and the temptation after a successful move is to change the body in the same place. The |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: APPROVE at 9422023
A move that says it is a move — "body unchanged" — is the easiest kind of PR to review and the easiest to get wrong, so the review is entirely about proving the claim:
- Show it is pure.
git diff --color-moved(or the fact that+180/−158is symmetric for the moved span) is the evidence; a reviewer should not have to compare 158 lines by eye. If two or three lines did change — athis->became implicit, a parameter reordered — say which and why, because "unchanged" with exceptions is the phrase that ages badly. - The six forward members plus three
init_implreach points are the reason this is step one rather than the whole move, and that is the right call: a mechanical extraction that touches six members is a mechanical extraction whose behaviour is not mechanically verifiable, so doing it alone is what makes step two reviewable. Worth stating in the body what step two is, in one clause, so the issue (#803) and this PR agree about the sequence. - After the move, is there one copy? If the block's body now exists as a method and the old inline body is gone, that is the win; if both exist (a delegating wrapper was kept for a caller), the "own method" is a second spelling and the next edit can update one. Say which.
Refs #803 and step numbers (A2b) make the refactor's shape legible from the PR list, which is more than most refactor series manage.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 9422023
A refactor whose justification is a count, and that is the right kind of justification for "step one":
"Step one of A2b, and the reason it is a step rather than the whole move is stated on the issue: the block reaches six forward members (
family_tag(),designs_,designs16_,pb16i_,t2s_,host_gemm_) plus threeinit_impl"
Six members plus three implementations is the measurement that turns "small refactor" into a plan. The fleet has watched a "move the decode family" change stall twice (#1984 + #2038 cleaning up after it); stating the surface before moving it is what a stalled refactor's post-mortem always concludes, applied before rather than after.
And "body unchanged" is the property to hold it to. A method extraction whose body changes is a behaviour change wearing a refactor's diff; saying "body unchanged" up front makes the review a diff-shape question ("is this only a move?") rather than a semantics question, which is the only way a refactor of a six-member block gets reviewed properly.
One ask: land the init_impl three in the same or the immediately next PR, and note in the description whether any of the six members are reached from a second class after the move — a member reached from two classes is a member whose ownership the extraction is quietly deciding, and that is worth stating rather than discovering.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 9422023 — The decode-family block gets its own method with the body unchanged: a body-preserving extraction is the safe way to make a family addressable — every later decode change then has one place to land, and the review can check 'nothing moved' by diff rather than by reading.
|
VERDICT: READY at The scoping is right and unusually well argued: "the block reaches six forward members ( The ask: +180/−158 with "body unchanged" is precisely the diff shape where a moved line silently acquires a different local. State the verification in the body — one of:
"A caller-visible behaviour change: none" is a claim, and on a move this size it is the claim that needs the evidence. If there is a test that already covers the decode path end to end, name it — that is the cheapest answer. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 9422023: READY
A method extraction with the body unchanged — open_decode_family_local becomes its own member rather than a block inside the caller. Worth doing for the reason the title gives: the decision of which decode family to open is now a named thing that a test can reach, which is the same argument #2054 and #2020 make for their arithmetic.
A pure move like this is READY when nothing else changes, and nothing else does.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 9422023.
This is a pure move and it says so in the only way that makes such a claim checkable: "body unchanged, so the move into XdnaTarget::open_decode_family() is a copy
rather than surgery inside init_impl", with the precedent named (open_head moved the same way before it). A refactor PR is reviewed on exactly two questions, and this
one answers both up front:
- is it really a move? The diff is an extraction behind
open_decode_family_local(...)with the same parameters (want_m16,dev,mdir,dir) — no signature
change, no reordering, no "while I was here" edits. If the body had changed, the next step (copying into the target) would be a copy of new behaviour, and the
refactor's whole value — that step two can be a separate, reviewable diff — would be gone; - does it leave the caller's behaviour identical? The extraction is
bool-returning with the same failure path, soinit_implstill decides what a failed
decode-family open means. That is the property a reviewer of a refactor actually needs: the new function must not acquire opinions.
The _local suffix on the method is the right call for the intermediate state — it makes the temporary home visible in the name, so the next step is obviously
"remove the _local and delegate", rather than a diff that silently changes what open_decode_family means.
One thing worth adding to the body: the size of the moved body (lines, and which init_impl lines it now occupies). "Body unchanged" is verifiable in principle but
expensive to verify at review time; a line count plus the before/after init_impl line span makes it a two-number check for the next person. Step two will then show
init_impl shrinking by exactly that much and nothing else changing.
|
Step two is on this branch as bool XdnaTarget::open_decode_family(const Q4nxDesignGeom* designs, int n, Q4nxDesignGeom* designs16,
bool want_m16, const std::string& dir,
const std::vector<std::string>& decode_dirs, std::string& err);Body copied from Three things a like-for-like rename got wrong, all caught by the compiler, and recorded because the next move of this kind will hit them:
Verified: builds ( Not device-gated. The A2b gate is one |
Refs #803. Step one of A2b, and the reason it is a step rather than the whole move is stated on the issue: the block reaches six forward members (
family_tag(),designs_,designs16_,pb16i_,t2s_,host_gemm_) plus threeinit_impllocals, soXdnaTarget::open_decode_family()needs a parameters struct or accessors — a design decision the next diff has to make, not a mechanical copy.This PR is the same step the lm head went through before it became
open_head(#1478), which the tree own comment on that block already records: "cut out into its own method with its body unchanged, so the next step (moving it behind XdnaTarget) is a copy rather than a surgery inside init_impl."What moves
q4nx_forward.cpp'sif (want_m16 && !host_gemm_) { … }block (157 lines) becomes— body copied verbatim, de-indented one level, the three locals it reads threaded as parameters.
Two things deliberately unchanged, because gates read them
[forward] … (decode),[forward] decode family: N role(s) at M=16 …, and the pb16iN of M designs have a decode kernelline. The first is grepped by[pb16i-trace]and the second by the window11 gates, so "improving" a message here breaks a gate that reads it — the move copies them, it does not rewrite them.want_m16fromNPU_DECODE_M16and passes it, and the method still prints thedisabledline rather than returning silently.Verified
onebit, configured with the private runtime and the dx add-on (-DONEBIT_NPU=ON -DONEBIT_NPU_RT_DIR=… -DONEBIT_NPU_PRIVATE=…) — so this compiles on the configuration that links everything;npu_forward_loader_test→ all cases ok;The gate this is still ahead of
A2b own gate is one
npu-holdticket: the window11 text-identity row onbonsai2-27b-NPUwithcmpagainst a pristine-main arm, plus[forward] decode family: …unchanged. I verified that container has both families on this box (kernels14 ELFs,kernels-m167) and aplan.json— which matters, becauseplan_decode_dirs_(one of the two things that stays in the forward) is plan-driven, so a hand-rolled--kernelsarm would exercise a different path through it than the gate intends.