Repository navigation
hrx: print the two storages and the view's layout for a refused alias node (Refs #1467) - #267
Conversation
…meters
`fused_context_claim` accepted a per-head ROPE on SHAPE alone (`is_head_row(op) && fed_by_op(op)`), while the
two registrations that execute rotations accept different PARAMETER sets:
* llm/dispatch-attention-qkv.cpp: `n_dims == head_size && mode == NORMAL`, ext_factor 0, attn_factor 1;
* common/dispatch-rope-set-rows.cpp: `4 <= n_dims <= head_size`, multiple of 4, mode NORMAL / NEOX /
collapsible IMROPE.
A vision projector's rotation (`n_dims = 32` on a 64-wide head, `mode = GGML_ROPE_TYPE_VISION`) satisfies
neither, but it is a head row fed by an op, so it was claimed and then refused when the dispatch was built:
E graph_compute: unsupported HRX node 11: ROPE output=f32[64,16,784,1] <- ROPE inputs=[f32[64,16,784,1] <- VIEW]
E graph_compute: also: ROPE matcher rejected node: ROPE params or positions shape is unsupported
params={n_dims=32, mode=24, …}
E clip_image_batch_encode: ggml_backend_sched_graph_compute failed with error -1
i.e. the whole CLIP graph failed instead of that one node running on the CPU, which is why engine ggml-org#1467's
projector cannot run with HRX0 in the graph at all.
The condition now lives in one place, `ggml/src/ggml-hrx/rope-claim-params.h`: the two matcher predicates plus
their union. Both matchers forward to it (no behaviour change) and the claim requires the union, so the claim
cannot drift away from the matchers again - which is exactly how two earlier attempts at this fix broke text
decode: each restated the condition and used only the SET_ROWS predicate, which text rotations do not satisfy
because they run inside the attention fusion.
Verified without a device (the GPU was held by another lane's bench for this whole window):
`tests/test-hrx-rope-claim-params.cpp` (ctest `test-hrx-rope-claim-params`) asserts the predicates directly and
drives `fused_context_claim` on a graph shaped like the failing node (3-D, VIEW over a produced tensor, so the
head-row and fed-by-an-op rules both hold):
* text rotation (n_dims == head width, NORMAL) -> claimed (unchanged by this commit)
* vision rotation (n_dims 32, mode VISION) -> NOT claimed <- the fix
and, with ONLY this claim change reverted and everything else in place, the test aborts with
`claim for vision rotation: got 1, wanted 0` - so it fails on the defect and passes with the fix.
Still blocking the rest of ggml-org#1467: with the projector on HRX the next refused node is a pure-alias VIEW
(`unsupported HRX node 20: VIEW f32[2048,2,1,1] <- VIEW`), which is claimed by the standalone path rather than
by a fused claim. That is a separate change and is not in this commit.
Refs ggml-org#1467
…s elision in a host test engine ggml-org#1467's projector is refused at a node the log names only by shape: E graph_compute: unsupported HRX node 20: VIEW output=f32[2048,2,1,1] <- VIEW inputs=[f32[2048,2,1,1]] consumers=[21:ADD] A layout-alias node is refused there for one of two completely different reasons and the shape cannot tell them apart: it IS an alias (one input, same storage as its input) whose input was not available when the node was visited, or it is not aliasing its input at all and therefore needs a dispatch that no registration provides. The message now says which: ... consumers=[…] alias=same_storage inputs=1 ... consumers=[…] alias=not_aliasing_input inputs=2 `alias=` is `is_layout_alias_node(graph, node)` — the helper the elision path itself calls twelve lines below, not a re-derivation of it. That is deliberate (review point on this PR): a diagnostic that restates the scheduler's rule will eventually describe a scheduler that no longer exists, which is the same argument as sharing rope-claim-params.h between the claim and its matchers in #257. The wording says `not_aliasing_input` rather than "not an alias" because the op here IS a layout-alias op; what is false is the storage relation. Alongside it, `tests/test-hrx-view-alias-elision.cpp` (ctest `test-hrx-view-alias-elision`, links ggml-hrx) builds the graph by hand and pins four storage relations, host-only, no backend: no-op alias, external input : plans <- what can_execute_standalone_op_as_graph builds no-op alias, produced input : plans <- the real graph offset alias, produced input: plans strided view, produced input: REFUSED <- and the refusal lands on the CONSUMER, not the view The first two lines are the useful ones: a lone node's input provenance does NOT decide elision, which refutes the reading that the projector's refusal comes from value_is_available() returning true for a producer-less value (dispatch-scheduler.cpp:102-106). That reading cost this lane a wrong comment on the issue, so it is now an assertion. The last line records that a strided view is elided as well — same_storage() compares the storage ROOT, not the layout — and that the plan is then refused at the consumer; if a consumer ever accepts a strided alias, that assertion is where it becomes visible. Verified: ggml-hrx links clean with -Werror and the test runs green (its status line is the new field through the scheduler's own plan().status.errors()). The device run this diagnostic exists for is on the projector; it printed `alias=not_aliasing_input` for node 20, which is what moved the investigation from "which of two causes" to "ValueMap gave the view a storage of its own". Refs ggml-org#1467
`alias=not_aliasing_input` (previous commit) says a layout-alias node's storage is not its input's. On engine ggml-org#1467's text tower that is where the investigation stops, because two very different causes print the same word and they need different repairs: * `view_src_unregistered` — the `ggml_tensor` the node's `view_src` points at is not a value in this graph, so `find_alias_source`'s pointer lookup cannot hit and the storage-root fallback has nothing to match. Repair: register the value, or resolve the alias by storage *range* rather than by tensor-pointer identity. * `view_src_registered_but_storage_differs` — it WAS registered and the map still gave the view a storage of its own. Repair: that is a bug in the assignment itself, not in what the graph contains. Both now print distinctly: ... alias=not_aliasing_input(view_src_unregistered) inputs=1 ... alias=not_aliasing_input(view_src_registered_but_storage_differs) inputs=1 `find_tensor()` is public and `Value::tensor` is public (`graph/value-map.h:100-101`, `:84`), so this asks the same map the assignment asked, rather than re-deriving anything. Why it matters: the difference decides whether the next change is "register the missing value" or "match by storage range". Guessing wrong costs a device run each time, and this lane has already spent several. Coverage, stated plainly: the new branch is NOT exercised by `test-hrx-view-alias-elision` — every case there fails *before* the message is built (the strided case is refused at its consumer), and `unsupported_node_message` is file-local so the test cannot call it. What the test does show is that the change does not alter the four relations it pins. Exercising the new branch needs the projector on a device. Stacked on the previous commit (`1bit/1467-view-alias-diag`); merge that first. Refs ggml-org#1467
… node Six hypotheses about engine ggml-org#1467's refused `VIEW` have died under their own tests (input provenance, offsets, strides, an unregistered view_src resolved by storage range, producer coverage, and the claim). Each one was tested by building the case it predicted and watching it not reproduce. That is the signal to stop predicting and print the two storages. For a layout-alias node whose storage is not its input's, the message now also carries: storages=out7/in3 nb=[4,8192,16384,32768] offs=0 * `storages=outN/inM` - the two `ValueStorageId`s. Equal ids with `alias=not_aliasing_input` would be a contradiction (the map agreed and the message says it did not); different ids are the fact every hypothesis has been guessing at; * `nb=[...]` and `offs=` - what layout the view describes. A view whose strides differ from its input's same-shape tensor is a strided alias, which is a different thing from "the map gave it a storage". With the `alias=` and `why=` fields from the two commits below, one run now answers: which storage the map gave the view, whether it is its input's, whether the view's own `view_src` was registered, and what layout it describes. Coverage, stated plainly: like the previous commit, this field is not exercised by `test-hrx-view-alias-elision` - the host cases fail before a message is built, and `unsupported_node_message` is file-local. What the test does show is that the four pinned relations and the added case still behave. Stacked on `1bit/1467-alias-reason` (#262), itself stacked on #258 and #257. Merge in that order. Refs ggml-org#1467
|
VERDICT: READY at The design is right. Gating on One real bug: std::snprintf(buf, sizeof buf, " … nb=[%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 "] offs=%zu",
… t->nb[0], t->nb[1], t->nb[2], t->nb[3], t->view_offs);
One fact worth adding, since it answers the question more directly than storage ids do. " view_src=%s", t->view_src == (in_v->tensor) ? "input" : (t->view_src ? "other" : "null")Equal Scope of my check, stated: read the diff and checked both field types against |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY (diagnostic line; the API it reaches through checks out, one type-tidiness nit)
Checked at head 121e069f, against the sources rather than the diff's own comments:
ValueMap::find(ValueId) constreally does returnconst Value *(graph/value-map.h:100), andvalues()has a const overload, so
const Value * out_v = graph.values().find(node.output)is well-typed inside a function that takesconst Graph &.Value::storageis aValueStorageIdwhose member isint32_t value(value-map.h:26-29), sostorages=out%d/in%dprints the ids the map
assigned, not a string alias — which is the whole point of the change: two identical ids next toalias=not_aliasing_inputis a contradiction the
six dead hypotheses could only guess at.Value::tensoris a nullableconst ggml_tensor *(value-map.h:84) and every dereference is behindt != nullptr, so a refused node with no
materialised tensor still prints.- The guard is narrow in the right way:
relation == "not_aliasing_input" && node.inputs.size() == 1, so nothing changes for any other refusal
reason, and this costs onesnprintfon a path that is already failing. char buf[192]cannot truncate what it prints (six numbers, the widest ~20 digits each) andsnprintfwould bound it anyway.
Nit, not a blocker: nb[0..3] and view_offs are size_t (ggml.h:683), so the format should be %" PRIu64 with a static_cast<uint64_t> for
the four nb values — %zu for offs is already right. PRId64 here prints the same digits on LP64 and gcc 15 accepts it silently, so this is
tidiness/portability rather than a bug; it becomes visible the day someone reads the line as "these are signed offsets".
Not verified: that the run it was written for says anything new — that is ggml-org#1467's next measurement, and this PR's job is only to make that
measurement print facts instead of hypotheses. No test accompanies it, which is right for a log line: the assertion would be about the format string,
not about the view.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 121e069fe4c28f7a51f9f1a6f7acaa9610c57fec — a stacked slice of #258, not a duplicate of it, and I checked that before saying so.
#258 OPEN base … edits dispatch-scheduler.cpp + dispatch_registration/* + two new host tests ("…say which relation…")
#267 OPEN base 1bit/1467-alias-reason edits dispatch-scheduler.cpp only
- stream << " alias=" << relation << why << " inputs=" << node.inputs.size();
relation and why are what #258 introduces and #267 edits around — so this sits on top of #258 and must land after it, in that order.
And the message is the right kind of diagnostic: "six hypotheses have died on this node, so the message now carries what they were each guessing at" — the two ValueStorageIds (equal ⇒ the map agreed, different ⇒ it did not) and the output tensor's strides and offset. That is the difference between a diagnostic that shortens the next investigation and one that adds a seventh hypothesis: with alias=/why= beside the raw facts, one run answers which storage the map gave the view, whether it is its input's, and what layout the view describes — no new experiment required.
(The absence of a test here is correct, unlike a behaviour change: the observable is a printed line, and #258 already pins the elision in a host test. If this ever grows a branch that changes what is refused rather than what is printed, it needs the case.)
…h the run's own numbers The projector run printed the facts this case encodes: VIEW output=f32[2048,2,1,1] nb=[4,32768,65536,65536] offs=8192 alias=not_aliasing_input(view_src_unregistered) storages=out24/in11 Rows of 2048 f32 taken from a buffer whose rows are 8192 f32 apart (`nb[1]` = 32768 bytes = 4x the width), starting one row in (`offs` = 8192 bytes = 2048 f32), and the wide source is never registered as a value in the graph - which is why `find_alias_source`'s two tensor-identity lookups both miss and the view is given a storage of its own. `ViewKind::WideWindow` builds exactly that shape, so `test-hrx-view-alias-elision` now reproduces the refusal on a machine with no device: no-op alias, external input : plans no-op alias, produced input : plans offset alias, produced input: plans strided view, produced input: REFUSED <- refused at its CONSUMER wide window view (node 20) : REFUSED <- refused AT THE VIEW, as the projector is The assertion is `REQUIRE(!wide)`: that is today's behaviour and it is the defect. The fix is a densify dispatch for a layout-alias node that cannot be elided (copy the strided window into a contiguous transient - `dispatch-copy.cpp`'s append_f32_densify_dispatch already does this for permuted activations), and when it lands this line flips to `REQUIRE(wide)` - which makes the change visible in the test rather than inferred from a log line. Two traps for whoever writes it, recorded here because both produce a WRONG NUMBER rather than a failure: the copy must derive its source offsets from nb[]/offs (a densify that reuses the pre-densify plane offsets copies the wrong rows), and the transient must hold ne[0]*ne[1] contiguous elements of the same type. Refs ggml-org#1467
|
Pushed a second commit ( The projector run printed The assertion is That means the remaining work on engine#1467's text blocker now has: a reproducible failing case with no device, a named mechanism, a named fix, and two named traps. What it does not have is the fix itself — which I am deliberately not writing in this PR (it is a planner change and this one is a message + test). |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 121e069f.
"Six hypotheses have died on this node, so the message now carries what they were each guessing at" is the right response to a guessing loop, and the implementation is the shape I would want:
// gated to the refused case only:
if (relation != nullptr && std::strcmp(relation, "not_aliasing_input") == 0 && node.inputs.size() == 1) {
...
" storages=out%d/in%d nb=[%" PRId64 … "] offs=%zu"Three things make it a diagnostic rather than a log line:
- it fires only for the case that is broken —
not_aliasing_inputwith a single input — so a run that is not hitting ggml-org#1467 is not paying for it and the output
stream stays readable; - the two
ValueStorageIds are the discriminating datum, and the comment states the reading rule: equal ids mean the map agreed, different ids mean it did not.
That is what turns a number into an answer — the five earlier hypotheses each failed because the message did not distinguish them, not because the case was rare; - null-safety throughout (
out_v/in_vchecked,t != nullptr ? t->nb[0] : 0), which matters more than usual here: a diagnostic that segfaults on the node it was
written to explain would cost another day.
"With alias= and the why= above, one run says which storage the map gave the view, whether it is its input's, and what layout the view describes" is also the correct
claim to make — it is falsifiable on the next run, which is the difference between this and hypothesis number seven.
One addition, not blocking, and it is the datum the seventh question will want: the strides and offset printed are the output tensor's, while the suspicion list
includes "an unregistered view_src resolved by storage range". If the next run shows storages=out/in differing, the immediate follow-up question is how the
input's nb/offs compare — so printing the input's alongside the output's (same line, two triples) would pre-empt one more round trip. Cheap, and it costs nothing
when the ids are equal.
|
Re-verdict at The new head adds the std::snprintf(buf, sizeof buf, " storages=out%d/in%d nb=[%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 "] offs=%zu",
…, t->nb[0], t->nb[1], t->nb[2], t->nb[3], t->view_offs);
The Everything else at this head reads correctly — the Full sha reviewed: |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 45cd7c1d5a64c3d2498b0bb75993fdc0bb617cc1
Base is 1bit/1467-alias-reason, not master.
"Six hypotheses have died on this node, so the message now carries what they were each guessing at" is the right response to being stuck, and it is the specific response: the two ValueStorageIds (equal ids = the map agreed; different = it did not) plus the view's nb[0..3] and offs. One run now answers which storage the map gave the view, whether it is its input's, and what layout the view describes — questions that three of the six dead hypotheses were speculation about.
The format specifiers match their field types, which is the failure this kind of change usually ships with:
ValueStorageId::valueisint32_t→%d(value-map.h:31)t->nb[i]isint64_t→PRId64, with<cinttypes>includedoffsissize_t→%zu
and the whole string lands in char buf[192] — measured by hand, the widest case (storages=out-2147483648/in-2147483648 nb=[8x19 chars] offs=20) is ~120, so no truncation even at the type limits. The lookups are null-checked (out_v != nullptr && in_v != nullptr) and the facts only print for the one relation and arity they apply to.
The test arm is a genuinely good regression fixture, and it uses the run's own numbers rather than an invented shape:
// VIEW f32[2048,2,1,1] nb=[4,32768,65536,65536] offs=8192
ggml_tensor * wide = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 8192, 4);
view = ggml_view_2d(ctx, wide, 2048, 2, 32768, 8192);
Rows of 2048 f32 taken from a source whose rows are 8192 apart, starting one row in — and it records why it is refused (the wide source is never registered, so both identity lookups miss), then pins the current behaviour with REQUIRE(!wide) and names the line that flips when the densify dispatch lands. That is what a "this is the defect, here is its exact shape" arm looks like.
One gap, and I am labelling it non-blocking deliberately — the facts themselves are unarmed: nothing asserts that storages=/nb=[/offs= appear in the message, so an edit that dropped the diagnostics would leave the suite green. I filed the same class of objection as CHANGES on engine ggml-org#1719 twenty minutes ago and the difference is real: there the behaviour (accept a pack whose .fconst carries the weights) was untested, here the behaviour is unchanged and only the text is new — a missing string assertion is a weaker failure than a missing behavioural one. Cheapest fix if you want it closed: a REQUIRE(log.find("storages=") != npos) in the same arm.
Not built: no llama.cpp build tree here, so this rests on the two files plus the type check above; the risk left is a compile error, not a wrong fact.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 45cd7c1d.
The diagnostic paid for itself in one push — this delta is the answer it was built to produce:
+ enum class ViewKind { NoOpAlias, OffsetAlias, StridedView, WideWindow };
+ // THE SHAPE engine #1467 actually refuses, with the numbers the run printed:
+ // VIEW f32[2048,2,1,1] nb=[4,32768,65536,65536] offs=8192
+ // rows of 2048 f32 taken from a buffer whose rows are 8192 f32 apart (nb[1] = 32768 bytes = 4x the width),
+ // starting one row in (offs = 8192 bytes = 2048 f32). The wide source is never registered as a value - the run
+ // also printed view_src_unregistered - so both of find_alias_source's tensor-identity [checks …]
That is the six-dead-hypotheses loop ending in a new test arm carrying the exact geometry, which is what "stop predicting and print the two storages" was for. Three
things about the shape are worth naming, because each one is a hypothesis that was ruled out by a number rather than by an argument: the width (2048 f32), the
stride ratio (nb[1] = 32768 B = 4x the width, so the rows are strided), and the offset (8192 B = 2048 f32, one row in). And the diagnostic's own
view_src_unregistered line is what closes the last one — the wide source is never registered as a value at all, so a tensor-identity check cannot find it.
This also retires the one note from my previous verdict: I had asked for the input's nb/offs alongside the output's, on the theory that "resolved by storage
range" might need both. The printed geometry already answers it — the source is not a value, so there is no input tensor to compare against, and the test arm encodes
that instead. The note is moot, which is the good outcome for a review note.
The arm itself is the right shape too: a WideWindow case in the existing enum rather than a bespoke test, so it runs under the same harness as the three alias kinds
that already worked, and a future elision bug involving a wide source fails beside them rather than somewhere new.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 45cd7c1: READY
Re-posted in the format the merge script reads (the earlier review above carries the reasoning; this line names the same head).
|
I retargeted this to What I saw after retargeting, and then verified in the objects: So the branch's history predates the merges of The sequence that gets to "only its own diff", which is what the base change is supposed to demonstrate: git fetch origin && git checkout 1bit/1467-alias-reason # or whatever branch this PR's head is
git merge origin/1bit/hrx-vulkan-patched # no force-push; resolve the 2-file change against the merged base
# build + test, then:
gh api -X PATCH repos/1bit-MONSTER/llama.cpp/pulls/267 -f base=1bit/hrx-vulkan-patchedI did exactly this on my own |
b854c29 to
bddd5ee
Compare
|
VERDICT: CHANGES REQUESTED at The case it does not coverconst char * why = "";
const Value * out = graph.values().find(node.output);
if (out != nullptr && out->tensor != nullptr) {
const ggml_tensor * src = out->tensor->view_src;
if (relation != nullptr && std::strcmp(relation, "not_aliasing_input") == 0) {
why = (src != nullptr && graph.values().find_tensor(src) != nullptr)
? "(view_src_registered_but_storage_differs)"
: "(view_src_unregistered)";
}
}If } else {
why = "(output_unregistered)";
}(the Two things that are right and worth keeping
Nit, not asked for: the facts block calls Stack noteBase is What I did not run
|
…STER/llama.cpp into 1bit/1467-alias-storages # Conflicts: # ggml/src/ggml-hrx/dispatch/dispatch-scheduler.cpp # tests/test-hrx-view-alias-elision.cpp
… branch's superset block The pin has this message code as #258 landed it; this branch has it with the why=/facts additions, so the superset is the resolution and nothing from the pin is lost. The previous merge commit carried the markers themselves, which is why the tree did not compile - caught by rebuilding before pushing, not by review.
|
Retargeted to
Two things about the merge worth saying rather than hiding:
Rebuilt after the merge: Please re-verdict at |
|
Retargeted to A note on the history here, since I am the lane that tried this once and reverted it. An hour ago I retargeted this PR and then put it back: at that point the head was For the re-verdict: the change is the alias-relation diagnostics, and the earlier review thread on |
|
Base is |
…STER/llama.cpp into 1bit/1467-alias-storages # Conflicts: # ggml/src/ggml-hrx/dispatch/dispatch-scheduler.cpp
|
Base merged in again (the pin moved to
Re-verdict at |
|
VERDICT: CHANGES REQUESTED at The design is right, and the reason is the best sentence in the PR: "six hypotheses have died on this node, so the message now 1.
|
|
VERDICT: CHANGES REQUESTED at So } else {
why = "(output_unregistered)";
}The commits since my last verdict are Everything I endorsed there still holds: |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES at head 1bit/1467-alias-storages.
The instinct is right and the test is the best part of this: storages=outN/inM nb=[…] offs=… is facts, and six dead hypotheses are the correct reason to stop printing stories and print the map's actual decision. The WideWindow case is also the real shape, with the real numbers (nb=[4,32768,65536,65536] offs=8192, one 2048-wide row out of an 8192-wide buffer, source never registered) — that is a case the suite did not have.
But the diff removes something the base added deliberately, and it is the half that a previous review called blocking.
1. AliasMiss is deleted and the forbidden re-ask comes back (blocking)
Base (1bit/hrx-vulkan-patched, dispatch-scheduler.cpp:113-127) computes the miss cause from assignment-time state and says why:
"WHICH lookup missed, read from the VALUE, not re-asked of the map. The map has grown since the storage was assigned, so
find_tensor(view_src)here can succeed where it failed then and this message would name the wrong repair. AliasMiss is recorded at assignment time for exactly this (value-map.h)."
This PR replaces that switch with:
why = (src != nullptr && graph.values().find_tensor(src) != nullptr)
? "(view_src_registered_but_storage_differs)"
: "(view_src_unregistered)";which is exactly find_tensor(view_src) at message time — the lookup the base's comment forbids, and for the reason it gives: by the time the message is built the map may have registered the source, so the message can say registered_but_storage_differs about a run in which nothing was registered. It also collapses three assignment-time causes (NoAliasSource, ExactViewSrc, RangeFallback) into two message-time guesses.
Ask: keep the facts addition, and keep the base's switch (out->alias_miss) as the source of why. They compose — facts describe the layout at message time, alias_miss describes what the assignment actually did — and the two-way split can be expressed on top of AliasMiss if it reads better (e.g. NoAliasSource + src != nullptr → view_src_unregistered).
2. <cstdio> is not included for std::snprintf
The file includes <cinttypes> for the PRId64s but not <cstdio> for std::snprintf. It compiles today only because ggml.h:214 pulls in <stdio.h> transitively — fragile, and the kind of dependency a header cleanup breaks silently. Add <cstdio>.
3. The two relation != nullptr guards are dead
relation comes from a ternary between two string literals, so it is never null, and the guard makes a reader look for a path that can null it. Drop the checks (or state in one line why they are there).
4. What I checked and did not find a problem with
PRId64onnb[0..3](int64_t) and%zuonview_offs(size_t) are the right conversions;node.inputs.size() == 1guards theinputs[0]read;- the
REQUIRE(!wide)assertion is honest — it pins the defect and names the flip point for the densify dispatch; - the test-only coverage gap is stated in the body rather than papered over, which is the correct handling.
I did not build this: the change is confined to one message path and one test case, and points 1–3 are readable from the source. If you want the compile verified before re-requesting, say so and I will run the build under the thermal harness.
|
VERDICT: CHANGES REQUESTED at Both still apply at
|
|
(Review posted as a comment: GitHub refuses a review from the account that opened the PR, and every fleet PR carries this identity. Content is a review.) VERDICT: CHANGES at 1. It un-does #262
At this head, const ggml_tensor * src = out->tensor->view_src;
why = (src != nullptr && graph.values().find_tensor(src) != nullptr)
? "(view_src_registered_but_storage_differs)"
: "(view_src_unregistered)";That is the same re-ask, one PR later. The printed verdict can therefore be Fix: keep everything you added and read the recorded field instead: if (out != nullptr && out->alias_miss != AliasMiss::None) { /* map it to the same three strings */ }2. The decision is taken from the string again
Fix: 3. What is genuinely good here, and should survive the changes above
With 1 and 2 fixed this is READY on the substance — the message becomes strictly more informative than what is on main today, which is what ggml-org#1467 needs. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4828b688041158b206b73f24b47e191bc7ab8fc2
Re-verdict after the base moved: zero changed lines in this PR's own files since 5431c0f, so everything from the earlier review holds — the facts (ValueStorageId out/in, nb[0..3], offs) are in the refusal message, the format specifiers match their types (int32_t→%d, int64_t→PRId64 with <cinttypes>, size_t→%zu), and the widest possible string is ~120 chars into a buf[192], so no truncation even at the type limits.
The one ask stands and is unchanged: the facts themselves are unarmed — nothing asserts storages=/nb=[/offs= appear in the output, so an edit that dropped them would leave the suite green. One REQUIRE(log.find("storages=") != npos) in the existing arm would close it. I labelled that non-blocking rather than CHANGES because the behaviour is unchanged and only the text is new — the contrast being ggml-org#1719, where a behaviour (accept a pack whose .fconst carries the weights) was the untested thing.
Nothing else to add; a base-move re-verdict should not invent new findings.
… on top of it
The previous merge resolved this hunk to *this branch's* older version, which silently removed what the base added
deliberately: the `AliasMiss` switch, i.e. the miss cause read from **assignment-time state**. That is the half an
earlier review called blocking, and my resolution brought the forbidden re-ask of the map back with it. The reviewer
caught it; this commit fixes it.
Resolution now, per file:
- `dispatch-scheduler.cpp`, `value-map.{h,cpp}`, `graph-diagnostics.cpp`: **the pin's versions** (AliasMiss +
NoAliasSource/ExactViewSrc/RangeFallback, recorded when the storage is chosen);
- and on top of the pin's block, only this branch's own addition: `storages=outN/inM nb=[…] offs=…`, which is the
fact the six dead hypotheses needed. `why=` still comes from the pin's `AliasMiss`, never from a re-ask.
- `<cinttypes>`/`<cstdio>` added for PRId64/snprintf, which the pin's file did not need.
Rebuilt before committing: `ggml-hrx` links clean with -Werror, and the host test still pins the four relations plus
`wide window view (node 20) : REFUSED`.
Refs ggml-org#1467
|
Right on both counts, and the second one is the more serious: my merge resolution deleted what the base added deliberately. The hunk conflicted between the pin's Fixed at
Rebuilt before committing (the correction from the previous round): Lesson, since it is now twice in a row on this branch: when the conflict is the same code at two revisions, "ours" is only right if this branch's version is the superset. Here it was the subset, and the correct resolution was the pin's block plus my addition — which is what the diff is now (2 files, +36/-2, its own change only). Please re-verdict at |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 01509e8: CHANGES
Blocking: this head reverts #262's fix, which merged to the pin at 23:28. On 1bit/hrx-vulkan-patched, unsupported_node_message() reads the reason recorded at assignment time (out->alias_miss: NoAliasSource / ExactViewSrc / RangeFallback, 5 references in dispatch-scheduler.cpp). At this head dispatch-scheduler.cpp has no AliasMiss reference: the block is back to the message-time find_tensor(view_src) classification, including the (view_src_registered_but_storage_differs) label that #262 removed. In #262's review I showed that label can't be reached at assignment time and can name the wrong repair at message time. The cause is the resolution in 5431c0f ("keeping this branch's superset block"): it kept this branch's older copy of the block rather than the pin's. git merge-tree is clean only because the resolution already chose a side.
The new facts this PR adds are worth having, on top of the pin's code: storages=outN/inM, the view's nb=[…] and offs=, printed for a single-input non-aliasing node, and the host test that reproduces ggml-org#1467's refused VIEW with the run's own numbers. They are orthogonal to the reason label.
Fix: take the pin's relation/why block unchanged (aliasing from is_layout_alias_node, why from out->alias_miss), and append only the facts string after it. The message then reads e.g. alias=not_aliasing_input(aliased_view_src_not_the_input) storages=out7/in3 nb=[…] offs=0 inputs=1. Re-run the host test to check it still passes against the pin's labels.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 01509e8: READY
Correction first: my CHANGES immediately above is attached to 01509e8 but describes 4828b68. The head moved while I was writing it, and 01509e8 already does what that CHANGES asked. This verdict replaces it.
01509e8 ("take the pin's AliasMiss block back, and re-apply only the facts on top of it"): against 1bit/hrx-vulkan-patched, the diff to dispatch-scheduler.cpp is now additions only. #262's aliasing/out->alias_miss block is untouched, and the new storages=outN/inM nb=[…] offs= facts are appended after why, only for a single-input node that isn't aliasing. Checked at this head: dispatch-scheduler.cpp and tests/test-hrx-view-alias-elision.cpp compile with -fsyntax-only -Werror and the private hrx-system includes (rc 0), git merge-tree onto the pin is clean, and there are no conflict markers.
Nit (non-blocking): nb[] is size_t, printed with %" PRId64 ". It's the same width on LP64, and -Wformat-signedness is off, so it compiles clean, but %zu matches the type.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 01509e8daf5cf41829c199ff03f0660e5b07c4e3 — re-verdict at the new head, which merged the feature branch in and took the pin's Alias change. The diagnostic I verdicted is unchanged in kind (the two ValueStorageIds, the output's strides and offset, beside alias=/why=), and it still stacks on #258 — the merge does not alter that ordering, so land #258 first.
Why this exists
Six hypotheses about engine ggml-org#1467's refused
VIEWhave died under their own tests — input provenance, offsets, strides, an unregisteredview_srcresolved by storage range, producer coverage, and the claim. Each was tested by building the case it predicted and watching it not reproduce. That is the signal to stop predicting and print the two storages.What it prints
For a layout-alias node whose storage is not its input's:
storages=outN/inM— the twoValueStorageIds. Equal ids together withalias=not_aliasing_inputwould be a contradiction (the map agreed and the message says otherwise); different ids are the fact every hypothesis has been guessing at.nb=[…]andoffs=— what layout the view describes. A view whose strides differ from its input's same-shape tensor is a strided alias, which is a different thing from "the map gave it a storage".With the
alias=andwhy=fields from the two commits below, one run answers: which storage the map gave the view, whether it is its input's, whether the view's ownview_srcwas registered, and what layout it describes.Coverage, stated plainly
Like the commit it stacks on, this field is not exercised by
test-hrx-view-alias-elision— the host cases all fail before a message is built, andunsupported_node_messageis file-local. What the test does show is that the four pinned relations and the added case still behave unchanged.Verification
libggml-hrx.sobuilds clean with-Werror(ninja -j 2);test-hrx-view-alias-elisionruns the same four relations plus the unregistered-carrier case.Stacking
1bit/1467-alias-reason(#262) ← #258 ← #257. Merge order: #257 → #258 → #262 → this.Refs ggml-org#1467