Repository navigation
bug: two int8 H3 jobs on two cards OOM-kill the host - dispatch is VRAM-aware only, and the earlier job was the one killed #869
Description
What happened
2026-10-13 ~02:15-02:24 on lem (125 GB host RAM): two H3 int8 jobs dispatched to the two cards at once.
7cba28c2095b(H3Q1_base_int8_30step, cuda:0, submitted first): "Worker process died: killed by SIGKILL (typically the out-of-memory killer)"7cbac071da26(H3Q4_turbo8_int8, cuda:1, submitted ~9 min later): "Worker process died: killed by SIGTERM"
An int8 H3 Ref2VA job holds ~62-63.5 GB RSS steady after load (host_memory_rss_mb 63471 in job 27b9d0bff514's memory event at +44 s; transformer_ref quantized cache alone is 34 GB, text encoder 35.6 GB on disk). Two of them don't fit 125 GB.
Two problems
- Dispatch doesn't gate on host RAM.
server/admission.pycomputesvram_needand the pool routes on it (idea: support multiple GPUs for concurrent workload execution #462), andhost_memory_projectiononly produces validate warnings. The second job should have waited (or been refused for that card) when projected RSS of the running job + the new one exceeds available host memory. - The wrong job died.
_rank_for_oominserver/jobs.pyraises the later-started worker'soom_score_adjso "a host-RAM squeeze kills the later-started worker - one job - rather than whichever is largest". Here the earlier job (Q1) got SIGKILL. Possibilities: the cuda:1 worker was already alive from an earlier job (so it was not "this call just started" and kept a lower score), or the ranking ran after the squeeze. Then Q4 also died (SIGTERM) - unclear by whom; maybe the pool restarting after the first death.
Expected
The second int8 job queues until the first finishes (or the agent is told at submit that it will wait for host RAM). If a squeeze happens anyway, the later job dies and the earlier survives.
Workaround in use
Run int8/bf16 H3 one at a time (tools/ladder.py waits for lem to be idle). int4 jobs (~35 GB) do run two at once.
Questions
- Use observed peak RSS per (pipeline, weights_dtype) from
observed_costas the projection, orhost_memory_projection's fit? - Should a waiting-for-host-RAM job show a distinct status so the agent can quote the delay?
Source: H3 quality matrix on lem (2x RTX 3090 24 GB, 125 GB host RAM), workspace h3-quality, Known Issues ep 6 shots, seed 42. Full data and per-run assessments: ~/testing/opus/known-issues/quality/matrix.json, report quality/REPORT.md; every run in h3-quality carries a note with its config, minutes and score. Related: #861 (follow-up experiments).
Activity
dkackman commentedon Oct 14, 2026
triage: escalate — part 1 adds a new dispatch gate on host RAM in admission/the pool. That needs a choice of projection (observed peak RSS vs
host_memory_projection's fit) and maybe a new waiting status consumers would see. Both are open questions in the issue. Part 2 (_rank_for_oompicking the wrong victim) is a self-contained bug and could go to a fix session on its own if you'd rather split it off. (claude-opus-5-5, anthropic)dkackman commentedon Oct 14, 2026
Review: split. Part 2 is now #881, going to a fix session. Part 1 goes to the lead for a plan.
Part 2 (wrong job died). The cause is in
_rank_for_oom. Its +100 bump is only about 12.5 GB of OOM badness on a 125 GB host, so a 63 GB earlier job still outscores a smaller job that is still loading. It also ranks once per worker pid, not per dispatch. The fix: on every dispatch, set the dispatching worker to 1000 and the others to 0. Details, plus the SIGTERM investigation (the kernel's OOM killer never sends SIGTERM), are in #881.Part 1 (dispatch gate on host RAM): approved for planning. Answers to the open questions:
host_memory_job_peak_rss_mbper (pipeline, weights dtype) from history first, then thehost_memory_projectionfit. If neither has a figure, don't gate, as today.queued, and its warnings and events carry the reason, e.g. "waiting for host RAM: needs ~63 GB, ~60 GB free". A new status would ripple through the UI, the API models and MCP for something a reason string already says.claude-opus-5-5 / anthropic, for Don
21 remaining items
dkackman commentedon Oct 14, 2026
Spec for plan v2. All cases are in
regression-suite-complete.md(harnest), each markedpending:for its stage.#909 (A: memory key + projected host-RAM peak, no gating)
waiting_for/queue_wait(dispatch unchanged)weights_dtypeint4 and int8 are distinct keys, and a never-run dtype has no figurebasis: "fit", matching the projection warninghost_memorykey and no error (a new workflow, a task-only one, pre-column history)#910 (B: host-RAM dispatch gate,
queue_wait,waiting_for)waiting_forappears onget_jobandlist_jobs; onequeue_waitper hold; the job starts after the first finishes; all succeed; nothing lands inwarningswaiting_forand leaves the other held job intactminimax-h3skill reference drop "One int8 job at a time" / "wait between variants"Notes (not blocking):
acorn-wars/shots-batchkeeping only pre-column list history (M-F029). It reports itself skipped, not failed, if that history is gone or has gained a same-key run.docs-review. I left it out of the cases.projected_mb. If the message rounds differently, say so at hand-off.— tester, claude-opus-5-5 via anthropic
Merge feat/ui-ux-improvements into develop
Merge docs/869-closeout (#869)
dkackman commentedon Oct 15, 2026
Close-out (claude-opus-5-5, anthropic): both stages are closed and verified on lem. #909 (A: memory key and projected host-RAM peak) and #910 (B: the dispatch gate,
queue_waitandwaiting_for).Design record:
docs/proposals/complete/host-ram-dispatch-gate-complete.md, merged todevelopas 36e20e7 (commit 215688d). It covers what was built, what was deferred and why, and the bounces per stage. Also in that commit:todo.mdshipped entry;series-episodes/references/cinema-path.mdloses a stale "One int8 job at a time" that stage B missed.Docs only, so no deploy. C-F495 (single worker or MPS) was never run on lem and still carries
pending: #910.verify: run every case whose
source:names one of this feature's stages, and close the parent if they all pass.dkackman commentedon Oct 15, 2026
Feature-parent verify, progress so far — claude-opus-5-5 via anthropic, lem on develop (stages #909/#910 deployed).
Free / cheap cases whose
source:names #909 or #910, all run over MCP this session in workspaceregression-complete:c-f486-t2i(text-to-image, seed 42): validate before →host_memory {3347, observed, runs 11}. Ran J18e8e51e217fe(succeeded, nowaiting_for, noqueue_waitevent). P = maxhost_memory_job_peak_rss_mb= 3215. Re-validate →{3347, observed, runs 12}(≥ P, not > 2×P). Inline W gives the identical{3347, observed, 12}. W2 (offloadmodel) → valid, nohost_memorykey.{int4}→{47481, observed, 11};{int8}→{89391, observed, 4};{bf16}→ nohost_memory. Distinct keys. (Note: int8 at ~87 GB is well over the ~63 GB in the issue body; not a C-F487 finding.){61566, fit, runs 7}, no warnings.model→ valid, nohost_memory, no host-memory warning (sequentialalready had 1 observed run on this box, so I used the other enum value). qr_code task-only → valid, nohost_memory. Call 3 not run: I didn't find a named non-list workflow with pre-column history and no run since stage A.c-f490-t2i(offloadmodel, 200 steps): nohost_memorybefore; J148ac33b942f5cancelled at denoise step 9/201 → statuscancelled; re-validate still has nohost_memory. Failed arm not run.waiting_for; neither forbidden phrase present.Case-wording nit (not a server finding): C-F486/C-F490 say
run_workflow(workflow="<name>"), butworkflowis the inline-JSON parameter; a saved name goes inname.Next: the lem-only dispatch cases C-F491–C-F494. C-F495 is single-worker only and is skipped on lem (stays
pending: #910).dkackman commentedon Oct 15, 2026
Feature-parent verify, final: all cases pass. Run by claude-opus-5-5 via anthropic against lem on
develop. Every call was made over MCP this session in workspaceregression-complete. The earlier progress comment has C-F486–C-F490 and C-F496.The lem dispatch cases (2× RTX 3090, 125 GB host RAM):
05bb7dad9239(int8, seed 42) ran alone on cuda:0 and succeeded in 431 s. Re-validate gave M = 94950 MB (observed, 5 runs).545371b214f7started on cuda:0 at once.9687dbfce77dstayedqueuedwhile cuda:1 was idle. Bothget_jobandlist_jobsshowedwaiting_for: "waiting for host RAM: needs ~93 GB, ~60 GB free"(later "~59 GB free"). It had exactly onequeue_waitevent (reason: host_ram, same message), and that was still one on two re-checks minutes apart.warnings: [].warnings: []and still exactly onequeue_wait. Nothing was killed.d3366af99adb(text-to-image) backfilled onto cuda:1 past the held J3. It succeeded with noqueue_wait.90a092528653(int8) was held: onequeue_wait, "needs ~93 GB, ~59 GB free".cancel_job(J5): statuscancelled,started_at/device/run_dirnull, nowaiting_for. Its events are queued → queue_wait → cancelled, with no load phase. It was gone fromlist_jobs(status="queued,running"), and J3 was still queued withwaiting_forintact.{47481, observed, runs 11}for int4, which is below M. K1 was skipped because int4 already had 11 observed runs, and seeding that history is all K1 does.8f0a3c3b016a(cuda:0) and K33df6483eace5(cuda:1) were submitted back to back and both started at once. Neither hadwaiting_foror anyqueue_waitevent, and both succeeded.78a1de7051e7was an inline t2i withoffload: "model". Validate gave nohost_memory. It ran while J2 held cuda:0 and J3 was host-RAM-held: it started on cuda:1 at once, had noqueue_wait, and succeeded.aff3f5842861was a lone int8 run on the idle box. It started on cuda:0 immediately with nowaiting_for. I then cancelled it to free the box for C-F493, since the case only checks the start.pending: #910.Deviations from the cases as written:
cached_steps: 1). The memory key is the same.Metrics:
Cleanup: deleted the outputs of J1–J4, L1, L2, K2 and K3. J5 never ran, so it has no run directory.