--- name: serve-boot-triage description: Fast triage and recovery of sm121 serve launches — a container that died, a node missing its container, or an engine that never reaches readiness. Log preservation (--keep), detached serve, per-node state matrix, ledger hygiene, GB10 UMA memory gate, --speculative-config quoting. Use whenever a serve boot fails or a node has no container, before waiting 10+ minutes. --- # Serve boot triage Goal: within ~5 minutes of a failed boot, know (a) which node and (b) why, with the crash log in hand — instead of a dead `--rm` container and a guess. ## 1. Log preservation (the #1 time sink) Default `serve` runs `docker run --rm`: a crashed container is gone WITH ITS LOGS. For any first / unproven launch: - `sm121 serve ... --keep` — containers are NOT auto-removed on stop or crash; `sm121 logs --recipe R --node N.lan` then works on the corpse. - Cleanup afterwards: `sm121 stop` stops but keeps (`--keep` semantics); remove the leftovers with `docker rm` per node (note them in the report). ## 2. Detached serve (the harness kills long foreground calls) A long foreground `sm121 serve` gets "Command aborted" by the tool harness; the process dies mid-launch → "containers on 2 of 4 nodes", lost logs, unclear ledger. Pattern (short call, returns immediately): ``` setsid nohup ~/.cargo/bin/sm121 serve --recipe R --cluster C --no-wait --keep \ > /tmp/serve-R.log 2>&1 < /dev/null & sleep 2; echo started ``` `--no-wait` skips the 30-min readiness poll; do your own polling (crashes are caught sooner anyway). Poll with SHORT calls: `tail /tmp/serve-R.log` + per-node `docker ps -a --filter name=`. ## 3. Per-node state matrix (first thing, ~10 s) ``` for ip in ; do ssh -i ~/.ssh/id_ed25519_shared -o IdentitiesOnly=yes kilork@$ip \ 'docker ps -a --filter name= --format "{{.Names}} {{.Status}}"' done ``` | State | Meaning | Action | |---|---|---| | Up on all | boot in progress (weights from NAS 25–40 min; first autotune +15–40) | poll API: `curl -s http://:30000/v1/models` | | Up on some, ABSENT (not even in `ps -a`) on others | serve process died first (aborted call) OR image missing | check `docker images` on the absent node; then full clean re-serve (stop → fresh detached) or surgical `sm121 serve --recipe R --cluster C --node --no-wait --keep` (only if the ledger run is closed) | | `Exited (2)` (with `--keep`) | engine crashed | `sm121 logs --recipe R --node ` → last lines are the cause | | Up on all, no API after 45+ min | NCCL hang or slow NFS | tail each node's logs: NCCL hang = all quiet at the same step; check `df`/NFS on the head | ## 4. Hard rules (learned the hard way) - **Node IPs only from `config.toml` (`ssh_ip`), never from memory.** A wrong IP looks exactly like "the node hung" (ssh timeout/refused). Current (2026-10-02): dgx1 88.14, dgx2 **88.10**, dgx3 **88.50**, dgx4 **88.51**, dgx5 88.43, dgx6 88.44, dgx7 88.54, dgx8 88.55; ib 192.168.200.x; DAC (dgx1/2) 192.168.100.x. - Multi-line remote scripts: base64 them (`printf '%s' "$S" | base64 | ssh ... "bash -c 'base64 -d | bash'"`) — nested quotes in local double-quotes + remote zsh single-quotes break. - `sm121 runlock list` — an open run = a serve that never closed; `sm121 runlock close ` after manual cleanup. - Do NOT duplicate launcher built-ins: `drop_caches` (config field, default `true` — already runs before every serve), config-level `before_serve`. - `sm121 -d serve ...` (dry run) renders the exact per-node commands — use it to predict, not to execute (its shell escaping is not paste-able). ## 4b. Long-running local work (benches, builds) — move to a node The tool harness reaps long foreground calls (~10 min) and detached local processes (`nohup`/`setsid` die with the call). Benches run 1.5–2 h. Pattern: 1. Ship the launcher to a cluster node and build it there (the Mac binary is Mach-O; nodes are aarch64 Linux): `rustup` one-liner (minimal profile, ~1 min) + `cargo build --release` (deps ~5 min, incremental ~15 s). 2. Ship the minimal repo subset — `src Cargo.toml Cargo.lock locales host-scripts` (~400 KB). NEVER tar the whole repo: `target/` is tens of GB. 3. Fix `ssh_key_path` in the node's `config.toml` (Mac path → node key, `~/.ssh/id_ed25519_shared`). 4. Install the harness on the node: `uv` installer + `uv tool install "tool-eval-bench[perf] @ git+...@"`; add `~/.local/bin` to PATH inside the tmux command (non-interactive sessions don't source the installer's profile). 5. Run under `tmux new -d -s `; poll with short calls: `tail -c 2000 /tmp/.log | tr \\r \\n | tail`. 6. `--tokenizer` must be a **host-side** path readable by the bench user (the NAS path, not the container-side `/root/.cache/...` — containers are root, the host user is not). 7. Reports land in the node's `recipes//reports/`; copy them back to the Mac repo and commit (same stem). ## 5. Failure patterns (GB10 / kindling era) - **UMA memory gate** (vLLM #48140): `Free memory on device cuda:0 (106.53/121.63 GiB) ... less than desired GPU memory utilization (0.88, 107.03 GiB)` — random node, <1 GiB margin; the check reads MemFree at worker init; weight/NFS page cache tips it. Built-in drop_caches covers the common case; recurrence → recipe `before_serve` drop_caches (`on_failure = "warn"`) or GMU −0.02. - **`--speculative-config` quoting**: the TOML value must be `"""` + newline + `'{JSON}'` + `"""` with the closing `"""` **on the same line** as the JSON (mimo-recipe form). A trailing newline before `"""` → the launcher keeps the apostrophes → vLLM `cannot be converted to `. A doubled `--speculative-config` (recipe args + transcribed env EXTRA) → vLLM takes the last one = garbage. - X/gdm running on a node = extra GBs + a display context: headless = `sudo systemctl set-default multi-user.target && sudo systemctl stop gdm` (dgx1–5 already headless; dgx6/7/8 switched 2026-10-02). - First launch after an image build = autotune candidate runs (394 on the 8x b12x stack) + NAS weight read: budget 60–90 min total; don't declare failure early. - The engine's own preflight (kindling entrypoint) is bypassed in B-mode; their preflight checks (fabric, GID) are the first things to replicate manually if a boot fails without a clear vLLM traceback.