--- name: dgx-spark-comfyui description: Install, migrate, maintain ComfyUI on DGX Spark (GB10). version: 1.0.0 author: Hermes Agent license: MIT platforms: [linux] metadata: hermes: tags: [dgx-spark, comfyui, gb10, aarch64, unified-memory, sm_121a, infrastructure] related_skills: [local-ai-media-generation, proxmox-lxc-deployment] --- # DGX Spark ComfyUI — Install, Migrate, Maintain ## Overview The DGX Spark (GB10 Grace Blackwell, sm_121a, 128GB unified LPDDR5x, 3.7TB NVMe, aarch64) is a capable ComfyUI host but its unusual architecture requires specific configuration. The community has converged on **spark-comfyui** (bjarkebolding) as the standard — one script, fully containerized, self-healing, actively maintained. **Current best option (July 2026):** [spark-comfyui](https://github.com/bjarkebolding/spark-comfyui) — 52 commits, last updated July 27, 2026. NVIDIA forum thread: 19 likes, 848 views. **Our DGX Spark:** 10.0.0.6, hostname edgexpert-7d04, SSH `adm1n@10.0.0.6` (passw0rd), sudo passw0rd. ## Fresh Install (spark-comfyui) ```bash # 1. Clone git clone https://github.com/bjarkebolding/spark-comfyui.git cd spark-comfyui # 2. Build image (10-30 min) ./spark-comfyui.sh install # 3. System tuning (swap off, persistence mode, optional clock cap) ./spark-comfyui.sh tune --persist # 4. If overcurrent crashes occur, cap GPU clock: ./spark-comfyui.sh tune --clock-cap 2100 --persist # 5. Run as service (auto-start on boot, survives crashes) ./spark-comfyui.sh service # 6. Verify ./spark-comfyui.sh doctor ``` Models go in `data/models/checkpoints/` (etc.). No venv, no system Python changes. ## Migration from aeon-spark ### Step-by-step 1. Stop and remove old container + images: ```bash docker stop comfyui-spark && docker rm comfyui-spark docker rmi ghcr.io/aeon-7/comfyui-aeon-spark:latest docker image prune -f ``` 2. Clone spark-comfyui and install (see Fresh Install above). 3. Configure `spark-mounts.conf` to reuse existing workspace: ``` models = /home/adm1n/comfy/workspace/models custom_nodes = /home/adm1n/comfy/workspace/custom_nodes user = /home/adm1n/comfy/workspace/user output = /home/adm1n/comfy/workspace/output input = /home/adm1n/comfy/workspace/input ``` 4. Fix permissions — aeon-spark wrote files as root, spark-comfyui runs as UID 1000: ```bash echo passw0rd | sudo -S chown -R 1000:1000 /home/adm1n/comfy/workspace/user/ echo passw0rd | sudo -S chown -R 1000:1000 /home/adm1n/comfy/workspace/custom_nodes/ echo passw0rd | sudo -S chown -R 1000:1000 /home/adm1n/comfy/workspace/models/ echo passw0rd | sudo -S chown -R 1000:1000 /home/adm1n/comfy/workspace/output/ echo passw0rd | sudo -S chown -R 1000:1000 /home/adm1n/comfy/workspace/input/ ``` 5. Remove obsolete custom node (no `__init__.py`, superseded by unified memory mod): ```bash rm -rf /home/adm1n/comfy/workspace/custom_nodes/ComfyUI_LTX-2_VRAM_Memory_Management ``` 6. Tune and start service. **Downtime:** ~30 minutes (image build + model copy time). Models, custom nodes, workflows, and user data are all reused — only the Docker image is rebuilt. ## Dockerfile Patch for libGL ## Dockerfile Patch for libGL The spark-comfyui Dockerfile installs `libx11-6 libxext6 libxcb1` but NOT `libgl1`. This causes import failures for LTXVideo, VideoHelperSuite, RES4LYF, and Easy-Use (`ImportError: libGL.so.1: cannot open shared object file`). **Fix:** Add `libgl1 libglib2.0-0` to both apt-get install lines in `container/Dockerfile`. Do NOT use `libgl1-mesa-glx` — that package was removed in Ubuntu 24.04 and has no installation candidate. ```bash sed -i 's/libx11-6 libxext6 libxcb1/libx11-6 libxext6 libxcb1 libgl1 libglib2.0-0/' container/Dockerfile ``` Then rebuild: `./spark-comfyui.sh update` ## Common Import Failures and Fixes | Failure | Cause | Fix | |---------|-------|-----| | `libGL.so.1: cannot open shared object file` | Missing mesa libs in container | Patch Dockerfile to add `libgl1 libglib2.0-0`, rebuild | | `No module named 'soundfile'` | Missing pip package, no requirements.txt in custom node | Create `requirements.txt` with `soundfile` in the node dir | | `cannot import name 'interleaved_freqs_cis'` | LTXVideo custom node stale vs ComfyUI core | `git pull origin master` in ComfyUI-LTXVideo dir | | `ComfyUI_LTX-2_VRAM_Memory_Management` no `__init__.py` | Obsolete node, superseded by unified memory mod | `rm -rf` the directory | | `comfyui.db.lock` Permission denied | Root-owned files from aeon-spark | `chown -R 1000:1000` on workspace dirs | | Impact Pack / controlnet_aux import failed | Missing dependencies | Acceptable — non-critical nodes | ## Manager Fixes ### Manager invisible in UI ("Blocked by policy") The pip package `comfyui-manager` conflicts with the git clone at `custom_nodes/ComfyUI-Manager`. The pip package's `should_be_disabled()` returns True for any dir containing "comfyui-manager", blocking the git clone's `WEB_DIRECTORY = "js"`. **Fix:** `pip uninstall comfyui-manager -y`. The git clone is then unblocked and Manager appears in the UI bar. ### Manager: "Invalid channel" / empty node list The git clone may point to the OLD `ltdrdata/ComfyUI-Manager` repo. The repo moved to `Comfy-Org/ComfyUI-Manager`. Three fixes needed: 1. **Git remote:** `git remote set-url origin https://github.com/Comfy-Org/ComfyUI-Manager.git` 2. **channels.list / channels.list.template:** Replace `ltdrdata/ComfyUI-Manager` with `Comfy-Org/ComfyUI-Manager` 3. **Hardcoded DEFAULT_CHANNEL in Python:** Replace in `glob/manager_core.py` and `glob/manager_server.py` ### --enable-manager flag is redundant ComfyUI core already loads the built-in Manager. The `--enable-manager` flag in the entrypoint is unnecessary. Remove it from `container/entrypoint.sh` DEFAULT_FLAGS. ## Dockerfile Changes (Baked into Image) The spark-comfyui Dockerfile at `container/Dockerfile` has these local modifications: 1. `libgl1-mesa-glx` → `libgl1` (Ubuntu 24.04 compat — mesa-glx removed in 24.04) 2. `pip install soundfile` after `manager_requirements.txt` 3. `--enable-manager` removed from entrypoint.sh DEFAULT_FLAGS (redundant) ## Entrypoint Changes The spark-comfyui entrypoint at `container/entrypoint.sh` has this local modification: 1. `--bf16-vae` removed from SPARK_BF16 default flags (2026-07-29) — the audio VAE conv layers have float32 bias that crashes with `Input type (float) and bias type (c10::BFloat16) should be the same`. Only `--bf16-unet --bf16-text-enc` remain. These survive `./spark-comfyui.sh update` because they're in the repo's tracked files. If the upstream files change significantly, re-apply after `git pull`. ## Key Commands | Command | Purpose | |---------|---------| | `./spark-comfyui.sh install` | Build image | | `./spark-comfyui.sh run` | Foreground | | `./spark-comfyui.sh service` | Background, auto-restart, survives reboots | | `./spark-comfyui.sh stop` | Stop container | | `./spark-comfyui.sh update` | Rebuild image on current ComfyUI master | | `./spark-comfyui.sh update --rollback` | Swap back to previous image | | `./spark-comfyui.sh doctor` | Live GPU gates in throwaway container | | `./spark-comfyui.sh status --watch` | Live sparkline dashboard | | `./spark-comfyui.sh tune --persist` | System tuning (swap off, persistence mode) | | `./spark-comfyui.sh tune --clock-cap 2100 --persist` | Cap GPU clock to prevent overcurrent crashes | | `./spark-comfyui.sh backup` | Backup workflows, settings, custom nodes | | `./spark-comfyui.sh restore FILE` | Rebuild from backup | ## Workflow-Driven Model Download When the user says "download all required files for workflow X," do NOT guess or search manually. Extract dependencies from the workflow JSON, inventory what's already on the DGX, then download only what's missing — in parallel. ### Step-by-step 1. **Read the workflow JSON** — `cat /home/adm1n/comfy/workspace/user/default/workflows/{name}.json` 2. **Extract all model references** — parse `widgets_values` from Loader nodes (VAELoader, DualCLIPLoader, UNETLoader, LoraLoader, LoadImage, etc.). Also check subgraph nodes (UUID-type nodes) — they embed model filenames in their widget values. 3. **Extract input images** — `LoadImage` nodes reference files in `input/`. 4. **Check what's already on the DGX** — `ls -la` each model directory for the expected filenames. 5. **Find download URLs** — workflow MarkdownNote nodes often contain HuggingFace links. If not, search `huggingface.co/{filename}` to find the repo. Use HF API (`https://huggingface.co/api/models/{org}/{repo}`) to get file sizes. 6. **Download in parallel** — use `terminal(background=true, notify_on_complete=true)` for each file. Large models (20GB+) go to `diffusion_models/`, LoRAs to `loras/`, VAEs to `vae/`, text encoders to `text_encoders/`. 7. **Copy input images** — if the workflow references images from .202, use two-hop pipe: `ssh .202 "cat /path/file" | ssh DGX "cat > /home/adm1n/comfy/workspace/input/file"`. Do NOT try direct scp from the Hermes host — the files live on .202, not locally. ### Example: qwenI2I.json + qwenT2I.json (2026-07-30) Workflow JSONs at `user/default/workflows/qwenI2I.json` and `qwenT2I.json`. **qwenI2I.json needs:** | File | Type | Dir | Status | |------|------|-----|--------| | `qwen_image_edit_2511_fp8mixed.safetensors` | diffusion model | `diffusion_models/` | Download from Comfy-Org/Qwen-Image-Edit_ComfyUI | | `qwen_2.5_vl_7b_fp8_scaled.safetensors` | text encoder | `text_encoders/` | Already present | | `qwen_image_vae.safetensors` | VAE | `vae/` | Already present | | `Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors` | LoRA | `loras/` | Already present | | `August-2017-4643-2.jpg` | input image | `input/` | Copy from .202 | | `Qwen-Image-2512_00007_.png` | input image | `input/` | Copy from .202 | | `RobLTX.png` | input image | `input/` | Copy from .202 | **qwenT2I.json needs:** | File | Type | Dir | Status | |------|------|-----|--------| | `qwen_image_2512_fp8_e4m3fn.safetensors` | diffusion model | `diffusion_models/` | Download from Comfy-Org/Qwen-Image_ComfyUI | | `qwen_2.5_vl_7b_fp8_scaled.safetensors` | text encoder | `text_encoders/` | Already present | | `qwen_image_vae.safetensors` | VAE | `vae/` | Already present | | `Qwen-Image-2512-Lightning-4steps-V1.0-fp32.safetensors` | LoRA | `loras/` | Download from lightx2v/Qwen-Image-2512-Lightning | ### Two-Hop File Transfer (input images from .202) The Hermes host cannot directly scp from .202 to DGX. Pipe through the Hermes host: ```bash sshpass -p 'n8n' ssh -o StrictHostKeyChecking=no n8n@10.0.0.202 \ "cat /home/n8n/comfy-ui/input/FILENAME" | \ sshpass -p 'passw0rd' ssh -o StrictHostKeyChecking=no adm1n@10.0.0.6 \ "cat > /home/adm1n/comfy/workspace/input/FILENAME" ``` Run each image transfer in its own background process for parallelism. ### Subgraph Nodes (UUID-type) Workflows from Comfy-Org templates use subgraph nodes with UUID type identifiers (e.g. `cdb2cf24-c432-439b-b5c8-5f69838580c9`). These embed model filenames directly in their `widgets_values` array — parse them the same way as regular loader nodes. The subgraph's internal nodes are stored in the workflow's `extra` field. ### Max Precision Policy **Always check HuggingFace for higher-precision versions of every model referenced in a workflow.** Download the max-precision variant to disk alongside the workflow's fp8 reference. Do NOT modify the workflow JSON — keep it pointing at the fp8 filename. The user can switch to max precision later by editing the workflow themselves. For each model in a workflow, check the same HF repo for bf16/fp16/fp32 variants: - `qwen_image_edit_2511_fp8mixed` → also download `qwen_image_edit_2511_bf16` (39GB) - `qwen_image_2512_fp8_e4m3fn` → also download `qwen_image_2512_bf16` (41GB) - `qwen_2.5_vl_7b_fp8_scaled` → also download `qwen_2.5_vl_7b.safetensors` (16.6GB) - Lightning LoRAs are already fp32 — no higher precision exists - VAE has only one version (254MB) — no higher precision exists Use the HF API to discover available files: `https://huggingface.co/api/models/{org}/{repo}` → check `siblings` for bf16/fp16/fp32 variants. Download in parallel with the fp8 versions. ## Cross-Environment Model Parity When someone says "make the DGX match the .202 setup," verify before acting. The DGX (Docker, aarch64, unified memory) and .202 (bare-metal LXC, x86_64, discrete VRAM) have different environments. A .202 workflow JSON won't load on DGX without path adjustments — node IDs, model paths, and custom node versions differ. **Checklist before copying models:** 1. Inventory BOTH sides first (`find` by model type) 2. Most "upgrades" are already present — the DGX often already has full-precision variants 3. Only copy what's genuinely missing, not what someone assumes is missing 4. Verify LoRA paths match what the workflow JSON expects (subdirectories like `loras/ltxv/ltx2/` vs `loras/`) 5. Check cond-safe LoRA presence — Director workflows may depend on it **Full mirroring procedure (when user explicitly wants .202 parity at full precision):** 1. Inventory both sides: `find models/ -iname '*ltx*'` and `ls custom_nodes/` on both hosts 2. Copy missing LoRAs via scp (two-hop: .202 → local → DGX) 3. Fix LoRA path mismatches with symlinks (e.g. `ln -s loras/ltxv/ltx2/file.safetensors loras/file.safetensors`) — avoids duplicating 7GB+ files 4. Git clone missing custom nodes into workspace `custom_nodes/`; for nodes without public repos (like SeqParallel V5), scp the .py files directly 5. Copy the working Director workflow JSON from .202 `user/default/workflows/` to DGX 6. Rebuild Docker: `./spark-comfyui.sh stop && ./spark-comfyui.sh update && ./spark-comfyui.sh service` 7. Verify: `docker logs spark-comfyui | grep "IMPORT FAILED"` must be zero **Known delta (2026-07-29):** IC-LoRA Ingredients, cond-safe LoRA, Cinemagraph, Transition, SeqParallel V5, MelBandRoFormer, and comfyui-ollama were on .202 but not DGX. Full-precision models (dev 43GB, Gemma 3 12B 23GB) were already present. **Model inventory:** See `references/dgx-model-inventory.md` for the full current snapshot. **Workflow port + validation checklist:** See `references/workflow-port-validation.md` for the step-by-step procedure to copy a workflow from .202 to DGX with byte-for-byte verification, input file transfer, and independent validation. **Multi-agent validation pattern:** See `references/multi-agent-validation.md` for dispatching Claude + Grok + self in parallel to validate a workflow before queuing. **Max-quality upgrade pattern:** See `references/rapvidfinal-quality-analysis.md` for the ranked bang-for-buck analysis pattern — dispatch multiple agents to analyze a workflow, rank quality improvements, apply top 2-3 as a low-risk package. **Hours-capable workflow pattern:** See `references/hours-capable-workflow.md` for converting multi-scene workflows from single-concat to per-scene file output, removing the ImageBatchMulti RAM wall for arbitrarily long videos. **Diff-based workflow port pattern:** See `references/diff-based-workflow-port.md` for porting parameter changes between workflows with field-level widget diffs — prevents missing changes and editing wrong nodes. Validated by Claude + Grok on the RapVidFinal → FullHqHours port (2026-07-30). ## Pitfall: Don't Pull Files When Dispatching Validators When the user says "ask X to SSH in and validate workflow Y," do NOT pull the workflow files locally first. The agents (Grok, Claude) should SSH directly into the DGX and read the files themselves. Pulling files locally wastes time and defeats the purpose of independent validation — the agents need to verify the files exist at the expected paths on the actual host. **Wrong:** Pull JSONs to /tmp, scp to agent hosts, paste inline. **Right:** Write a prompt telling the agent to `sshpass -p 'passw0rd' ssh adm1n@10.0.0.6` and `cat` the files directly. ## Multi-Scene OOM on DGX Spark — Two Failure Modes The DGX Spark (128GB unified memory) can OOM during multi-scene LTX video workflows. There are TWO distinct failure modes — knowing which one hit determines the fix. ### Failure Mode 1: Mid-Render OOM (full-precision models, 6+ scenes) **Symptoms:** Container is OOM-killed during scene rendering. Sampling progress bars show completions but NO per-scene output files (`DGXmaxq_Scene*.mp4`) exist — only generic `LTX_00001.*` temp files. Journal shows `python invoked oom-killer` with 110GB+ anon-rss. **Root cause:** Full-precision stack (43GB dev + 23GB Gemma = 66GB base) leaves only ~62GB for decoded frames across all scenes. With 8 scenes at 768×448×121 frames each, memory exhausts before all VHS combine nodes run. The `save_output=True` fix does NOT help here — scenes never reach their VHS combine. **Fix — switch to fp8 distilled:** The distilled-1.1 fp8 checkpoint (23GB) + fp4 Gemma (12GB) = 35GB base, leaving ~93GB for frames. This is enough for 8+ scenes. The quality difference at CFG 1.0 is negligible for most workflows. **Fix — reduce scenes per run:** If full precision is required, split into 4-scene batches. Run batch 1 (scenes 1-4), then batch 2 (scenes 5-8), stitch with ffmpeg. **PITFALL — `save_output=True` does NOT protect against Mode 1:** The agent may incorrectly claim that `save_output=True` on per-scene VHS nodes will save intermediate files even if the container OOMs. This is FALSE for Mode 1. `save_output` only works when the VHS node actually executes and writes — but in Mode 1, the OOM kills the process during sampling, before any VHS combine runs. Do NOT tell the user that `save_output` will prevent data loss in this scenario. Only Mode 2 (final combine OOM) benefits from `save_output`. ### Failure Mode 2: Final Combine OOM (all scenes rendered, combine crashes) **Symptoms:** All per-scene VHS nodes complete and write `DGXmaxq_Scene*.mp4` files to `output/`. The FINAL VIDEO VHS_VideoCombine node then loads all decoded scene frames simultaneously for concatenation and OOMs. **Root cause:** Even with fp8 models, holding 8 decoded scenes in memory for the final concat can exhaust 128GB. **Fix:** Set `save_output=True` on all per-scene VHS nodes with unique prefixes. Each scene writes to `output/` immediately after rendering. If the final combine still crashes, the scene files survive and can be ffmpeg-concatenated manually: ```bash # After crash, stitch surviving scene files: for f in output/DGXmaxq_Scene*.mp4; do echo "file '$f'" >> concat.txt done ffmpeg -f concat -safe 0 -i concat.txt -i audio.mp3 \ -map 0:v -map 1:a -c:v copy -c:a aac -b:a 256k -shortest final.mp4 ``` **Important:** The `-audio.mp4` variant from VHS has both video+audio streams. The non-suffixed `.mp4` is video-only. Always use the `-audio.mp4` files. ### Diagnosing Which Mode Hit 1. Check if per-scene files exist: `ls -lth output/ | grep DGXmaxq_Scene` 2. Check journal for OOM kills: `sudo journalctl --since '