Repository navigation
hrx: say once, at load, when a model's nodes fall back to the CPU (engine #991) - #197
Conversation
…gine ggml-org#991) An HRX build prints "device HRX0" and then runs part of the graph on the CPU without saying so. The engine admits a dense MUL_MAT only when input_size % 256 == 0 (eight gate sites in dispatch-registration/common/ dispatch-mul-mat.cpp, stated by common_is_supported_dense_input_size()), so a model whose hidden size is not a multiple of 256 loses its matmuls silently while the SSM/scan/norm side stays on HRX: measured with GGML_HRX_STRICT=1, Zamba-7B-v1 (n_embd 3712) puts 383 of 5113 nodes on the CPU and BlackMamba-2.8B (1472) 145 of 1179, while Zamba2-2.7B (2560) is fully claimed. The placement observer already counted the off nodes for GGML_HRX_REPORT and GGML_HRX_STRICT; it now also counts the off matmuls whose input_size is not a multiple of 256 and, in the default configuration, prints one WARN per process on the first graph that loses a node: placement_observer: placement: 383 of 5113 nodes on the CPU (MUL_MAT x 281, CONT x 89, UNARY x 13) - 3712 is not a multiple of 256, so 281 matmuls have no HRX dense schedule (input_size % 256 != 0) GGML_HRX_REPORT=0 silences it and a value that is not a number counts as unset, so a typo cannot hide the line (an explicitly numeric <=0 does silence it, as before); GGML_HRX_REPORT=1 keeps the pre-existing per-graph report instead of printing both; GGML_HRX_STRICT=1 is unchanged. WARN rather than INFO because a placement fallback is a degraded state of the same class as the residency guardrail, and it should survive a front-end that keeps warnings and drops info. Note llama-bench without -v installs a null log callback through llama_log_set -> ggml_log_set, so no ggml line of any severity reaches the terminal there; use -v, or llama-server/llama-cli, which print it as usual. No behaviour change: placement, kernels and the split are untouched. The scan is bounded to the first 16 graph evaluations, which is where the prefill graph and the first decode steps are built. Scanning every graph for the rest of the run would put a per-node backend lookup plus a vector allocation in the default hot path for a log line, and for a fully claimed model the scan would never terminate itself. GGML_HRX_STRICT=1 (aborts on the first off node) and GGML_HRX_REPORT>=2 (every graph) remain the exhaustive probes. Measured on gfx1151 under gpu-hold (llama-server, default verbosity, HRX0, -ngl 99): Zamba-7B-v1 prints the line above once per process; with GGML_HRX_REPORT=0 it prints nothing; with an unparseable GGML_HRX_REPORT it still prints; Zamba2-2.7B prints nothing; the greedy continuation of "The capital of France is" is unchanged from the pre-change reference in every arm. GGML_HRX_STRICT=1 still aborts from inside the observer at the same node_14 MUL_MAT f32 ne=[14848,512,1,1] with the same 383/5113 counts.
|
#197 ready for review at 37ea88d Engine ggml-org#991's observability half, on its own branch off |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY on the code
37ea88dcf, 1 file (+56/-2, ggml/src/ggml-hrx/ggml-hrx.cpp). A load-time line the placement report was hiding, and
the gating is the part worth checking:
- The old gate silenced it:
if (!strict && (level <= 0 || (level < 2 && reported)))returned before any node scan
whenever nobody asked forGGML_HRX_REPORT, so "this model lost nodes to the CPU" only ever appeared under
GGML_HRX_STRICT=1. The new gate keeps the cheap exit but only once the line is said, deliberately silenced, or past
the scan budget —kFallbackScanGraphs = 16, and the counter is only incremented on thelevel == 0path, so a
GGML_HRX_REPORT=1run does not spend the budget. placement_fallback_quiet()fails loud: whole-stringstrtol,parsed <= 0only.GGML_HRX_REPORT=0is quiet,
GGML_HRX_REPORT=xis not (unlikeatoi, which would read 0 and silently hide the line). That is the right
direction for a knob whose job is to reveal a degraded state.- The reason is derived, not guessed:
src[0]->ne[0] % 256 != 0onMUL_MAT/MUL_MAT_ID—src[0]is the weight,
sone[0]isinput_size, which is what the message says ("… is not a multiple of 256, so N matmuls have no HRX
dense schedule"). With no matching cause the message still prints, just without a reason suffix. - Once per process via
fallback_reported.exchange(true), so a model that loses nodes to the CPU cannot flood a log. - The
GGML_LOG_WARNchoice with its own caveat (llama-bench nulls the log callback without-v) is documented at the
call site, which is where someone will look when the line does not appear.
Not run by me: the HRX device path (this TU only builds under -DONEBIT_HRX=ON and the fork's HRX build is an
ExternalProject). The gate logic I checked by reading the three branches against level/strict; a fork HRX build plus
one model whose hidden size is not a multiple of 256 is what would exercise it end to end.
What
An HRX build prints its device and then runs part of the graph on the CPU without saying so. The engine admits a dense
MUL_MATonly when
input_size % 256 == 0(eight gate sites indispatch_registration/common/dispatch-mul-mat.cpp, stated bycommon_is_supported_dense_input_size()indispatch-mul-mat-common.h), so a model whose hidden size is not a multiple of 256loses its matmuls silently — and the SSM/scan/norm side stays on HRX, which makes the result look like a normal device run.
Measured with
GGML_HRX_STRICT=1before this change:Until now the only way to see this was
GGML_HRX_STRICT=1, which aborts the run.How
The placement observer already counted the off nodes for
GGML_HRX_REPORT/GGML_HRX_STRICT. It now also counts the off matmulswhose
input_sizeis not a multiple of 256 and, in the default configuration, prints one line per process on the first graphthat loses a node:
Knobs, unchanged in meaning except the new default:
GGML_HRX_REPORT=0: silent (a model that loses nodes no longer announces itself);GGML_HRX_REPORTset to something that is not a number: treated as unset, so a typo cannot hide the line the wayatoi()would (
=x→0→ quiet); an explicit numeric<= 0still silences it;GGML_HRX_REPORT=1: the pre-existing per-graph report instead of the fallback line (not both);GGML_HRX_REPORT>=2: per-graph, every graph;GGML_HRX_STRICT=1: unchanged, aborts on the first node off HRX.Severity is
WARN: losing nodes to the CPU is a degraded placement state of the same class as the residency guardrail, so it shouldsurvive a front-end that keeps warnings and drops info.
Cost. The scan is bounded to the first 16 graph evaluations, which is where the prefill graph and the first decode steps are
built. Scanning every graph forever would put a per-node
ggml_backend_sched_get_tensor_backend()lookup plus astd::vector<int64_t>(GGML_OP_COUNT)in the default hot path, and for a fully claimed model (the common case) the scan neverterminates itself.
GGML_HRX_STRICT=1andGGML_HRX_REPORT>=2remain the exhaustive probes.No behaviour change: placement, kernels and the split are untouched; only the observer's reporting.
Verification
Build: 211 targets green (
build-hrx, HRX enabled,BUILD_EXIT=0). The wholeggml/src/ggml-hrxsubtree is byte-identicalbetween this PR's base
d8bd0ff60and the tree I built and ran (git diff --stat d8bd0ff60 <built> -- ggml/src/ggml-hrxis empty;ggml-hrx.cppblobd11ee71e3on both sides), so the device evidence below is this translation unit.Device, under
gpu-hold, gfx1151,-dev HRX0 -ngl 99:GGML_HRX_REPORT=0GGML_HRX_REPORT=15113 nodes, 383 off HRX (MUL_MAT x 281, CONT x 89, UNARY x 13), 384 graph splits-p 128 -n 32(many graphs)GGML_HRX_STRICT=1node_14 MUL_MAT f32 ne=[14848,512,1,1] … 383 of 5113 nodes off HRX … 384 splitsllama-serverat default verbosity (the path a user reads),gpu-hold, one greedy/completionper arm so the graph is really built:Wline0.06.535.157 W placement_observer: placement: 383 of 5113 nodes on the CPU (…) - 3712 is not a multiple of 256, so 281 matmuls have no HRX dense schedule (input_size % 256 != 0)GGML_HRX_REPORT=xGGML_HRX_REPORT=0Every arm's greedy continuation of
The capital of France isis unchanged from the pre-change reference (" Paris.\nThe capital of the United States is Washington, D.C."for v1,
" Paris."for Zamba2), so nothing above was bought with different maths.The 383/5113 and 281 matmuls reproduce the pre-change
GGML_HRX_STRICT=1measurement exactly.One thing to know when testing it
llama-benchwithout-vdiscards all ggml logging:llama_log_set()forwards toggml_log_set()(src/llama-impl.cpp:32)and
tools/llama-bench/llama-bench.cpp:2289installsllama_null_log_callbackwhen!params.verbose. No ggml line of any severityreaches the terminal there, so
llama-benchneeds-v.llama-serverandllama-cliprint it as usual. I lost a verificationcycle to this, and then a second one to a readiness gate that accepted llama-server's 503 "Loading model" as "ready".
Not doing
The real fix — a tail schedule for non-aligned
input_sizein the denseMUL_MATpath — is the other half of engine ggml-org#991 andneeds its own kernel work; this PR is the observability half only. No tolerance changes, no placement changes.
Refs engine ggml-org#991 (observability half; the tail-schedule half stays open).