Skip to content

[DataFlow runtime] Domino disagg: 1-server + DP=7 launcher + working --num-epochs#661

Merged
jiapingW merged 1 commit into
sgl-project:domino-disaggfrom
maocheng23:domino-disagg-1srv-dp7-config
Jul 9, 2026
Merged

[DataFlow runtime] Domino disagg: 1-server + DP=7 launcher + working --num-epochs#661
jiapingW merged 1 commit into
sgl-project:domino-disaggfrom
maocheng23:domino-disagg-1srv-dp7-config

Conversation

@maocheng23

@maocheng23 maocheng23 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a 1-inference-server + DP=7-trainer launcher for Qwen3-8B domino disagg, makes --num-epochs actually work in the online/disagg path, and adds config-only perf tuning that ~doubles single-node throughput (28.8 → 50.1 samples/s) plus the profiling instrumentation and analysis behind it. Stacks on domino-disagg (#660).

  • examples/disagg/run_qwen3_8b_domino_disagg_1srv_dp7.sh — single capture server (1 GPU) + DP=7 domino trainer (7 GPUs).
  • examples/disagg/run_disagg_domino.py — the producer now replicates the prompt pool by --num-epochs before streaming (the online channel is consume-once).
  • New tuning knobs (all off by default) + env-gated profiling across the data path.
  • examples/disagg/PERF_FINDINGS.md — full write-up.

Perf: 28.8 → 50.1 samples/s, config only (measured on 1×8 H200, ≥390 s integrated)

⚠️ Supersedes the earlier "trainer-bound / DP7 = 26.9" framing in this PR's history. Those single-~20 s-window numbers were noisy; everything below is total-samples ÷ total-time over ≥390 s, which is the only reliable method here.

Best config (see PERF_FINDINGS.md for the full recipe):

DISAGG_SERVER_URLS="$U,$U,$U,$U,$U,$U,$U,$U"   # 8 concurrent producer workers, one server
CLONE_ON_FETCH=0  LOADER_PREFETCH=2  SERVER_MEM_FRACTION=0.5  ACCUM=8  BATCH_SIZE=2

Result: 50.1 samples/s, all 8 GPUs healthy (server util 98% / SM 76%; trainers util 77–100% / SM 76–85%; trainer mem ~45 GB).

The pipeline is a supply/demand seesaw on 8 GPUs, and 1:7 is the best split:

Split / config samples/s regime
1 srv + 7 trn (this PR, tuned) 50.1 supply-bound (best)
2 srv + 6 trn (b2) 39.0 demand-bound — only 6 trainers
2 srv + 6 trn (b8) 39.5 demand-bound — bigger batch changes nothing

Bigger batch does not help (compute-latency-bound, MFU ~14%): b2/a8 = 50.1, b4/a4 = 47.5, b8/a2 = 47.2. Batch 8 drives trainer memory to 113 GB / 100% util but throughput falls — full util/memory ≠ fast.

Why per-sample trainer demand is "low" (num_anchors): the DFlash draft forward expands every sample to num_anchors × block_size = 256 × 16 = 4096 draft positions (+ full 151,936-vocab head). Per-microstep compute fits ≈ 53 ms + 0.73 ms × anchors, so ~78% of compute at 256 anchors is the anchor expansion — one "sample" is ~45 TFLOP (fwd+bwd), ≈9× a normal 1.1 B forward. This is by design (denser training signal), not inefficiency, and it runs at a healthy ~44 % MFU (measured, bench_domino_mfu.py) — the trainer is compute-bound, not stalled. num_anchors is not a throughput lever: reducing it raises sequences/s but lowers training-signal/s (anchor-updates/s), so it's a quality/data-efficiency setting only (the launcher keeps it fixed at 256).

MFU / FLOPs (both sides ≈ 43–44 %, compute-bound)

  • Trainer ≈ 44 % MFU — measured isolated (single GPU, no data-path/comm, FLOP-counted): ~430 TFLOP/s, ~45 TFLOP/sample, per-sample time flat across batch (compute-throughput-bound). Supersedes an earlier "~14 %" note that came from a cuda.synchronize-distorted timing.
  • Inference (target prefill) ≈ 43 % MFU — estimated from the server's ~27 k tok/s prefill at ~550-tok prompts (2·params·tokens, 8 B target).

Remaining ceilings (code, not config)

  1. Supply ≈ 50/s is PREFILL-COMPUTE-BOUND, not sink-bound. At ≥8 producer workers the server GPU prefills ~100 % of the time at ~27 k tok/s (~43 % MFU); the mooncake sink thread keeps up (~55/s) and is only the next wall. So an async/parallel sink is a second-order lever, not the primary fix. Real supply levers: higher prefill MFU (CUDA graphs / bigger prefill batches / lighter aux-hidden capture copy, ~+30 %); fewer aux layers (a training change); or a 2nd inference GPU (the seesaw — worse on 8 GPUs).
  2. Trainer demand ≈ 50/s at ~44 % MFU. Since the system is supply-bound, raising trainer MFU (CUDA-graphing the anchor-block kernels, fusion, reduced-vocab head) does not raise system throughput today — only matters once supply is lifted or to cut trainer GPU cost.

Notes for reviewers

  • The perf commit is split from the instrumentation commit; both are default-preserving (every knob is off unless its env var is set).
  • DISAGG_TOTAL_STEPS must be ceil(NUM_EPOCHS * num_filtered_prompts / (TRAIN_DP * BATCH_SIZE * ACCUM)) — too small decays the LR to ~0 early (documented inline).
  • With --num-epochs > 1 the producer re-captures the pool each epoch.
  • A GPU-cat optimization for the capture sink lives in the pod's installed SGLang (patches/sglang/.../spec-capture.patch territory); note the sink is not the current supply ceiling (prefill compute is), so sink work is secondary.
  • No credentials committed; W&B/HF auth via env at launch.

🤖 Generated with Claude Code

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

…--num-epochs

- examples/disagg/run_qwen3_8b_domino_disagg_1srv_dp7.sh: single inference
  server (1 GPU) + DP=7 trainer (7 GPUs) variant of the multiserver launcher.
  The domino disagg workload is trainer-bound and the capture server is
  heavily overprovisioned in the 2-server layout (~6% util); this rebalances
  to 1 server + 7 trainers, which raises both throughput and server util.

- examples/disagg/run_disagg_domino.py: the producer now replicates the prompt
  pool by --num-epochs before streaming. The online streaming channel is
  consume-once, so previously the pool streamed once regardless of
  --num-epochs; multi-epoch training now works as expected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

perf(domino-disagg): env-tunable data-plane knobs + findings (28.8->50.1 samples/s)

Config-only tuning that ~doubles single-node online domino-disagg throughput,
with util/SM/mem healthy on all 8 GPUs. All knobs are off by default.

Launchers (1srv+dp7 and multiserver):
- DISAGG_SERVER_URLS now overridable; repeating one URL N times spawns N
  concurrent producer workers with disjoint leases against one server, which
  breaks the single-producer supply ceiling (the blocking HTTP prefill releases
  the GIL so workers genuinely overlap).
- SERVER_MEM_FRACTION knob (default 0.85): 0.5 right-sizes the capture-only
  server from ~126 GB to ~78 GB with no perf cost.
- NUM_ANCHORS / BLOCK_SIZE knobs on the 1srv launcher (quality<->throughput).
- multiserver launcher now honors BATCH_SIZE / ACCUM (were hardcoded to 2 / none).

FeatureDataLoader:
- LOADER_PREFETCH=N: background-thread batch prefetch so the training step never
  pays mooncake get/collate latency inline (ack still after the trainer consumes).
- CLONE_ON_FETCH=0: skip the redundant defensive clone on the mooncake zero-copy
  path (get() already allocates a fresh tensor).

See examples/disagg/PERF_FINDINGS.md for the full analysis: the pipeline is a
supply/demand seesaw (1:7 beats 2:6), bigger batch does not help (compute-latency
bound, MFU ~14%), and ~78% of per-sample trainer compute is the num_anchors=256
draft expansion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

perf(domino-disagg): env-gated profiling instrumentation for the data path

Default-off timers that produced the PERF_FINDINGS analysis. Each is gated on an
env var and adds nothing when unset.

- PROFILE_PRODUCER=N  -> [prod] (backpressure-park / run_once / publish + in_flight)
                        in launch.py, and [prod-http] (build / HTTP RTT / parse)
                        in the server-capture adapter.
- PROFILE_LOADER=N    -> [loader rK] queue-wait / get / clone / release / gc.
- PROFILE_STORE=N     -> [store rK] get/put GB/s, rm_ok/rm_fail(-706), pending depth.
- PROFILE_DISTRIB=sec -> [dist] commit / count / inbox-publish + per-rank lag.
- PROFILE_STEPS=N     -> [profile] data-wait vs compute, [profile2] fwd/bwd/opt + padded len.
- PROFILE_TORCH=N     -> rank-0 torch.profiler top-ops + chrome trace.
- FSDP_SHARDING       -> override the FSDP sharding strategy (e.g. NO_SHARD).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

revert(domino-disagg): drop NUM_ANCHORS/BLOCK_SIZE knob; num_anchors is not a throughput lever

Reducing num_anchors raises sequences/s but LOWERS training-signal/s
(anchor-updates/s): each anchor is a supervised target, and a fixed ~53ms/
microstep per-sequence overhead means fewer anchors amortize that cost over less
signal (256 -> ~2130 vs 64 -> ~1280 anchor-updates/s). It also forces ~4x more
captured sequences for equal signal, loading the supply bottleneck. So it is a
quality/data-efficiency setting, not a speed knob — revert the launcher override
back to the fixed --num-anchors 256 / --block-size 16 and correct PERF_FINDINGS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

docs(domino-disagg): measured MFU/FLOPs + correct the inference-ceiling analysis

Adds bench_domino_mfu.py (isolated single-GPU, FLOP-counted, CUDA-event-timed
trainer benchmark) and folds its results into PERF_FINDINGS.md:

- Trainer MFU ≈ 44% (measured, b2/b4/b8; ~45 TFLOP/sample fwd+bwd; per-sample
  time flat across batch → compute-bound, not stalled). Supersedes the earlier
  "MFU ~14% / occupied-but-stalled" note, which came from a cuda.synchronize-
  distorted timing and was wrong.
- Inference (target prefill) ≈ 43% MFU, estimated from ~27k tok/s at ~550-tok
  prompts (2·params·tokens for the 8B target).
- Corrects the "supply ceiling = capture sink" framing: at >=8 producer workers
  the server is PREFILL-COMPUTE-BOUND (GPU prefills ~100% duty at ~27k tok/s);
  the sink thread keeps up (~55/s) and is only the *next* wall. So an async/
  parallel sink is a second-order lever, not the primary fix — the real supply
  levers are higher prefill MFU (CUDA graphs / bigger prefill batches / lighter
  aux-hidden capture copy), fewer aux layers (training change), or more inference
  GPUs (the seesaw). Trainer-MFU work does not raise system throughput while
  supply-bound.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

fix lint

fix lint
@jiapingW jiapingW force-pushed the domino-disagg-1srv-dp7-config branch from db0fcb4 to 0b7cd90 Compare July 9, 2026 06:02
@jiapingW jiapingW marked this pull request as ready for review July 9, 2026 06:03
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@jiapingW jiapingW merged commit d9232e7 into sgl-project:domino-disagg Jul 9, 2026
1 check passed
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.

2 participants