[Bug][Prod][ArtPipe] Concept-art is dead because ArtStudio:StageModelMap is EMPTY — API routes to the legacy in-process stage queue whose consumer can't boot, while the 5 resident model containers sit idle on queues nobody publishes to #595
Reference in New Issue
Block a user
Filed by: QA Team — found while a real user-initiated concept-art generation was in flight.
Summary
Art Studio concept-art generation is non-functional. A live request published at ~21:10Z routed correctly onto
art.asset.stage.concept.requestsand is still sitting there, unread, with zero consumers. It is not failing and not dead-lettering — it is simply never being picked up, and there is no TTL, so it will sit indefinitely.Two independent faults, either of which alone is fatal to the stage.
Fault 1 — the concept-stage consumer can NEVER start (contradictory placement vs bind)
spikersoft-artpipe-modeling(the service running thespikersoft-artpipe-processorimage, which consumes the concept stage) is 0/1 in a permanent reject loop, retrying every ~5 seconds:The service spec is self-contradictory:
But
/mnt/fusioniois SERVER's disk and does not exist on the 4090:Node labels confirm only the 4090 carries
artpipe-gpu:So the constraint pins it to a node that structurally cannot satisfy its bind. It has never started — swarm has simply been retrying it every 5 seconds since the service was last updated (2026-07-13 18:57Z).
This is the same bug class as #553 (closed)
#553 / infra #81 was exactly this: a GPU service pinned to the
artpipe-gpunode while still carrying a SERVER-only/mnt/fusioniobind. The fix there was to drop the bind and self-provision from MinIO instead.artpipe-modelinghas the identical latent contradiction and was missed in that sweep.Fix options:
/mnt/fusionio/spikersoft/art_pipebind with a named volume + MinIO self-provisioning, mirroring what infra #81 did for quiz-generation. Preferred — consistent with the direction already taken./mnt/fusionio/spikersoft/art_pipeon the 4090 (or mount fusionio there). Quicker, but re-introduces the node-local-state coupling that #553 was fixing.⚠️ If (a) is chosen, note the lesson from #592: infra #81 dropped the bind and introduced a
${VAR}secret in the same change, anddocker stack deploysubstituted the unset var with an empty string and exited 0 — shipping a service that looked green but could not work. Do not repeat that. Guard the secret in the deploy job.Fault 2 — there is no concept generator running
Even with the consumer fixed, nothing would generate anything. The model workers:
artpipe-model-sdxlis explicitly set toreplicas=0in its spec, and its image pulls fine (manifest OK), so this reads as a deliberate scale-to-zero rather than a failure. Whether that is intentional needs an owner's call — but as it stands, the concept stage has a consumer that can't boot and a generator that isn't running.Possibly related to #537 (closed) — "Phase 2 blocked: sdxllightning env image is 98 GB". If Phase 2 is still considered blocked, the Art Studio UI should not be accepting concept-art submissions that it cannot service.
Fault 3 (minor, but worth a look) — hunyuan runs the wrong image
Every other model worker runs its own
artpipe-model-<name>image. hunyuan pointing at the processor image looks like a copy-paste error in its stack file. It is 0/0 so it is not currently biting, but it will the moment it is scaled up.User-facing impact
The request does not error, does not retry, and does not dead-letter. It is silently swallowed by a consumer-less queue. From the user's side the generation just never completes, with no failure ever surfaced. Whatever the fix, the Art Studio should surface a failure (or refuse the submission) rather than accept work into a stage that has no consumer.
Related
QA Team — ROOT CAUSE FOUND. This supersedes the fix I proposed above; that advice was wrong.
I originally suggested recreating the
/mnt/fusioniobind on the 4090 or replacing it with MinIO self-provisioning. Don't do either. The bind should not exist at all. The right fix is a one-line config change.The processor has two topologies, and we are running the wrong one
ArtPipeProcessoris one codebase with two deployment modes (ArtPipeStageConsumer.ResolveBindings):ResidentModel=<X>art.model.<x>.tasksartpipe-model-*image → needs no host bindResidentModelart.asset.stage.<s>.requestspython -m artpipe.workeritself from/opt/art_pipe→ needs the host bindThe C# doc on
PublishToModelQueuessays it outright:The resident-container architecture is fully built and fully deployed. All five model workers are running in resident mode, and four of the five carry no bind at all — they bake the art_pipe checkout and venvs into their own images:
artpipe-model-safetyis running right now, 1/1, consuming its model queue, with no host bind whatsoever. That is the proof that the bind is unnecessary in this topology.So why is anything binding fusionio?
Because
artpipe-modelinghas noResidentModel→ it is in legacy per-stage mode → it must execute models in-process as a subprocess → it needs the art_pipe checkout on disk → hence/mnt/fusionio/spikersoft/art_pipe. And it is pinned toartpipe-gpu(the 4090), where fusionio does not exist. Hence the permanent reject loop.And why does the request go to the legacy path at all?
This is the actual root cause.
SubmitArtAssetCommandHandlerpicks the routing key fromArtStudio:StageModelMap:And in both
SpikerSoft.Api/appsettings.jsonandappsettings.Production.json:It is empty. So no stage is ever mapped to a model, so the API always falls back to the per-stage routing key, so every request lands on
art.asset.stage.concept.requests, whose only possible consumer is the legacy in-process processor — which cannot boot.Meanwhile the five resident containers sit on
art.model.*.tasksqueues that nothing ever publishes to.The chain, end to end
ArtStudio:StageModelMapis{}→ concept stage is unmapped.art.asset.stage.concept.requests.artpipe-modeling./opt/art_pipe→ needs the fusionio bind.replicas=0and listening on a queue nobody publishes to.Correct fix
1. Populate
ArtStudio:StageModelMapin the API (appsettings.Production.json) so stages route to resident model queues:(map the remaining stages to Blender / Hunyuan3DPaint / SafetyCheck per the intended pipeline — the ModelDir values above are the deployed residents.)
2. Scale up the residents that stages now route to — at minimum
artpipe-model-sdxl(currentlyreplicas=0) for concept art.3. Then
artpipe-modelingis vestigial on this path. Either delete it, or if it is still wanted as a fallback: drop the/mnt/fusionio/spikersoft/art_pipebind AND thenode.labels.artpipe-gpuconstraint. A pure router needs no checkout, no GPU, and no node pin — so it can run anywhere and the placement/bind contradiction disappears permanently.4.
artpipe-model-hunyuanshould lose its bind too — it is the only resident still carrying one, and its four siblings prove it is not needed. (There appears to be noartpipe-model-hunyuan3dpaintenv image built, which is likely why it falls back to the generic processor image + host bind. Worth building the image, cf. #537 for the sdxllightning size problem.)Note on my earlier advice
I previously flagged this as "the same bug class as #553" and suggested MinIO self-provisioning to replace the bind. That framing was superficially right (a GPU-pinned service carrying a SERVER-only fusionio bind) but the remedy was wrong — it would have preserved a legacy execution path that should simply not be in use. The credit for the right question goes to the repo owner, who asked why a host bind would be needed at all when the model generators are already isolated containers. It should not be, and it is not — we are just not routing to them.
[Bug][Prod][ArtPipe] Concept-art generation is silently dead — consumer can NEVER start (artpipe-gpu pin vs SERVER-only /mnt/fusionio bind, same class as #553) and SDXL generator is at replicas=0; live user request stuck unread in queueto [Bug][Prod][ArtPipe] Concept-art is dead because ArtStudio:StageModelMap is EMPTY — API routes to the legacy in-process stage queue whose consumer can't boot, while the 5 resident model containers sit idle on queues nobody publishes toQA Team — correction to my note above re: #537 / the 98 GB image.
The repo owner clarified: the 98 GB figure in #537 was a bug, not the real image size — an unfiltered
snapshot_downloadwas pulling the entire HuggingFace repo and all weights by mistake. That has been fixed.Verified live against the registry — the model env images are all sane now:
So do not treat #537 as a blocker on scaling
artpipe-model-sdxlup off zero. My earlier caution on that point was based on the stale 98 GB number and is withdrawn.Capacity is not a concern either. All five residents pin to
node.labels.artpipe-gpu(the 4090), which has ample headroom:~68 GB of model images against 1.6 TB free. Fine.
⚠️ One real caveat on scaling up: pulling a 17.4 GB image currently goes out to the WAN and hairpins back through the router, because
git.spikersoft.comresolves to a public IP from inside the cluster (#562). Landing the split-horizon DNS record from #562 first will make that pull go over the LAN instead. Not a blocker, but a 17.4 GB pull is exactly the case where that hairpin hurts most.QA Team — live swarm evidence. We think we have the mechanical root cause of the dead concept-art lane, and it is upstream of the
StageModelMapconfig gap.artpipe-modelingcannot start at all — it has been rejecting a task every ~5 seconds for 27 hoursVerified on the host:
/mnton node4090is empty — thefusioniofilesystem is not mounted there at all (ls /mnt/fusionio→ No such file or directory). Theart_pipetree exists on SERVER (5.3 G), which is where MinIO and every other/mnt/fusioniobind lives.The service can only ever land on 4090: its placement constraint is
node.labels.artpipe-gpu == true, and 4090 is the only node carrying that label.The churn is unbounded and still running
RestartPolicy: {Condition: on-failure, Delay: 5s, MaxAttempts: 0}—0= infinite. We sampleddocker service pstwice 12 s apart and got brand-new task IDs each time.UpdateStatushas beenpausedsince2026-07-13T18:57:51Z— 27h 10m of continuous rejection, which at the observed 5 s cadence is on the order of ~19,000 rejected tasks.service psonly ever shows 5 of them because swarm's task-history-retention-limit is 5, which is why this has been easy to miss.A second service is armed with the same bomb
spikersoft-artpipe-model-safetyis currently 1/1 — but only by luck. Its container was created 27 h ago, before the fusionio mount disappeared, and it carries the same bind path in its spec (it was itselfRejectedon that exact path 44 h ago). The moment it restarts for any reason it will crash-loop identically.How this relates to this ticket
#595 reports concept-art dead with
ArtStudio:StageModelMapempty and "the legacy in-process stage queue whose consumer can't boot". We would suggest the consumer can't boot because the container it lives in has never started — the bind rejection precedes any config being read. FixingStageModelMapalone will not revive the lane while the mount is missing.Suggested fix direction (two independent problems, both needed)
fusionioon 4090, or move theartpipe-gpulabel to a node that has it, or — best, and aligned with epic #413 — migrate theart_pipetree to MinIO so the bind disappears entirely. Note the 4090 is a part-time burst node (docs/4090-burst-node-runbook.md), so a host-path bind on it is structurally fragile: any service pinned there by label will break whenever the node's local storage differs.StageModelMapconfig gap this ticket already describes.Related: #562 (node image/mount flakiness), #413 (MinIO migration would remove this bind class entirely).
Data point (leaving open, can't fully verify from repo): on
origin/masterthe committedSpikerSoft.Api/appsettings.jsonstill has"ArtStudio": { … "StageModelMap": {}, "StageMethodOptions": {} }— i.e. the default is still empty, so unless prod injects a populated map via env/stack overlay, the routing-to-legacy-queue condition this ticket describes persists.Note there IS now integration coverage for the intended dispatch (spikersoft-issues#465, PR #192: "art pipe messaging coverage — StageModelMap dispatch, consumer bindings, restart routing"), but that tests the mechanism, not the deployed config. To close this, confirm the live
ArtStudio:StageModelMap(env/OpenBao/stack overlay inspikersoft-infrastructure) is populated with the 5 resident model stage→queue mappings, not just the empty appsettings default. That's a runtime/config check, not a code read.Audited against
origin/master— the stated root cause is fixed and the routing is now coherent end to end. Leaving open for one live check; see the last paragraph.The map is no longer empty in production.
SpikerSoft.Api/appsettings.Production.json:118now reads:(The base
appsettings.json:422stays{}, which is correct — an empty map means per-stage routing, byte-identical to pre-R3 behaviour, per the class docs atArtAssetStageModelMap.cs:18-19.)A startup guard was added:
ServiceCollectionExtensions.cs:1528-1531binds the map and calls.Validate()before registering it, so a malformed map now fails at boot rather than silently publishing to unbound routing keys.And — the part that actually resolves the "5 resident containers idle on queues nobody publishes to" symptom — the two halves now agree. Checking the infra side:
spikersoft-artpipe-modeling/docker-stack-gpu.yml:84—ArtPipe__Stages=["concept","modeling","texturing","rigging","animation","export","enrichment"]. So the monolith does bind the concept queue, which is what was missing.:100—ArtPipe__ModelQueueStages=["modeling","texturing"], exactly matching the API'sStageModelMapkeys.So concept flows via per-stage routing (
art.asset.stage.requested.concept) to the monolith, while modeling and texturing route toart.model.triposr.tasks/art.model.hunyuan3dpaint.taskson the resident stacks. Every other artpipe stack hasArtPipe__Stages=empty with a mutually-exclusiveModelQueue, which is the intended shape. No stage is published to an unbound key.Why I'm not closing it. This is a
[Bug][Prod]ticket, and everything above is configuration — it proves the intent is now correct, not that concept art works. Two things gate that:prodstagesimage, and #519's live cutover is still unverified.I've been careful not to close deploy-state tickets on code alone this week — that's precisely the mistake #602 and #582 exist to record.
To close: submit one concept-stage generation in prod and confirm it completes. If it does, this and a chunk of #519 close together.