Skip to content

infra/delta: multinode config, DeltaAI catalog, launch-test --nodes (#174) - #178

Open
dadamsncsa wants to merge 17 commits into
mainfrom
multinode-kit
Open

dadamsncsa wants to merge 17 commits into
mainfrom
multinode-kit

Conversation

@dadamsncsa

@dadamsncsa dadamsncsa commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Retargeted to main so CI runs on this head. It still contains #177's commits: review and merge #177 first.

Closes #174. Part of #60. Stacked on #177 (the multinode adapter): review and merge that first.

What (infra/delta, plus tests)

  1. The GAP-098 pair, on both clusters. FI_CXI_RDZV_THRESHOLD=8192 FI_CXI_RDZV_GET_MIN=0 are added to LLMHUB_VEC_INF_ENV (delta.env) until the images bake them.

    • DeltaAI: with the image's own libfabric settings, cross-node all_reduce returned wrong data on 146-181 of 3,000 calls at 896 KiB-1 MiB. With the pair, 0 (job 3533388).
    • Pipeline-parallel serving puts only send/recv across nodes, which was clean either way, so for serving the pair is defence in depth.
    • Delta was clean without it (GDR 0, 23559724); it carries the pair for uniformity.
  2. DeltaAI SLURM_NETWORK=disable_rdzv_get, the site value since 2026-10-22, replaces single_node_vni,disable_rdzv_get.

    • A multi-node step gets one VNI under either value.
    • This reaches every 1-node DeltaAI job too. Without single_node_vni, a 1-node step gets no VNI and creates no CXI service. That is safe because 1-node jobs already run with NCCL_NET_PLUGIN=none and never drive the NIC. The 1-node launch-test after the deploy checks it.
  3. A DeltaAI catalog override. config/models-deltaai.yaml is applied by render-models.py through LLMHUB_MODELS_OVERRIDE:

    • five 2-node A100 entries run at 1 node, TP=4, with mem_per_node: 400G. None of the five has been run on GH200 yet;
    • all four Mllama entries (11B and 90B, Vision and Vision-Instruct) and 405B are left out: neither image registers Mllama, and Llama-3.2-11B-Vision-Instruct had resolved to SGLang through vec-inf's packaged catalog, whose image is not on DeltaAI;
    • Qwen3-235B-A22B-Instruct-2507 is added at 2 nodes, TP=4 PP=2, 400G. So far it has served on 2 nodes only from a test harness, with all node memory (3536666, --mem=0, vLLM-step MaxRSS 41 GiB per node). This entry first runs through LLMHub in the acceptance run.

    vec-inf merges a catalog over a base one, its packaged catalog by default, so removed entries would come back at launch. With an override set, the rendered environment.yaml also names the rendered models.yaml as that base (cached_model_config_path, via render-vec-inf-env.py). vec-inf's launch-path load_config() then resolves exactly DeltaAI's 90 entries. Of the entries whose resolution this changes, all but the removed 11B one differ only in sglang_args, which vLLM does not use.

    Override names are checked against the base: remove and models must name an existing entry, and add a new one. Delta renders backend/config/models.yaml as is, with vec-inf's base catalog unchanged.

  4. launch-test --nodes N. At N > 1 it also reads the job's own files, as the backend's logs endpoint does:

    • one rank per node;
    • the model loaded on every Worker_PP<k> stage;
    • NCCL on CXI in every worker's debug file (the files Multinode vLLM in one srun step, without Ray (#173) #177 writes), with GDRDMA required on DeltaAI;
    • the pair in every rank's container;
    • 0 libfabric lines before vLLM's ready line.

    After DELETE:

    • sacct says CANCELLED (a kit-side scancel counts as a failure);
    • no node is left down, drained or completing.

    Slurm reads after "ready" can't abort it before the DELETE: an impersonated job is the backend's to cancel.

    The ready budget (the job's walltime) counts only while Slurm runs the job, so a long queue can't use up a big model's load time; pending has its own 4 h cap. The shutdown is timestamped: DELETE, sacct Start and End, and the node states.

    --client SCRIPT runs an acceptance client against the live server before that shutdown. It gets the endpoint and deployment key in its environment, never argv, and its exit status counts.

Tests (at 631f143): 98 passed (T1-T5 + test_kit_multinode_config.py); full suite 285 passed, 1 skipped; lint clean

  • T2 now uses each cluster's vec-inf-config, rendered by the kit's own scripts:
    • DeltaAI: 89 one-node entries identical, 1 multinode entry different;
    • Delta: 86 identical, 7 different.
  • test_kit_multinode_config.py checks:
    • the pair on both clusters;
    • DeltaAI's SLURM_NETWORK;
    • every catalog line above;
    • that vec-inf 0.9.0's load_config() loads DeltaAI's 90 entries, on the launch path too, where none of the removed names comes back;
    • that a name typo in the override is an error.

LLMHub CI runs only on PRs into main, so it runs here once #177 merges and this PR is retargeted.

On hardware (DeltaAI, 2 × GH200, Qwen2.5-7B-Instruct, TP=4 PP=2)

Job 3541508 passed 14/14. It was launched from a dev session with this branch's rendered DeltaAI config and env, shaped as launch-test --nodes 2 requests it.

  • Status: vec-inf RUNNING; all 8 workers.
  • Fabric: CXI on 8/8 ranks, GDRDMA.
  • Env: the pair in both containers; 0 libfabric lines before ready.
  • Auth: 401 without the key, 200 with it.
  • Shutdown: after scancel, CANCELLED, with both nodes freed.

The base-catalog rule leaves that job's generated script byte-identical; it changes only which catalog vec-inf can resolve.

launch-test's multinode checks, run on that job's files: all pass. On a copy with the NCCL files removed and a libfabric line planted, 3 fail, as they should.

launch-test --nodes 2 and a 1-node launch-test through the DeltaAI dev stack follow once both branches are deployed there.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD

dadamsncsa and others added 7 commits November 6, 2026 12:21
vec-inf 0.9.0 launches num_nodes > 1 vLLM as a Ray cluster in N+1 srun
steps. On DeltaAI each step gets its own Slingshot VNI, so the ranks
cannot reach each other over CXI.

For multinode vLLM only, LLMHub now builds MultinodeLauncher, a vec-inf
ModelLauncher whose generator replaces the Ray branch. The job runs one
srun step with one task per node and --kill-on-bad-exit=1. A rank script,
written into the job directory, derives the node's hsn<digits> address,
waits on a barrier, and execs the same container vec-inf would. Rank 0
runs vllm serve with the API; the other ranks run it --headless. They
join through vLLM's --nnodes/--node-rank/--master-addr/--master-port
with the mp executor. vLLM runs under `env -u FI_LOG_LEVEL`, so the
image's libfabric warnings cannot make vec-inf report a serving job as
FAILED. Single-node launches, and anything that is not vLLM, go to
vec-inf unchanged. The route is chosen on vec-inf's resolved params, so
the catalog's node count counts.

The adapter refuses a multinode launch on any vec-inf other than 0.9.0.
Contract tests T1-T5 cover the version guard, single-node byte
identity over the catalog, the job's shape, the rank script run with
stubbed tools, and the vec-inf names it depends on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
Drop the heredoc-delimiter guard (no generated line can equal it), inline
the job-directory helper, and leave the multinode check to the generator,
which refuses before sbatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
- Clear the rendezvous directory before srun: a requeued job keeps its id,
  and stale rank files would satisfy the barrier at once.
- Decide the NCCL debug file on the rank, so NCCL_DEBUG reaching the
  container through APPTAINERENV_* is caught too, not only the job env.
- Time the barrier by the wall clock, not by loop iterations.
- Document that a non-container multinode launch is refused, and that the
  rank's --env flags rely on llm_inference's one-flag-per-variable patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
… fixes

Behaviour change: every multinode launch now records NCCL INFO for INIT
and NET, written to one file per process in the job directory's
nccl-debug/ and never to stderr. That is how a multinode job shows which
transport each rank chose (provider, NIC, GDR). It applies unless the job
env or the host sets a level, and a NCCL_DEBUG_FILE given by either is
kept.

From the fresh reader:
- A rank whose srun step has no CUDA_VISIBLE_DEVICES fails with a reason,
  not a bash "unbound variable".
- A job env that sets VLLM_HOST_IP, NCCL_SOCKET_IFNAME or
  GLOO_SOCKET_IFNAME is refused: each rank derives them, and --env would
  silently win.
- The master-port probe's stderr stays out of the job's .err.
- T5 also names ModelLauncher.slurm_script_path, which the override sets
  and launch() reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
…check

- The job env's VLLM_HOST_IP, NCCL_SOCKET_IFNAME, GLOO_SOCKET_IFNAME and
  VLLM_PORT are left out of a multinode job's container env and noted in
  the job log, not refused: each rank sets its own, and a deployment may
  set them for single-node jobs.
- With no NCCL level anywhere, the level is INFO even when a debug file is
  named.
- A rank without CUDA_VISIBLE_DEVICES fails before the barrier, so no other
  rank waits on it; a rank that cannot write its barrier marker says so.
- The master-port probe's stderr is visible again: a failing ss should
  leave a trace.
- T4 runs the rank script for each NCCL case, job env and host alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
- The job JSON records the env the job was given, without the per-rank
  keys left out of it.
- A missing ss is reported on stdout; ss's own stderr stays out of the
  .err that vec-inf scans for fatal patterns.
- A container module other than apptainer is refused: the rank script
  passes its values as APPTAINERENV_* and binds through APPTAINER_BINDPATH.
- T3 and T4 also cover a job env that sets both the NCCL level and file;
  T5 names CONTAINER_MODULE_NAME.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
dadamsncsa added a commit that referenced this pull request Nov 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
@dadamsncsa
dadamsncsa marked this pull request as draft November 6, 2026 20:58
dadamsncsa and others added 8 commits November 6, 2026 15:13
…ted in full

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
…odes (#174)

- LLMHUB_VEC_INF_ENV carries FI_CXI_RDZV_THRESHOLD=8192 and
  FI_CXI_RDZV_GET_MIN=0 on both clusters until the images bake them
  (GAP-098: cross-node all_reduce corruption on DeltaAI, job 3533388).
- DeltaAI's job pre-command exports SLURM_NETWORK=disable_rdzv_get, the
  site value; a multi-node step gets one VNI under either value.
- DeltaAI renders its catalog through config/models-deltaai.yaml
  (render-models.py, LLMHUB_MODELS_OVERRIDE): five 2-node A100 entries at
  1 node TP=4, no Mllama, no 405B, and Qwen3-235B at 2 nodes TP=4 PP=2
  with 400G per node. Delta renders backend/config/models.yaml as is.
- launch-test --nodes N. At N > 1 it checks one rank per node, every
  worker and stage loaded, NCCL on CXI, the pair in every rank's
  container, no libfabric line before ready, and that DELETE leaves the
  job CANCELLED with no node held.
- T2 now compares each cluster's kit-rendered catalog, job env and
  pre-command. New tests check the kit's values and load DeltaAI's catalog
  with vec-inf's load_config().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
…cct retry

The multinode checks read an impersonated job's files directly, as the
backend's GET /deployments/{id}/logs does, through the workspace's default
ACL for the service account, instead of an untried `sudo -u`. The
post-DELETE state check waits up to 60 s for sacct to leave
RUNNING/PENDING.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
…aunch-test checks

From /code-review and the fresh reader on #178:
- vec-inf merges a cluster's models.yaml over a base catalog, its packaged
  one by default, so the entries DeltaAI's override removes (405B, Mllama)
  came back at launch. With an override set, the rendered environment.yaml
  now names the rendered models.yaml as that base (cached_model_config_path).
  The environment render moves from inline Python to render-vec-inf-env.py,
  which the tests now call, so T2 and the kit tests use the kit's own render.
- render-models.py: a `models:` name the base lacks is an error; new
  entries go under `add:`.
- The five 2-node entries moved onto one GH200 node get 400G per node.
  None of them has been run on GH200 yet.
- launch-test --nodes N:
  - --nodes is validated before use;
  - Slurm reads after "ready" cannot abort the script before DELETE (an
    impersonated job is the backend's to cancel);
  - a kit-side scancel fails the DELETE check;
  - "nodes freed" now checks the nodes' states in sinfo;
  - stages are counted by Worker_PP prefix;
  - CXI is counted per worker file;
  - GDRDMA is required on DeltaAI;
  - the pair is matched as whole fields;
  - the libfabric count needs the ready line.
- Tests: the launch-path catalog (load_config() with no path, as vec-inf
  calls it) and the override typo errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
dadamsncsa and others added 2 commits November 6, 2026 15:30
…against the live server

The client runs after launch-test's own inference checks and before its
DELETE. It gets the endpoint, the deployment key, the served name and the
job id in its environment, never argv, and its exit status counts toward
launch-test's. This is how the Qwen3-235B acceptance run goes through
LLMHub's launch and shutdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD
…DELETE timestamps; no Mllama on DeltaAI

- launch-test's ready budget (the job's walltime) is counted only while
  Slurm runs the job, so a long queue cannot use it up before a large
  model has loaded. Pending has its own 4 h cap.
- The shutdown records the DELETE time, sacct's Start and End, and the
  node states with a timestamp: the acceptance check is "free within 60 s".
- DeltaAI leaves out all four Mllama entries. The two 11B entries join the
  90B ones: neither image registers Mllama, and 11B-Vision-Instruct had
  resolved to SGLang through vec-inf's packaged catalog, whose image is
  not on DeltaAI. DeltaAI renders 90 entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnSD7f83LXZjJVqJ3RLAAD

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kit: multinode config for Delta and DeltaAI (fabric env, DeltaAI catalog, 2-node launch-test)

1 participant