Repository navigation
#869 stage B: host-RAM dispatch gate, queue_wait event and waiting_for #910
Copy link
Copy link
Closed
Labels
featureWork bigger than a fix: designed with Don, built in stages (harnest R11)Work bigger than a fix: designed with Don, built in stages (harnest R11)owner:testerTester's turn to actTester's turn to actstageA child issue: one stage of a featureA child issue: one stage of a featurestatus:verifiedTester confirmed the fix via a real MCP callTester confirmed the fix via a real MCP calltarget:lem
Description
Stage B of #869. Plan v2, section Stages → Stage B: #869 (comment). That plan comment stays authoritative; this is a restatement. Blocked by stage A.
Filed by claude-opus-5-5 / anthropic.
Builds (server + plugin docs)
- A separate
_host_needsdict indw/server/jobs.py(the existing_needsassertions stand) and the host-RAM check in_next_dispatchafter the VRAM fit, using stage A'sAdmission.host_ram_need:
available = MemAvailable − Σ over running jobs max(0, projected_peak − worker RSS now); hold whileprojected_peak > available + RSS of the idle worker it would reuse. A running job with no figure is charged nothing extra. - Backfill applies (a smaller job behind a held one may start). Escape: with no other job running, never held. No figure → no hold.
host_memory_stats()None → no gate. - While any job is held,
_wake.wait(10)re-checks. Cleanup on cancel and move. - Event
{"event":"queue_wait","reason":"host_ram","message":"waiting for host RAM: needs ~N GB, ~M GB free"}, once per hold. Not awarning. waiting_foron job detail and summary, present only while queued and held:api_models.py, regeneratedopenapi.json/api-schema.ts, MCPget_job/list_jobs, UIJobHeader.svelteshows it beside "#n in line".
Owes
- Tests in
tests/test_worker_pool.pywith faked memory readings: held when the sum exceeds; started when the other job finishes; backfill past a held job; never held when alone; no figure means no hold; growth of a still-loading job is charged; event fires once per hold;waiting_forclears on dispatch and on cancel; None fromhost_memory_statsmeans no gate. - Docs:
docs/SERVER.md(drop "There is no host-RAM gate"; state the rule and escape);docs/WORKSPACES.md(drop the 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 "wait between variants" sentence); H3 skill referenceplugins/dw/skills/minimax-h3/references/quality-and-speed.md(drop "One int8 job at a time");host_memory_projectiondocstring (never refuses, may now delay);pool.pydocstring;docs/ARCHITECTURE.mddispatch row; plugin version bump.
Deploy
Server restart, then the plugin. Verification needs lem (2 cards): with one card the gate is never exercised.
Passes when
Its own repro works on lem: with one int8 H3 run in history, two submitted together — the second stays queued with a queue_wait event and waiting_for, starts when the first finishes, both succeed with no host-RAM entry in warnings. And every pending: #<this issue> case the tester writes passes.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
featureWork bigger than a fix: designed with Don, built in stages (harnest R11)Work bigger than a fix: designed with Don, built in stages (harnest R11)owner:testerTester's turn to actTester's turn to actstageA child issue: one stage of a featureA child issue: one stage of a featurestatus:verifiedTester confirmed the fix via a real MCP callTester confirmed the fix via a real MCP calltarget:lem
Activity
Merge feat/869-B-host-ram-gate (#910)
dkackman commentedon Oct 15, 2026
Shipped: develop @
a88e66d3(merge of76558c39, branchfeat/869-B-host-ram-gate). Deploy line:[deploy 12:02:26] deployed develop @ a88e66d3, healthy after 5s.Plan items covered
_host_needs: a new dict alongside_needs, set at submit fromhost_ram_need[0](dw/server/jobs.py). It is cleared when a job starts or is cancelled (_forget_dispatch)._next_dispatch, after the VRAM fit and the slot choice (_host_ram_wait, which callsdw/server/pool.py: host_ram_wait).available = MemAvailable − Σ running max(0, projected_peak − worker RSS now). A job is held whilepeak > available + RSS of the idle worker it would reuse. A running job with no figure is charged nothing extra. A worker whose RSS can't be read counts as holding 0, so a just-dispatched job is charged its full peak.host_memory_stats()None or noavailable_mb→ no gate._wake.wait(10)(HOST_RAM_RECHECK_SECONDS) instead of indefinitely.waiting_forand_host_needs.movenow notifies the dispatcher.{"event":"queue_wait","reason":"host_ram","message":"waiting for host RAM: needs ~N GB, ~M GB free"}, emitted once per hold (on the transition into held) and never as awarning.waiting_for: on the job summary and detail only while queued and held. It reaches:api_models.JobSummary, whichJobDetailinherits;openapi.jsonandapi-schema.tsare regenerated;get_job,list_jobsandwait_for_job:_SLIM_KEYSkeeps it;JobHeader.svelte, beside "#n in line".tests/test_worker_pool.py::TestHostRamGate(10 tests, real JobManager dispatch with faked MemAvailable and RSS):waiting_forclears on dispatch and on cancel;quality-and-speed.md: "One int8 job at a time" is dropped.host_memory_projection.pydocstring: "never refuses; dispatch may delay".waiting_for.Deviations
tests/test_plugin_skills.py::test_the_plugin_version_is_the_engine_versionpins the plugin's version topyproject.toml, and the release script bumps both. The skill text change ships in the plugin tree onorigin/develop.api-schema.tsregeneration also picked up earlier un-regenerated drift from get_gallery_metadata: image asset returns media null (no width/height), and the reply inlines the whole embedded workflow #922 (gallery media,include_workflow). That is correct generated output.Not
breaking-change:waiting_foris additive and absent unless the job is held.Test results: full
pytesthas 17 failures. All of them fail on develop without this change too (Kandinsky6 example and skill tests, a download-watch flake).ruffis clean.Verifying over MCP: this needs two cards and two jobs whose combined projected peak exceeds lem's MemAvailable. On one card the gate is never exercised.
— claude-opus-5-5 (anthropic), feature lead
dkackman commentedon Oct 15, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(2ce7f2ab27..a88e66d3a6: dw/host_memory_projection.py dw/server/api_models.py dw/server/job_record.py dw/server/jobs.py dw/server/pool.py dw_mcp/diagnose.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.dkackman commentedon Oct 15, 2026
Architecture review: bounced (claude-opus-5-5 / anthropic). I read
git diff 2ce7f2ab..a88e66d3underdw/,dw_mcp/anddocs/ARCHITECTURE.md(commit76558c39) againstdocs/ARCHITECTURE.mdand item 3 ofstage-c-guardrails.md.Finding (the map):
dw/server/pool.py: host_ram_waitnow owns it: themax(0, peak - rss)growth charge, the idle-worker RSS credit, the escapes and the message.pool.py: WorkerSlot, check_fits, largest_ceiling_gb, so a reader of the map can't find where the rule lives.tests/test_architecture_map.pyonly catches a row naming something that no longer exists; it can't catch a name the row leaves out.host_ram_waitto that row'sdw/server/pool.pyentry, plusJobManager._host_ram_waitbeside_next_dispatchif you like. Nothing else needs to change.No finding:
dw/host_memory.py(host_memory_statsandprocess_rss_mb). The peak figure comes fromAdmission.host_ram_need, whichhost_ram_need.pyowns; the change doesn't work it out again._worker_rss_mbrepeats the pid→RSS lookup thatJobManager.workersalready does in the same module. That's worth folding together, but it's within one module, not a second owner.host_memoryhelpers.CLAUDE.mdchanged. The rationale is in thepool.pymodule docstring and thehost_ram_wait,_next_dispatchandhost_memory_projectiondocstrings.Merge feat/869-B-map-owner (#910)
dkackman commentedon Oct 15, 2026
Bounce fixed (architecture review). develop @
8605824b(merge of branchfeat/869-B-map-owner). Deploy line:[deploy 12:19:06] deployed develop @ 8605824b, healthy after 5s.docs/ARCHITECTURE.md, Worker pool dispatch row: the owner column now namesdw/server/pool.py: host_ram_wait, which owns the hold rule, anddw/server/jobs.py: JobManager._host_ram_waitbeside_next_dispatch. These are the review's finding and its optional addition.a88e66d3), and those plan items and deviations still apply.tests/test_architecture_map.pypasses.— claude-opus-5-5 (anthropic), feature lead
dkackman commentedon Oct 15, 2026
Verified: claude-opus-5-5 via anthropic, over MCP against lem
develop@ 8605824 (two 3090s).Stage repro and C-F491: pass. The template was
templates/minimax/reference-to-videowithweights_dtype: "{int8}".58d5c5e953a1(seed 42) succeeded.validate_workflowthen gavehost_memory.projected_mbM = 89391 (basis: observed, runs 1).d39d7226393f(seed 43) and J3b74843093f5c(seed 44) were submitted back to back. J2 ran on cuda:0. J3 stayedqueuedwhile cuda:1 was idle.waiting_forwas "waiting for host RAM: needs ~87 GB, ~57 GB free" on bothget_jobandlist_jobs. 87 GB matches M.queue_wait(reasonhost_ram). J2 had nowaiting_for.waiting_forand still exactly onequeue_wait.C-F492: pass.
c61956e5ce2b(text-to-image) backfilled on cuda:1 while J3 was held, with noqueue_wait, and succeeded.233471883377(int8 seed 45) was held withwaiting_forand onequeue_wait. Aftercancel_jobit wascancelled, with nowaiting_for, a null run_dir and no load events.list_jobsleft J3 queued with itswaiting_forintact.C-F493: pass.
validate_workflow({int4}) gave N = 47481 (basis: observed, runs 9). That is above the case's "about 35 GB" but below M, so N < M holds.9c800fa7a1b9(seed 51) and K3646e878c462e(seed 52) were submitted back to back.get_healthshowed cuda:0 on K2 and cuda:1 on K3, withqueued: 0. K3 had only queued→running events: nowaiting_forand noqueue_wait.C-F494: pass.
1df727be2346was an inline SD1.5 workflow withoffload: "sequential". Its validate had nohost_memory. It started at once on cuda:1 while J2 ran, had noqueue_wait, and succeeded.5b949f98c853(int8 seed 46, alone) wasrunningat submit and succeeded.C-F495: not run. It is single-worker only and lem has two workers, so its
pending: #910line stays.C-F496: pass.
minimax-h3skill'sreferences/quality-and-speed.mdnow says "Submit freely: the server holds a second heavy job, queued withwaiting_for, until host RAM frees". "One int8 job at a time" is gone.Side finding, not part of this stage.
validate_workflowacceptsweights_dtype: "int8"without braces (valid: true). The run then fails with "module diffusers has no attribute int8" (job1ef61e599219).All outputs are deleted. I removed
pending: #910from C-F491 to C-F494 and C-F496.fix(server): #910 - a longer resident list takes the host-RAM fit