Skip to content

Add DeepSeek-V4-Flash-FP8 (MoRI-EP wide-EP, EP8 + EP16) to sglang_disagg - #240

Open
raviguptaamd wants to merge 10 commits into
ROCm:developfrom
raviguptaamd:add-deepseek-v4-flash-sglang-disagg
Open

raviguptaamd wants to merge 10 commits into
ROCm:developfrom
raviguptaamd:add-deepseek-v4-flash-sglang-disagg

Conversation

@raviguptaamd

Copy link
Copy Markdown
Contributor

Summary

Adds DeepSeek-V4-Flash-FP8 as a MoRI-EP disaggregated model under scripts/sglang_disagg, with both EP8 (1P1D) and EP16 (2P2D) topologies. Mirrors the existing DeepSeek-V3 MoRI-EP entry, plus DSV4-specific handling (dedicated dsv4 attention backend, fp8_e4m3 KV, sparse-MLA, mandatory DP-attention, MoRI-EP HT-prefill / LL-decode split).

What's added

  • models.yamlDeepSeek-V4-Flash-FP8 recipe. num_key_value_heads=1 → DP-attention is mandatory; --deepep-mode normal (prefill/HT) + low_latency (decode/LL); eager prefill.
  • models.json — two runnable entries: ..._ep8_1p1d (xP=1 yD=1) and ..._ep16_2p2d (xP=2 yD=2), both DP_MODE=1.
  • Allowlists (sglang_disagg_mori_io_ep.sh + run_xPyD_models.slurm) — register the model.
  • set_env_vars.sh — DSV4 load-bearing env, MODEL_NAME-guarded.
  • README.MD — model section, image, and validation.

Framework robustness fixes

These generalize the framework beyond SLURM + Mellanox (validated on a non-SLURM Broadcom Thor2 / bnxt cluster); each is guarded so existing setups are unaffected:

  • Persistent JIT cache + orphaned-lock sweep — the MoRI/aiter FileBaton has no PID/timeout, so a lock orphaned by a killed run hangs every later run. Sweep stale locks pre-launch + mount a persistent, image-versioned JIT cache (compile-once).
  • Router-ready gate via network /health poll — instead of grepping a node-local peer log (invisible without a shared FS). FS-agnostic; falls back to the log grep.
  • host_ip / socket-iface autodetect — use the route dev field and match the IPADDRS scope (fabric), not the default-route metric.
  • Don't append mlx5_* (Mellanox-only) to the NIC list in set_env_vars.sh.
  • bnxt libbnxt_re exposure (USE_CX7_NICS=0) — bind-mount the host's ABI-correct libbnxt_re + ldconfig in-container so MoRI finds an RDMA transport.
  • KEEP_ALIVE=1 — leave servers running for external NIAH/perf testing.

Validation (MI308X / gfx942 + Broadcom Thor2)

Served via sglang_router, greedy /v1/completions.

Correctness — needle-in-a-haystack, needle at 10/50/90% depth:

Topology 1K 4K 16K 32K 100K 200K Total
EP8 1P1D 18/18
EP16 2P2D 18/18

Factual length-sweep coherent through 200K on both.

Perf (functional; eager decode, all requests succeeded):

ISL/OSL Con EP8 EP16
8K/1K 16 16/16 16/16
8K/1K 32 32/32 32/32
16K/1K 16 16/16 16/16
16K/1K 32 32/32 32/32

Image

docker pull rocmshared/sglang-disagg-dsv4:mori-mi308-pr (SGLang + MoRI on ROCm 7.2, gfx942), or build from docker/sglang_disagg_inference.ubuntu.amd.Dockerfile.

Known follow-ups

  • Decode runs eager (--disable-cuda-graph) — the MoRI-EP low-latency kernel HIP-launch-fails under cudagraph capture on the pinned MoRI commit. Re-enabling decode cudagraph + SLO perf tuning is a follow-up; correctness is unaffected.

Test plan

  • EP8 1P1D serve + NIAH 18/18 + perf
  • EP16 2P2D serve + NIAH 18/18 + perf
  • Reviewer validation on a Mellanox/SLURM cluster (fixes are guarded no-ops there)

🤖 Generated with Claude Code

@raviguptaamd

Copy link
Copy Markdown
Contributor Author

Reproduction artifacts added

Self-contained perf report + repro scripts are now in scripts/sglang_disagg/dsv4_flash/:

  • PERF_REPORT.html — NIAH (18/18 each topology) + perf tables, image digest, full reproduce steps
  • run_dsv4_nonslurm.sh — per-node docker launch driver (non-SLURM / bnxt path)
  • niah.py — needle-in-a-haystack harness (ENDPOINT env var, defaults to localhost)
  • perf.py — lightweight concurrent-request throughput timer

Anyone can re-measure with: pull rocmshared/sglang-disagg-dsv4:mori-mi308-pr → launch via the driver → ENDPOINT=http://<router>:2322 python3 niah.py and perf.py.

raviguptaamd and others added 3 commits August 28, 2026 23:21
Adds DeepSeek-V4-Flash-FP8 as a MoRI-EP disaggregated model:
- models.yaml: DSV4-Flash recipe (dsv4 attention, fp8_e4m3 KV, sparse-MLA,
  DP-attention mandatory, MoRI-EP with --deepep-mode normal (prefill/HT) /
  low_latency (decode/LL), eager prefill)
- models.json: EP8 1P1D and EP16 2P2D runnable entries
- allowlists (mori_io_ep + slurm): register DeepSeek-V4-Flash-FP8
- set_env_vars.sh: DSV4 load-bearing env (MODEL_NAME-guarded)

Framework robustness fixes (generalize to non-SLURM / non-Mellanox / bnxt):
- Persistent JIT cache mount + orphaned-lock sweep (MoRI/aiter FileBaton has
  no PID/timeout; a lock orphaned by a killed run otherwise hangs later runs)
- Router-ready gate: poll peer /health over the network (FS-agnostic) instead
  of grepping a node-local peer log, so it works without a shared filesystem
- host_ip / socket-iface autodetect: use the route dev field and match the
  IPADDRS scope (fabric), not the default-route metric
- set_env_vars: don't append mlx5_* (Mellanox-only) to the NIC list
- Expose host libbnxt_re + ldconfig in-container for the bnxt (USE_CX7_NICS=0)
  RoCE path so MoRI finds a working RDMA transport
- KEEP_ALIVE=1 to leave servers running for external NIAH/perf testing

Validated on MI308X/gfx942 + Broadcom Thor2: NIAH 18/18 (1K-200K, depths
10/50/90%) on both EP8 1P1D and EP16 2P2D; perf at 8K/1K and 16K/1K @ con 16/32.
Prebuilt image: rocmshared/sglang-disagg-dsv4:mori-mi308-pr

Co-Authored-By: Claude <noreply@anthropic.com>
Self-contained artifacts to reproduce/re-measure DeepSeek-V4-Flash-FP8
EP8/EP16 results:
- PERF_REPORT.html : NIAH (18/18 each) + perf tables, image digest, full
  reproduce steps
- run_dsv4_nonslurm.sh : per-node docker launch driver (non-SLURM / bnxt)
- niah.py  : needle-in-a-haystack harness (ENDPOINT env, defaults localhost)
- perf.py  : lightweight concurrent-request throughput timer

No cluster-internal IPs/hostnames; endpoints are env-parameterized.

Co-Authored-By: Claude <noreply@anthropic.com>
…ments

Replace an internal cluster codename in two comment lines with a neutral
"bnxt/Thor2 (Broadcom)" / "fabric subnet" description. No behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
@raviguptaamd
raviguptaamd force-pushed the add-deepseek-v4-flash-sglang-disagg branch from a0b6819 to 2cbb7b8 Compare August 29, 2026 06:25
DeepSeek-V4-Flash decode previously ran eager (--disable-cuda-graph) because
MoRI-LL crashed under CUDA-graph capture. Root-caused to two independent issues,
both fixed here:

1. MoRI too old. Pinned 158c7e83 (2026-06-08) predates the fixes for the exact
   capture crashes:
   - #363 "guard dispatch kernels against out-of-range expert id" -> the
     low_latency_async.cpp:360 `(pe>=0 && pe<worldSize)` device assertion.
   - #505 "AsyncLL slot double-alloc when top-k does not divide warpSize" ->
     DSV4-Flash is topk6, warpSize64 (6 does not divide 64).
   Bump MORI_COMMIT -> 7c51d18f (includes both, plus recent [AMD][DSV4] fixes).

2. Recv-buffer sizing conflated with the dispatch-token cap. The launcher set
   SGLANG_MORI_NUM_MAX_DISPATCH_TOKENS_PER_RANK (which sizes the LL recv buffer,
   ~= PER_RANK * world_size) equal to MORI_MAX_DISPATCH_TOKENS_DECODE. Shrinking
   the dispatch cap then starved the recv buffer -> low_latency_async.cpp:324
   "Total recv token overflow" during capture. Decouple them (CI sets them
   separately); default decode PER_RANK to 512. Keep this role-specific: prefill
   needs PER_RANK >= chunked_prefill (8192), so set_env_vars must NOT export it
   globally.

models.yaml: decode.dp now captures a bounded set (--cuda-graph-bs 1..64,
backend=full); prefill stays eager (MoRI-HT). set_env_vars adds the decode
dispatch caps (guarded by MODEL_NAME).

Validated on MI308X/gfx942 + Thor2, decode cuda graph=True, 8K/1K:
  EP8 1P1D : con16 35->57 tok/s (+63%), con32 ->104; NIAH 18/18 (1K-200K).
  EP16 2P2D: con16 62->166 tok/s (+168%), con32 65->192 (+195%).

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

Copy link
Copy Markdown
Contributor Author

Decode CUDA-graph now enabled (was eager) — major perf gain

Follow-up to the earlier "decode runs eager" caveat: decode now runs under CUDA-graph (MoRI-LL, backend=full). Root-caused the capture crash to two independent issues, both fixed in commit e08efd6:

  1. MoRI too old. The pinned 158c7e83 (2026-06-08) predated the exact capture fixes:
    • #363 guard dispatch kernels vs out-of-range expert id → the low_latency_async.cpp:360 (pe>=0 && pe<worldSize) device assertion.
    • #505 AsyncLL slot double-alloc when top-k does not divide warpSize → DSV4-Flash is topk6 / warpSize64 (6 ∤ 64).
      Bumped MORI_COMMIT7c51d18f.
  2. Recv-buffer sizing conflated with the dispatch cap. The launcher tied SGLANG_MORI_NUM_MAX_DISPATCH_TOKENS_PER_RANK (sizes the LL recv buffer) to the smaller decode dispatch cap → low_latency_async.cpp:324 "Total recv token overflow" during capture. Decoupled them (decode PER_RANK=512), role-specific so prefill still gets ≥ chunked_prefill.

Perf (8K/1K, decode eager → cudagraph):

con16 con32
EP8 1P1D 35 → 57 tok/s (+63%) 104
EP16 2P2D 62 → 166 tok/s (+168%) 65 → 192 (+195%)

Correctness unchanged: EP8 NIAH 18/18 (1K–200K) with cudagraph, cuda graph: True confirmed on both topologies. Decode TPOT ~87–102 ms (further tunable).

Updated image: rocmshared/sglang-disagg-dsv4:mori-mi308-pr2.

raviguptaamd and others added 6 commits August 29, 2026 08:03
- prefill chunked-prefill-size 8192 -> 131072. Under DP-attention SGLang divides
  the chunk by dp_size (server_args.py: chunked_prefill_size //= dp_size), so 8192
  became 512/rank and serialized the 100K prefill. 131072/dp16 = 8192/rank.
- launcher: set the prefill-branch MoRI recv-buffer PER_RANK
  (SGLANG_MORI_NUM_MAX_DISPATCH_TOKENS_PER_RANK, default 16384) so it satisfies
  "PER_RANK >= per-rank prefill dispatch (chunked/ dp_size)" with the larger chunk.
- decode: --num-continuous-decode-steps 2 amortizes per-step scheduling/MoRI-LL
  dispatch overhead -> TPOT p95 55->49-50ms (meets <50ms at con24/36).

Measured EP16 2P2D, 100K/1.1K, MI308X: TPOT p50 ~48ms / p95 49-50ms at con24/36.
TTFT for 100K remains compute-bound (~82s) on ROCm because the sparse-MLA prefill
kernel is CUDA-only (SGLang disables SGLANG_OPT_FLASHMLA_SPARSE_PREFILL on HIP for
DSV4) and prefill-CP requires interleave which conflicts with the mandatory
DP-attention; <10s TTFT holds for ISL up to ~16K. These are upstream enablement
gaps, tracked separately.

Co-Authored-By: Claude <noreply@anthropic.com>
…aph + 100K/1.1K SLO

The HTML report was previously skipped by .gitignore (*.html), so the PR
referenced a file that was never committed. Force-add it and refresh with the
current state: decode CUDA-graph enabled (MoRI 7c51d18f), image pr2, full
customer SLO sweep (100K/1.1K con 12/24/36 TTFT/TPOT percentiles), TPOT p95<50ms
met at con24/36, TTFT-at-100K root cause + upstream ask.

Co-Authored-By: Claude <noreply@anthropic.com>
… accuracy identical

DeepSeek-V4-Flash ships one MTP head (num_nextn_predict_layers=1). Enabling it as
the EAGLE draft (--speculative-algorithm EAGLE --speculative-num-steps 3
--speculative-eagle-topk 1 --speculative-num-draft-tokens 4) accepts ~3 tokens per
full-model forward pass, cutting decode latency with NO accuracy change (every
speculative token is verified by the full model → bit-identical output).

Measured EP16 2P2D, 100K/1.1K, MI308X (vs decode-cudagraph baseline):
  con12 TPOT p95 59->39ms, con24 49->40ms, con36 50->39ms; throughput +5-14%.
  NIAH 18/18 unchanged. accept len ~3, cuda graph: True (MTP + MoRI-LL coexist;
  no paged_mqa_logits_metadata assertion on SGLang 0.5.15 / gfx942).

Supersedes --num-continuous-decode-steps 2 (MTP gives a larger TPOT win).
Adds slo_harness.py (streaming TTFT/TPOT percentiles; counts usage.completion_tokens
so per-token latency is correct under speculative burst delivery).

Co-Authored-By: Claude <noreply@anthropic.com>
…y identical)

Update PERF_REPORT with the MTP results: full 100K/1.1K customer sweep, the
eager->cudagraph->MTP optimization path, and the accuracy-preserved note.

Co-Authored-By: Claude <noreply@anthropic.com>
…ated

Add §4.3 MTP: EAGLE spec-decode enabled on both topologies, NIAH 18/18 each
(EP8 paced), accept len ~3 (EP16) / ~2 (EP8), TPOT p95 to 39-40ms (EP16) /
~54ms (EP8), accuracy identical. Note EP8 single-node burst sensitivity at
extreme context; EP16 recommended for 100K/1.1K. MTP works with MoRI-LL +
decode CUDA-graph on 0.5.15/gfx942 (no paged_mqa assertion).

Co-Authored-By: Claude <noreply@anthropic.com>
…I ROCtx)

Adds steps_to_profile.md — field notes for profiling MoRI-IO + MoRI-EP with
rocprofv3 on a stock DSV4 MoRI image (no MAD-private profiling plumbing), taken
on MI308X + Broadcom Thor2. Documents what works (MoRI ROCtx markers PR #510
already in image; rocprofv3 follows forks; bnxt NIC counters) and — importantly
— what does NOT: wrapping all 8 DP workers under rocprofv3 --kernel-trace
crashes the gloo collective on MI308X (per-kernel hook overhead stalls a sync).
--dist-timeout and -P collection-period delay but do not remove the overhead;
--attach cannot retroactively instrument kernels. Recommends rank-0-only (needs
a small SGLang patch), marker-trace-only, or a TP-only serve.

Also wires a RUN_PROFILE=1 gate into sglang_disagg_mori_io_ep.sh (_prof_prefix:
rocprofv3 wrapper with -P + MoRI ROCtx gates + PROFILE_KERNEL toggle; raised
dist-timeout). Marked in-code as the BASE to build the rank-0 patch on, not a
working full-DP capture — see steps_to_profile.md §3/§4.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

1 participant