[diffusion] feat: data-parallel serving (--dp-size) - #33725
Conversation
The group plumbing was already DP-aware -- dp is the outermost axis of the "tp-sp-pp-cfg-dp" layout, so every replica is a contiguous rank block whose sp/cfg/tp subgroups never cross replicas -- but validation rejected dp_size > 1 because nothing routed requests. This adds the routing and removes the reject. Each replica's first rank is its driver: it binds its own ingress endpoint (one settled scheduler port per replica), and the existing sp/cfg/tp broadcast relay in recv_reqs -- whose groups are replica-internal by construction -- distributes requests within the replica and never across. Non-driver ranks already no-op on result return, and request-based warmup keys on per-scheduler state, so each replica warms itself. The scheduler clients route instead of assuming one endpoint: generation goes to one replica (round-robin; realtime sessions stick to a replica by session-id hash, since their GPU state lives there), control ops that mutate replica state (weights, LoRA, memory occupation, shutdown) fan out to every replica and the first error wins, and ping only reports ready when every replica answers. Also deletes dp_degree, a dead twin of dp_size that nothing read. DP remains monolithic-only: disaggregated roles scale by adding role instances, so dp_size > 1 with a disagg role still raises. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same seed through two round-robined replicas must return identical bytes, both drivers must bind their own ingress, and a concurrent pair must finish in about one request's wall time -- two requests serializing on one replica take ~2x, so the 1.6x bound separates the behaviors with margin for jitter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Output materialization, file saving, frame transport, peak-memory recording and request metrics were all gated on rank == 0, which is the replying rank only at dp=1. On any other replica the driver nulled the output without ever saving it, so every request served by replica >= 1 returned "Model generation returned no output" -- caught by the concurrent e2e, since a single offline batch only ever exercises replica 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
/tag-and-rerun-ci |
…dp-size The control-req types load at module top -- the lazy loader guarded an import cycle that does not exist, both source modules being dataclass-only. The sync client keeps a single routed forward path with a per-call REQ socket instead of a dp==1 special case around a persistent socket: connect cost is nothing next to a generation, and one path is one set of timeout semantics. cli.mdx documents --dp-size: replica layout, round-robin, session stickiness, control-op fan-out, and the num_gpus product. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The
Failure signatures — The other 946 unit tests pass, including this PR's |
|
Update: the unit-test breakage healed on main — |
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Motivation
--data-parallel-sizeparsed, the process-group layout reserved the dimension ("tp-sp-pp-cfg-dp", dp outermost), and validation rejected it:dp_size > 1raised "DP is not yet supported", because nothing routed requests to replicas. This implements the missing routing, so N replicas serve N requests concurrently on one launch.Modifications
The design leans on two properties the codebase already had. Because dp is the outermost axis, every replica is a contiguous rank block whose sp/cfg/tp subgroups never cross replicas; and request fan-out already travels a
broadcast_pyobjrelay over exactly those subgroups (Scheduler.recv_reqs). So per-replica serving needs only a per-replica ingress:pingreports ready only when every replica answers.rank == 0, which is the replying rank only at dp=1. On any other replica the driver nulled the output without saving it, so every request served by replica ≥ 1 returned "Model generation returned no output". These gates now key onis_output_rank(first rank of the replica).dp_degree, a dead twin ofdp_sizethat nothing read.dp_size > 1with a disagg role still raises.Test
test/unit/test_dp_routing.py: round-robin coverage, dp=1 degenerate case, session stickiness, control-op classification, fan-out merge semantics, per-replica endpoint derivation.test/single_test_file/test_dp_serving_2_gpu.py(registered in the 2-gpu suite): launches--dp-size 2(one GPU per replica), asserts both drivers bind their own ingress, a fixed seed returns identical bytes through two round-robined replicas, and a concurrent pair completes in < 1.6x one request's wall time — two requests serialized on one replica take ~2x, so the bound separates the behaviors. The no-output bug above was caught by exactly this concurrency check: a single offline batch only ever exercises replica 0.dp replica 0 ... :5575,dp replica 1 ... :5576)Checklist
🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ✅ Run #31059198106
Latest PR Test (Extra): ❌ Run #31059197889