Repository navigation
HRX: Qwen3-4B --parallel 4 NaN logits on a never-used slot while another GPU job runs (CWSR off) #528
Description
1bit-canonical on strixhalo aborted at 2026-10-23 07:45:12 ADT with a whole-vocabulary NaN:
367.05.963.920 E common_sampler_sample: HRX returned NaN logits: count=151936 of 151936,
index range [0..151935], contiguous=yes, continuing=no (engine#315)
/home/bcloud/wt/tsvchrx4/src/third_party/llama.cpp/common/sampling.cpp:620: HRX: NaN logits (engine#123)
Every one of the 151,936 logits was NaN, and the whole range was contiguous, so this is not one lane or one row of the output — it is the output of the request as a whole. The boot is CWSR off (/sys/module/amdgpu/parameters/cwsr_enable = 0), so the #315 eviction mechanism does not apply here.
What the slot was doing
07:45:09 slot get_availabl: id 3 | task -1 | selected slot by LCP similarity, f_sim_best = 0.252 (> 0.100 thold), f_keep = 0.472
07:45:09 slot launch_slot_: id 3 | task 187 | processing task, is_child = 0
07:45:11 slot print_timing: id 3 | task 187 | prompt eval 210.74 ms / 175 tokens (830.42 tok/s)
07:45:11 slot print_timing: id 3 | task 187 | eval 1624.04 ms / 29 tokens (17.86 tok/s)
07:45:11 slot print_timing: id 3 | task 187 | graphs reused = 190
07:45:11 slot release: id 3 | task 187 | stop processing: n_tokens = 262
07:45:11 slot get_availabl: id 2 | task -1 | selected slot by LRU, t_last = -1
07:45:11 slot launch_slot_: id 2 | task 218 | processing task, is_child = 0
07:45:12 E common_sampler_sample: HRX returned NaN logits (above)
07:45:18 systemd: Main process exited, code=exited, status=1/FAILURE
07:45:29 systemd: Scheduled restart job, restart counter is at 1 (active again, ready on :8095)
The abort is on slot 2's first task after selected slot by LRU, t_last = -1 — a slot that had never been used in this process (6 h 07 min uptime, 18.8 s CPU) — one second after slot 3 finished a 175-token prompt / 29-token generation normally with 190 reused graphs. Backtrace (the crash handler's): common_sampler_sample <- server_context_impl::post_decode lambda <- iterate <- post_decode <- update_slots <- server_queue::start_loop <- llama_server.
Concurrent GPU work
An hw-gate run was on the same GPU for the whole window: run 39940594897 (hw-gate.yml, head f061e5df = the gpu-kernels 744bd713d968 bump, created 2026-10-23T10:39:01Z, updated 10:46:28Z → 07:39:01–07:46:28 ADT), i.e. it spans the 07:45:12 abort. GTT at the service's restart one line later: GTT 95.1 of 120.0 GiB free, MemAvailable 84.7 GiB.
Environment
- Unit:
1bit-canonical.service+ drop-inhrx.conf—1bit serve -m ~/models/Qwen3-4B-Q4_K_M.gguf --device hrx --ctx-size 8192 --parallel 4(serve turnsqwen.attentionoff and passes-kvu; docs/serve.md, engine serve --parallel on HRX: shared KV cache, no qwen.attention (Qwen3-4B 4 slots 39 -> 153 tok/s) #240),ONEBIT_LLAMA_SERVER=~/.local/share/1bit-hrx-3092596/hrx/llama/bin/llama-server,HSA_USERPTR_FOR_PAGED_MEM=0,Restart=always. - Package:
~/.local/share/1bit-hrx-3092596(installed 2026-10-19T22:57 ADT, own copy, never rebuilt in place): engine309259627, llama.cpp00f2a68de(fork Vulkan: carry ggml-org#28243 (Qwen3.8-Flash-Next MTP) on our fork branch #110), hrx-system82804197bcbe, gpu-kernels7562196; built with-DONEBIT_HRX=ONand the private HIP add-on (ONEBIT_GPU_PRIVATE), packed byscripts/package-linux.sh 3092596. - Kernel
7.3.0-rc4-1bit-perfopt, CWSR off,--parallel 4(n_slots = 4, n_ctx_slot = 8192, kv_unified) as logged at load. - The service restarted itself once (
NRestarts=1).coder-llm(Qwen3-Coder-30B-A3B, same package family) has 0 restarts since the 01:38 boot.
One occurrence in about 6.5 h of uptime.
Related
#433 (ZAYA1-8B NaN with a second HRX process on the GPU) was re-checked clean today: 120 fresh loads, 0 NaN, no heavy GPU neighbour. #123 is the guard that turned this into an abort instead of a silently wrong token. The hrx-gdn-* NaN family is the 27B MTP path, a different model and a different graph. --parallel on HRX is per-slot KV with unified cache and qwen.attention dispatch off (engine #240).
Next
Reproduce with 60 requests per arm on this package and on main, each request landing on a fresh/LRU slot (prompts varied so LCP similarity stays below the 0.1 threshold), with and without a GPU neighbour inside the same claim — the neighbour being any heavy job of ours, not the real hardware gate.
Activity
bong-water-water-bong commentedon Oct 23, 2026
Reproduction attempt: 0 NaN in 300 requests across five arms, so this is one occurrence that these arms do not reproduce. Data and the two conditions I could not reproduce below.
Arms (each 60 requests, every request on a never-used or LRU slot)
Two builds,
1bit serve -m ~/models/Qwen3-4B-Q4_K_M.gguf --device hrx --ctx-size 8192, private port andsystemd-run --scope -p MemoryMax=8G,HSA_USERPTR_FOR_PAGED_MEM=0as the unit sets it, all inside onegpu-hold -l "pi issues-a" -t 14claim per arm pair:--parallel309259627, llama.cpp00f2a68de, gpu-kernels7562196)7871c71d(llama.cpp851fa6c8d8f2, hrx-system82804197bcbe, private HIP add-on744bd713d968,1bitsha256703edb65…)Method, so the arm shape can be checked: each request carried a 16-character nonce plus random words (~300 prompt tokens,
max_tokens32, temperature 0), so no two requests shared a prefix. The server's own slot log confirms it: every--parallel 4arm filled all four slots fromselected slot by LRU, t_last = -1first, then rotated by LRU, and no request was ever selected by LCP similarity (selected slot by LCP similaritycount = 0 in every arm) — i.e. no prefix reuse at all, which is the state the failing slot 2 was in. The neighbour was our own second HRX serve hammered from a thread (127 requests, 0 NaN), never the real hardware gate.Environment: strixhalo, kernel
7.3.0-rc4-1bit-perfopt, CWSR off, 90 W cap, PCM pad (23 Oct), Tctl 55–63 °C across the two claims, GTT 34.9 GB of 128.8 GB used and MemAvailable 74 GiB at claim start (the incident's line wasGTT 95.1 of 120.0 GiB free, MemAvailable 84.7 GiB).sudo -n dmesgafter each claim: no amdgpu fault, reset or ring timeout (the only amdgpu lines are the boot-timeREG_WAIT/TMZones from 01:37).What this does and does not rule out
Ruled out as a sufficient condition, on this box, in 300 requests: a never-used slot (4 hit per arm), full
--parallel 4multi-sequence serving, the 2026-10-19 package alone, the current release line alone, and a second model sharing the GPU.Two conditions of the incident are not in these arms:
39940594897wastest-backend-opsop sweeps plus repeated model loads: many different graphs, many allocations, the kernels this path never touched. That is the closest thing to a difference in mechanism and the next thing to reproduce.Next probes, in order: (1) same five arms with
test-backend-ops -b HRX0 -o MUL_MATin a loop as the neighbour instead of a second serve; (2) a long soak of the failing configuration (one--parallel 4serve, hours, unique prompts) withGGML_HRX_NAN_CONTINUE=1so a NaN is captured with its index range instead of aborting; (3) keep the abort behaviour in the service: the guard printedcount=151936 of 151936 … contiguous=yes, which is the diagnostic that makes this class of report actionable.Recommendation for the orchestrator (owner decides)
Move
1bit-canonicalto the release-candidate package when the candidate exists, for currency — the running package is from 2026-10-19 (engine309259627, llama.cpp00f2a68de) and predates the current HRX pin, strict placement by default and the warm-up work — but not as a fix for this NaN: 0/300 here attributes nothing to those changes. Rollback stays trivial (the~/.local/share/1bit-hrx-3092596directory and the drop-in'sONEBIT_LLAMA_SERVERline), and no agent should touch the service; the owner restarts it.bong-water-water-bong commentedon Oct 25, 2026
Status from
pi issues-c(the handover of this item) — a reproduction attempt that is still queued, and whatthe code says to look at next. No new NaN was produced; nothing here contradicts the 0/300 from the earlier
campaign.
What I built for it
~/wt/issues-c/nan528.sh, run inside one claim, shaped after the failing configuration rather than after asecond serve:
llama-server -m Qwen3-4B-Q4_K_M.gguf -c 8192 --parallel 4 -kvu -ngl 99 -dev HRX0, i.e. the same unified-KVfour-slot shape
1bit serve --device hrx --parallelpasses, withGGML_HRX_DISABLE_DISPATCH=qwen.attentionset (that is what
app/serve.cppadds, and it is the path that made per-slot answers bleed before serve --parallel on HRX: shared KV cache, no qwen.attention (Qwen3-4B 4 slots 39 -> 153 tok/s) #240);HSA_USERPTR_FOR_PAGED_MEM=0as the unit sets it, andGGML_HRX_NAN_CONTINUE=1so a NaN is captured with itsindex range instead of aborting (the guard at
common/sampling.cpp:608is the diagnostic that made the reportactionable in the first place);
llama-benchon Qwen3-0.6B in a loop inside the same claim, i.e. a neighbour with manydifferent graphs, many allocations, and kernels this path never touches - the closest cheap analogue of the
test-backend-opssweep the hardware gate was running at 07:45 on 23 Oct. The earlier campaign's neighbour wasa second serve (one graph reused), which is the one difference it named as unexplained;
cache_prompt: false, so no request can be picked by LCPsimilarity and every request lands on a never-used or LRU slot; 60 requests per arm, two arms (neighbour on /
neighbour off).
Arm 1 (heavy neighbour) has now run on the pin (llama.cpp
75242f31+ hrx-system82804197bc, our own build,not the 2026-10-19 package), one
gpu-holdclaim, label 90 W cap, PCM pad (23 Oct),sudo -n dmesgafter theclaim: no amdgpu fault, reset or ring timeout.
llama-benchQwen3-0.6B loopMedian request 0.7-0.9 s (175-token prompt + 32 generated), i.e. the shape of the incident's requests; both arms
selected every request by LRU and none by LCP similarity, and neither produced a single NaN line with the guard's
index-range diagnostic armed (
GGML_HRX_NAN_CONTINUE=1).So this makes 0 NaN in 120 requests on the current pin, on top of the earlier campaign's 0/300 across five arms
on the 2026-10-19 package and the release line. A 1-in-~500-request event cannot be settled by 120 requests; what
these arms do settle is that the neighbour class the incident blamed (a second serve then, a bench loop now) is
not a sufficient condition, and that the reproduce path is a soak, not another 60-request arm.
What the incident shape still points at
The abort was on slot 2's first task (
selected slot by LRU, t_last = -1, one second after slot 3 finishedcleanly with 190 reused graphs), and every one of the 151,936 logits was NaN with a contiguous range. That is not
a wrong-value kernel: the whole output of the request is NaN, which is what an unwritten or poisoned output range
looks like - the logits buffer was allocated, the graph ran, and what came back was not a number. Two mechanisms
in this tree can do that and both are independent of
-kvu's correctness:the class engine hrx: fusion must withdraw dispatches it makes redundant (reader emitted before the fused writer, engine #533) #570 is about (a fusion that makes an already-emitted dispatch redundant leaves a reader in
the program), except that the verifier catches those at plan time when it can see them, and a runtime plan
chosen under a different ubatch shape is where it could slip through;
time in that process, read before the first write. A never-used slot is exactly the state that makes this
visible, and a heavy neighbour changes which pool pages are fresh - which is why the neighbour matters.
Both are testable without waiting for a second 6.5-hour occurrence: the arm above records the NaN index range and
the slot id,
GGML_HRX_LOG_TRANSIENT_REUSE=1prints both arena sizes for every program (guarded vs stock), andGGML_HRX_TRANSIENT_REUSE=legacyis the A/B switch for the guarded packer. If an arm does produce a NaN, the nextstep is the same request with the stock packer and with the plan dump of that shape - that separates "arena reuse
without a barrier" from "uninitialized fresh memory" in one run.
Recommendation (unchanged, and now with a second reason)
Do not move
1bit-canonicaloff the 2026-10-19 package for this; move it when the release candidate exists,and add the soak to the candidate's acceptance instead:
GGML_HRX_NAN_CONTINUE=1on one--parallel 4serve fora few hours with unique prompts and a bench neighbour. 60 requests per arm cannot see a 1-in-~500 request event;
a soak with the index-range guard on can, and it costs one claim.
tools: a generated NPU engine leaderboard (#621)
leaderboard: the 27B first-token pair from the #622 trace (#623)
bong-water-water-bong commentedon Oct 27, 2026
Ran the issue's own repro plan on
strixhalo. Two valid arms, four slots each, no NaN.noneneighbour(second HRX server busy for the whole arm)Conditions, so this is auditable rather than a claim: package
~/.local/share/1bit-hrx-3092596(mtime 2026-10-19 22:57),
Qwen3-4B-Q4_K_M.gguf,--parallel 4 --ctx-size 8192,amdgpu.cwsr_enable=0(matches the report), 24-token greedy prompts, prompts generated from/dev/urandomso slot LCP similarity stays under the 0.1 reuse threshold, 4 requests in flight perbatch (a sequential loop only ever uses one slot and cannot reach a never-used slot — my first run
proved that by reporting
slots_used=1 of 4and is marked invalid by the harness now).Logs:
~/lb/528/run-20261027T215046Z-noneand~/lb/528/run-20261027T215131Z-neighbour.What this does not reproduce: the neighbour here is a second HRX server, not the hardware gate's
own workload (
hw-gaterun 39940594897 as named in the report). If the trigger is specific to thatworkload's shape — a bench holding the GPU's queue rather than a second server sharing GTT — this arm
does not cover it. That is the one remaining difference I can see between the report and this run.
Harness is in the repo convention for reruns; it refuses to report a result unless at least three
distinct slots actually launched. Verdict from me: unreproduced, not fixed — leaving the issue open
rather than closing it on absence of evidence, and if it recurs, the useful datum is which GPU job was
co-resident and whether GTT was still growing at the time.