prefill_sp.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  1. """Prefill sequence parallel for DeepSeek-V4.1 at TP4 (DSV41_PREFILL_SP=comm | 1).
  2. Today every TP rank runs every row of a prefill chunk through the row-local work (the
  3. hyper-connection post / combine / norm / stats on the [M, 4, 5120] residual, the Engram `wkv`
  4. GEMM and gate), and each sublayer ends with an all-reduce of [M, 5120]. Here, on the full-row
  5. layers (0 .. late_layer_start-1 under decoder SWA bounded replay), the residual stays split by
  6. rows: rank r owns rows [r*M/W, (r+1)*M/W).
  7. attention : all-gather x (bf16) -> stock attention on all M rows, wo_b without its
  8. all-reduce -> reduce-scatter along rows
  9. MoE : all-gather x (bf16) -> stock DeepseekV2MoE under mlp_reduce_scatter=True
  10. (skips the post-experts all-reduce) -> reduce-scatter along rows
  11. Engram : full-row lookup (unchanged, so the prefetch still matches) -> reduce-scatter
  12. -> wkv GEMM + gate on the shard (wkv per (layer, M) bit-exact self-check,
  13. agreed over TP with MIN, full-M fallback)
  14. hc post / combine / norm / stats: stock code on the shard; every kernel choice that the
  15. engine keys on the row count is keyed on the FULL chunk row count
  16. (sp_logical_rows(), used by _hc_combine / hc_post here, by hc_fused and by
  17. hc_prefill_fused)
  18. layer 21 : the tail rows of R and prev_pre are gathered (byte copy) and the late layers
  19. run stock
  20. no tail : R and the last pre are gathered at exit
  21. Modes
  22. DSV41_PREFILL_SP=comm P0, transport only: nothing is sharded; the same three reduction sites
  23. (wo_b, MoE output, Engram lookup) run reduce-scatter + all-gather
  24. instead of the all-reduce. Same numerics as stage 1 fast by
  25. construction (same reduce-scatters on the same tensors).
  26. DSV41_PREFILL_SP=1 stage 1, bf16 gathers.
  27. DSV41_PREFILL_SP_EXACT=1 (stage 1) every reduce-scatter is all-reduce + keep the quarter:
  28. must reproduce production bit for bit.
  29. DSV41_PREFILL_SP_MIN_ROWS default 2048; chunks below it or not divisible by the TP size run
  30. the stock path in every mode (same predicate).
  31. DSV41_PREFILL_SP_WKV=full never run wkv on the shard (gather the lookup, full-M GEMM).
  32. Scope: extend without speculation only (plain prefill chunks), eager (never under graph
  33. capture or torch.compile), TP only (no DP attention, no CP, no a2a MoE, PP 1). Decode,
  34. verify, draft and CUDA graphs never enter this code. Every decision depends only on shape,
  35. forward mode and static config, so it is identical on every rank.
  36. Drift guard: sha256[:16] of the engine sources this relies on are checked at install; any
  37. difference raises (refuses to boot). hc_prefill_fused, when enabled, must key its gates on
  38. sp_logical_rows() (it declares SP_LOGICAL_ROWS = True); otherwise install raises.
  39. """
  40. import hashlib
  41. import inspect
  42. import logging
  43. import os
  44. import threading
  45. from contextlib import nullcontext
  46. import torch
  47. logger = logging.getLogger(__name__)
  48. _RAW = os.environ.get("DSV41_PREFILL_SP", "0").strip().lower()
  49. MODE = "comm" if _RAW == "comm" else ("shard" if _RAW in ("1", "on", "true", "shard") else "off")
  50. EXACT = os.environ.get("DSV41_PREFILL_SP_EXACT", "0").strip().lower() in ("1", "on", "true")
  51. MIN_ROWS = int(os.environ.get("DSV41_PREFILL_SP_MIN_ROWS", "2048"))
  52. WKV_SHARD = os.environ.get("DSV41_PREFILL_SP_WKV", "shard").strip().lower() != "full"
  53. # Stage 2 (DSV41_PREFILL_SP_FP8=1, with DSV41_PREFILL_SP=1): on full-row layers whose attention
  54. # reads its input only through wqkv_a (no compressor / indexer: all but the kv/index sources 2, 8,
  55. # 14, 20), each rank MXFP8-quantizes its shard (per row, per 32 columns; linear scale layout),
  56. # all-gathers the fp8 bytes + ue8m0 scales, swizzles the scales to 128x4 and hands attention the
  57. # pre-quantized input (x_quant) instead of the bf16 rows. Exact by construction if the quantizer is
  58. # row-local; checked, not assumed: the first chunk of every size compares the gathered MXFP8 input
  59. # with the stock quantization of the gathered bf16 rows AND wqkv_a's output on both, agrees over
  60. # TP (MIN), and keeps the bf16 gather for that size on any difference.
  61. FP8 = os.environ.get("DSV41_PREFILL_SP_FP8", "0").strip().lower() in ("1", "on", "true")
  62. # Debug (never in production): DSV41_PREFILL_SP_DEBUG=compare,fp (either or both).
  63. # fp per traced prefill chunk, rank 0 logs the chunk layout and a checksum of every
  64. # layer's input / attention in+out / MoE in+out / output residual and pre, over the full
  65. # rows. Works with DSV41_PREFILL_SP unset too (then only the tracing is installed), so an
  66. # off boot and an SP boot can be diffed chunk by chunk.
  67. # compare (stage 1) next to every sharded op, the stock op runs on the same (gathered) inputs
  68. # and every rank logs each op whose bits differ: layer, op, differing elements, max abs.
  69. # wo_b and the MoE also run their stock all-reduce path on the same partials.
  70. # DSV41_PREFILL_SP_DEBUG_CHUNKS traced chunks (default 64, every extend forward counts)
  71. # DSV41_PREFILL_SP_DEBUG_DUMP directory: torch.save of the first mismatch per op and rank
  72. DEBUG = {t.strip() for t in os.environ.get("DSV41_PREFILL_SP_DEBUG", "").lower().split(",") if t.strip()}
  73. if "compare" in DEBUG:
  74. DEBUG.add("fp")
  75. DEBUG_CHUNKS = int(os.environ.get("DSV41_PREFILL_SP_DEBUG_CHUNKS", "64"))
  76. DEBUG_DUMP = os.environ.get("DSV41_PREFILL_SP_DEBUG_DUMP", "").strip()
  77. # Engine sources (sglang dsv4.1 f80c91a4b as in dsv41-4x-spark:ds41-verify-0924d), sha256[:16]
  78. # of inspect.getsource(inspect.unwrap(obj)). The loop below is a copy of the first; the others
  79. # are the call sites and kernel gates whose behaviour the exactness argument depends on.
  80. _EXPECTED = {
  81. "v4.DeepseekV4Model._forward_layers_hc_pre_from_prev": "bc059d80f038e7f8",
  82. "v4.DeepseekV4DecoderLayer.forward_hc_pre_from_prev": "49dc092d369923cf",
  83. "v4.DeepseekV4DecoderLayer._hc_combine": "da5688eff1cf6fab",
  84. "v4.DeepseekV4DecoderLayer._hc_mix_stats": "0a6edf4b45cd160b",
  85. "v4.DeepseekV4DecoderLayer.hc_post": "8adb8062e8e66c31",
  86. "v4.DeepseekV4DecoderLayer._run_moe_ffn_dp_sync": "065622659105f3fe",
  87. "v4.DeepseekV4Model.forward": "3ab2a4745377b83b",
  88. "v4.MQALayer.forward": "d21dfc6533e96207",
  89. "v2.DeepseekV2MoE.forward": "d931dfa4d2acacd8",
  90. "v2.DeepseekV2MoE.forward_normal": "9da361af16dafbec",
  91. "engram.Engram.forward": "841b358015e044b8",
  92. "engram.EngramEmbedding.forward": "b2f3ea98879b9437",
  93. "engram.EngramEmbedding._lookup": "66a0f98a49fcd266",
  94. "hcn.hc_combine_norm": "afa73fe49eb1d4b0",
  95. "hcn._hc_combine_norm_prefill": "74f71ebf85b010ee",
  96. "linear.RowParallelLinear.forward": "30be12725b32806b",
  97. }
  98. # sglang.srt.layers.attention.deepseek_v4_backend: the tail-row semantics _tail_rows reproduces.
  99. # Stage 2 additionally relies on these (checked at install when DSV41_PREFILL_SP_FP8=1): which
  100. # inputs attention's prefill path reads, and when it takes a pre-quantized input.
  101. _EXPECTED_FP8 = {
  102. "v4.MQALayer._forward_prepare": "b3adfe1a07ee6127",
  103. "v4.MQALayer.accepts_mxfp8_swizzled_input": "5bb4fddfc5b329ca",
  104. "v4.MQALayer._compute_kv_to_cache": "f52f600c4986428c",
  105. "v4.MQALayer._compute_kv_bf16": "00c4ab90b403d1e9",
  106. }
  107. _EXPECTED_TAIL = {
  108. "LateLayerTail.rows": "c58392d3ef651408",
  109. "LateLayerTail.real_rows": "c78962c2e01e0e3d",
  110. "_tail_rows": "54a3cbff5e674e84",
  111. }
  112. class _Ctx(threading.local):
  113. def __init__(self):
  114. self.plan = None # _Plan while the layer loop is inside the full-row region
  115. self.inner = 0 # > 0 inside a gathered attention / MoE call
  116. self.skip_wo_b = None # the wo_b whose all-reduce the current attention call skips
  117. self.dbg = None # _Dbg while a traced chunk runs
  118. _ctx = _Ctx()
  119. _M = {} # engine modules, filled by install
  120. _STATIC = {"checked": False}
  121. _WKV_OK = {} # (layer_id, M) -> bool (agreed over TP)
  122. _SEEN_M = set()
  123. _REQUIRE_CUDA = True # CPU tests switch this off
  124. _DBG = {"chunks": 0, "dumped": 0}
  125. def _agree_min(p, local):
  126. """Rank-agreed AND of a local boolean (MIN over the TP group)."""
  127. flag = torch.tensor([1 if local else 0], dtype=torch.int32, device=torch.cuda.current_device())
  128. torch.distributed.all_reduce(flag, op=torch.distributed.ReduceOp.MIN, group=p.group.device_group)
  129. return bool(flag.item())
  130. def _mega_moe(moe, x):
  131. try:
  132. from sglang.srt.layers.moe.mega_moe import should_use_mega_moe
  133. except ImportError:
  134. return False
  135. return should_use_mega_moe(moe, x)
  136. class _Dbg:
  137. def __init__(self, idx, rows, plan, rank):
  138. self.idx, self.rows, self.plan, self.rank = idx, rows, plan, rank
  139. self.compare = "compare" in DEBUG and plan is not None and plan.sharded
  140. self.layer = -1
  141. self.fps = [] # (layer, key, fp)
  142. self.checked = 0
  143. self.bad = [] # (layer, op, ndiff, numel, maxabs)
  144. self.dumped = set()
  145. def _fp(t):
  146. """Order-sensitive checksum of a tensor's bytes (GPU, chunked int64 sums)."""
  147. if t is None:
  148. return "none"
  149. t = t.contiguous().reshape(-1)
  150. nbytes = t.numel() * t.element_size()
  151. v = t.view(torch.int32) if nbytes % 4 == 0 else t.view(torch.uint8)
  152. s1 = torch.zeros((), dtype=torch.int64, device=v.device)
  153. s2 = torch.zeros((), dtype=torch.int64, device=v.device)
  154. step = 1 << 22
  155. for i in range(0, v.numel(), step):
  156. c = v[i:i + step].to(torch.int64)
  157. w = torch.arange(i, i + c.numel(), device=v.device, dtype=torch.int64) % 65521 + 1
  158. s1 += c.sum()
  159. s2 += (c * w).sum()
  160. return f"{(int(s1) * 1000003 + int(s2)) & ((1 << 48) - 1):012x}"
  161. def _full_rows(t):
  162. """t over every row of the chunk: gathered when it is this rank's shard (collective)."""
  163. p = _ctx.plan
  164. if (t is not None and p is not None and p.sharded and isinstance(t, torch.Tensor)
  165. and t.dim() >= 1 and t.shape[0] == p.shard and p.shard != p.rows):
  166. return _gather_rows(p, t)
  167. return t
  168. def _rec(key, t, full=False):
  169. d = _ctx.dbg
  170. if d is None:
  171. return
  172. d.fps.append((d.layer, key, _fp(t if full else _full_rows(t))))
  173. class _Inner:
  174. def __enter__(self):
  175. _ctx.inner += 1
  176. def __exit__(self, *a):
  177. _ctx.inner -= 1
  178. def _comparing():
  179. d = _ctx.dbg
  180. return d is not None and d.compare and _ctx.plan is not None and _ctx.plan.sharded
  181. def _cmp(op, ours, ref, inputs=None):
  182. """Bits of our result vs the stock op's on the same inputs; log and dump differences."""
  183. d = _ctx.dbg
  184. if d is None:
  185. return
  186. outs = ours if isinstance(ours, (tuple, list)) else (ours,)
  187. refs = ref if isinstance(ref, (tuple, list)) else (ref,)
  188. d.checked += 1
  189. for k, (a, b) in enumerate(zip(outs, refs)):
  190. name = op if len(outs) == 1 else f"{op}[{k}]"
  191. if a.shape == b.shape and a.dtype == b.dtype and torch.equal(a, b):
  192. continue
  193. if a.shape != b.shape:
  194. nd, mx = -1, float("nan")
  195. else:
  196. diff = (a.float() - b.float()).abs()
  197. nd, mx = int((a != b).sum()), float(diff.max())
  198. d.bad.append((d.layer, name, nd, a.numel(), mx))
  199. if len(d.bad) <= 24:
  200. print(f"DSV41_PREFILL_SP compare MISMATCH chunk={d.idx} M={d.rows} rank={d.rank} "
  201. f"layer={d.layer} op={name} shape={tuple(a.shape)}/{tuple(b.shape)} "
  202. f"differing={nd}/{a.numel()} max_abs={mx:.4g}", flush=True)
  203. if DEBUG_DUMP and op not in d.dumped and _DBG["dumped"] < 16:
  204. d.dumped.add(op)
  205. _DBG["dumped"] += 1
  206. try:
  207. os.makedirs(DEBUG_DUMP, exist_ok=True)
  208. path = os.path.join(DEBUG_DUMP, f"psp_c{d.idx}_r{d.rank}_L{d.layer}_{op.replace(' ', '_')}.pt")
  209. blob = {"op": name, "layer": d.layer, "chunk": d.idx, "rows": d.rows, "rank": d.rank,
  210. "ours": a.detach().cpu(), "ref": b.detach().cpu()}
  211. if inputs:
  212. blob["inputs"] = {n: (v.detach().cpu() if isinstance(v, torch.Tensor) else v)
  213. for n, v in inputs.items()}
  214. torch.save(blob, path)
  215. print(f"DSV41_PREFILL_SP compare dumped {path}", flush=True)
  216. except Exception as exc: # noqa: BLE001
  217. print(f"DSV41_PREFILL_SP compare dump failed: {exc!r}", flush=True)
  218. def _debug_begin(model, hidden_states, forward_batch, input_ids, p):
  219. if not DEBUG or _DBG["chunks"] >= DEBUG_CHUNKS:
  220. return None
  221. if torch.compiler.is_compiling() or (torch.cuda.is_available()
  222. and torch.cuda.is_current_stream_capturing()):
  223. return None
  224. if not forward_batch.forward_mode.is_extend_without_speculative():
  225. return None
  226. _DBG["chunks"] += 1
  227. rank = p.rank if p is not None else _M["v4"].get_tp_group().rank_in_group
  228. d = _Dbg(_DBG["chunks"], hidden_states.shape[0], p, rank)
  229. if rank == 0:
  230. lens = getattr(forward_batch, "extend_seq_lens_cpu", None)
  231. pref = getattr(forward_batch, "extend_prefix_lens_cpu", None)
  232. mode = "off" if p is None else (MODE + ("-exact" if p.exact else "") if p.sharded else MODE)
  233. print(f"DSV41_PREFILL_SP fp chunk={d.idx} M={d.rows} mode={mode} reqs={len(lens) if lens is not None else '?'} "
  234. f"extend={list(lens)[:16] if lens is not None else '?'} prefix={list(pref)[:16] if pref is not None else '?'} "
  235. f"ids={_fp(input_ids)} R0={_fp(hidden_states)}", flush=True)
  236. return d
  237. def _debug_end(d, out):
  238. try:
  239. hs, pre = out[0], out[1]
  240. fh, fp_ = _fp(hs), _fp(pre)
  241. except Exception as exc: # noqa: BLE001
  242. fh = fp_ = f"err {exc!r}"
  243. if d.rank == 0:
  244. by_layer = {}
  245. for layer, key, f in d.fps:
  246. by_layer.setdefault(layer, []).append(f"{key}={f}")
  247. for layer, items in by_layer.items():
  248. print(f"DSV41_PREFILL_SP fp chunk={d.idx} L={layer} " + " ".join(items), flush=True)
  249. print(f"DSV41_PREFILL_SP fp chunk={d.idx} out R={fh} pre={fp_}", flush=True)
  250. if d.compare:
  251. first = f" first: layer {d.bad[0][0]} {d.bad[0][1]}" if d.bad else ""
  252. print(f"DSV41_PREFILL_SP compare chunk={d.idx} M={d.rows} rank={d.rank}: {d.checked} ops checked, "
  253. f"{len(d.bad)} mismatched{first}", flush=True)
  254. def _make_layer_forward(orig):
  255. """Tracing only: record each layer's input / output residual and pre."""
  256. def forward_hc_pre_from_prev(self, *args, **kwargs):
  257. d = _ctx.dbg
  258. if d is None or _ctx.inner:
  259. return orig(self, *args, **kwargs)
  260. hs = kwargs["hidden_states"] if "hidden_states" in kwargs else args[1]
  261. d.layer = getattr(self, "layer_id", d.layer + 1)
  262. _rec("Rin", hs)
  263. out = orig(self, *args, **kwargs)
  264. _rec("Rout", out[0])
  265. _rec("pre", out[1])
  266. return out
  267. forward_hc_pre_from_prev.__wrapped__ = orig
  268. return forward_hc_pre_from_prev
  269. class _Plan:
  270. __slots__ = ("rows", "world", "rank", "shard", "lo", "hi", "group", "sharded", "exact")
  271. def __init__(self, rows, world, rank, group, sharded, exact):
  272. self.rows, self.world, self.rank, self.group = rows, world, rank, group
  273. self.shard = rows // world
  274. self.lo, self.hi = rank * self.shard, (rank + 1) * self.shard
  275. self.sharded, self.exact = sharded, exact
  276. @property
  277. def in_rows(self):
  278. return self.shard if self.sharded else self.rows
  279. # ------------------------------------------------------------------------------------------
  280. # the logical-row helper (kernel gates key on the full chunk row count)
  281. # ------------------------------------------------------------------------------------------
  282. def sp_logical_rows(x):
  283. """Row count a kernel gate must use for `x`: the full chunk row count M when `x` is this
  284. rank's shard of a sequence-parallel chunk, else x.shape[0]. Cheap and safe to call when
  285. the adapter is off."""
  286. n = x.shape[0]
  287. p = _ctx.plan
  288. if p is None or _ctx.inner or not p.sharded:
  289. return n
  290. return p.rows if n == p.shard else n
  291. def enabled():
  292. return MODE != "off"
  293. # ------------------------------------------------------------------------------------------
  294. # row partition (pure; unit-tested on CPU)
  295. # ------------------------------------------------------------------------------------------
  296. def eligible_rows(rows, world, min_rows=None):
  297. """Shape predicate shared by every mode: rows >= MIN_ROWS and divisible by the TP size."""
  298. min_rows = MIN_ROWS if min_rows is None else min_rows
  299. return world > 1 and rows >= min_rows and rows % world == 0
  300. def shard_range(rows, world, rank):
  301. s = rows // world
  302. return rank * s, (rank + 1) * s
  303. def tail_plan(rows, world, tail_len):
  304. """'full' when gathering all rows is no more traffic than gathering world * T rows."""
  305. return "full" if world * tail_len >= rows else "owners"
  306. def owner_local(idx, shard):
  307. owner = torch.div(idx, shard, rounding_mode="floor")
  308. return owner, idx - owner * shard
  309. # ------------------------------------------------------------------------------------------
  310. # collectives (the group is a sglang GroupCoordinator; tests pass a fake one)
  311. # ------------------------------------------------------------------------------------------
  312. def _gather_rows(p, x):
  313. """[S, ...] shard on every rank -> [M, ...] (byte copy)."""
  314. x = x.contiguous()
  315. out = x.new_empty((p.rows, *x.shape[1:]))
  316. p.group.all_gather_into_tensor(out, x)
  317. return out
  318. def _reduce_rows(p, x):
  319. """[M, ...] per-rank partial -> this rank's reduced [S, ...] rows."""
  320. x = x.contiguous()
  321. if p.exact:
  322. full = p.group.all_reduce(x)
  323. return full[p.lo:p.hi]
  324. out = x.new_empty((p.shard, *x.shape[1:]))
  325. p.group.reduce_scatter_tensor(out, x)
  326. return out
  327. def _reduce_out(p, x):
  328. """Output of a reduction site: this rank's rows (stage 1) or every row (P0: RS + AG)."""
  329. if p.sharded:
  330. return _reduce_rows(p, x)
  331. x = x.contiguous()
  332. part = x.new_empty((p.shard, *x.shape[1:]))
  333. p.group.reduce_scatter_tensor(part, x)
  334. return _gather_rows(p, part)
  335. def _tail_real_rows(p, tail, t):
  336. """tail.real_rows(concat of every rank's shard t), as a byte copy."""
  337. if tail.contiguous_start is not None:
  338. start, idx = int(tail.contiguous_start), None
  339. n_tail = p.rows - start
  340. else:
  341. start, idx = None, tail.token_indices
  342. n_tail = int(idx.shape[0])
  343. if tail_plan(p.rows, p.world, n_tail) == "full" or n_tail == 0:
  344. full = _gather_rows(p, t)
  345. return full[start:] if idx is None else full[idx]
  346. if idx is None:
  347. idx = torch.arange(start, p.rows, device=t.device)
  348. owner, local = owner_local(idx.to(torch.int64), p.shard)
  349. mine = t.contiguous()[local.clamp(0, p.shard - 1)].contiguous()
  350. buf = t.new_empty((p.world * n_tail, *t.shape[1:]))
  351. p.group.all_gather_into_tensor(buf, mine)
  352. buf = buf.view(p.world, n_tail, *t.shape[1:])
  353. return buf[owner, torch.arange(n_tail, device=t.device)]
  354. def _tail_rows(p, tail, t):
  355. rows = _tail_real_rows(p, tail, t)
  356. if tail.pad_rows:
  357. rows = torch.cat([rows, rows.new_zeros((tail.pad_rows, *rows.shape[1:]))])
  358. return rows
  359. # ------------------------------------------------------------------------------------------
  360. # drift guard
  361. # ------------------------------------------------------------------------------------------
  362. def _src_hash(obj):
  363. obj = getattr(obj, "fn", obj) # triton JITFunction
  364. obj = inspect.unwrap(obj)
  365. return hashlib.sha256(inspect.getsource(obj).encode()).hexdigest()[:16]
  366. def _resolve(name):
  367. mod, attr = name.split(".", 1)
  368. obj = _M[mod]
  369. for part in attr.split("."):
  370. obj = getattr(obj, part)
  371. return obj
  372. def engine_hashes():
  373. return {k: _src_hash(_resolve(k)) for k in _EXPECTED}
  374. def check_engine():
  375. exp = dict(_EXPECTED)
  376. if FP8:
  377. exp.update(_EXPECTED_FP8)
  378. got = {k: _src_hash(_resolve(k)) for k in exp}
  379. bad = [f"{k} {got[k]} != {exp[k]}" for k in got if exp[k] != got[k]]
  380. if bad:
  381. raise RuntimeError("DSV41_PREFILL_SP: engine source drifted, refusing to run: " + "; ".join(bad))
  382. def _load_modules(model_mod):
  383. from sglang.kernels.ops.layernorm import hc_combine_norm as hcn
  384. from sglang.kernels.ops.layernorm import mhc
  385. from sglang.srt.layers import engram, linear
  386. from sglang.srt.models import deepseek_v2 as v2
  387. _M.update(v4=model_mod, v2=v2, engram=engram, hcn=hcn, mhc=mhc, linear=linear)
  388. def _check_hc_prefill_fused():
  389. if os.environ.get("DSV41_HC_PREFILL_FUSED", "0").strip() in ("0", "off", "false", ""):
  390. return
  391. import hc_prefill_fused as hpf
  392. if not getattr(hpf, "SP_LOGICAL_ROWS", False):
  393. raise RuntimeError(
  394. "DSV41_PREFILL_SP=1 with DSV41_HC_PREFILL_FUSED=1 needs hc_prefill_fused to key its "
  395. "row gates on prefill_sp.sp_logical_rows() (and declare SP_LOGICAL_ROWS = True); "
  396. "refusing to run. See diagnostics/dsv41-prefill-sp/IMPL.md.")
  397. # ------------------------------------------------------------------------------------------
  398. # plan (rank-invariant: shape, forward mode, static config)
  399. # ------------------------------------------------------------------------------------------
  400. def _check_tail_engine():
  401. from sglang.srt.layers.attention import deepseek_v4_backend as b
  402. bad = []
  403. for k, want in _EXPECTED_TAIL.items():
  404. obj = b
  405. for part in k.split("."):
  406. obj = getattr(obj, part)
  407. got = _src_hash(obj)
  408. if got != want:
  409. bad.append(f"deepseek_v4_backend.{k} {got} != {want}")
  410. if bad:
  411. raise RuntimeError("DSV41_PREFILL_SP: engine source drifted, refusing to run: " + "; ".join(bad))
  412. def _static_check(model, world):
  413. v4 = _M["v4"]
  414. if not _STATIC.get("tail_checked"):
  415. _check_tail_engine()
  416. _STATIC["tail_checked"] = True
  417. par = v4.get_parallel()
  418. errs = []
  419. if model.pp_group.world_size != 1:
  420. errs.append("PP > 1")
  421. if par.attn_dp_size != 1:
  422. errs.append(f"attn_dp_size {par.attn_dp_size}")
  423. if par.tp_size != world or par.attn_tp_size != world:
  424. errs.append(f"tp {par.tp_size} / attn_tp {par.attn_tp_size} / group {world}")
  425. if not v4.get_moe_a2a_backend().is_none():
  426. errs.append("a2a MoE backend")
  427. if not model.hc_pre_from_prev_sublayer or model.hc_mult != 4:
  428. errs.append("hc layout")
  429. if v4.get_platform().is_sm100:
  430. errs.append("sm100 (_hc_mix_stats bf16x3/deepgemm gates are row-count keyed)")
  431. if MODE == "comm" and v4.envs.SGLANG_ENABLE_DETERMINISTIC_INFERENCE.get():
  432. errs.append("deterministic inference (reduce_scatter_tensor is all_reduce there)")
  433. if MIN_ROWS < 256:
  434. errs.append("DSV41_PREFILL_SP_MIN_ROWS below 256 would reach decode/verify sizes")
  435. last = model.late_layer_start if model.late_layer_start is not None else model.end_layer
  436. for i in range(model.start_layer, last):
  437. layer = model.layers[i]
  438. attn, mlp = layer.self_attn, layer.mlp
  439. if not isinstance(attn, v4.MQALayer) or not isinstance(mlp, _M["v2"].DeepseekV2MoE):
  440. errs.append(f"layer {i}: {type(attn).__name__}/{type(mlp).__name__}")
  441. continue
  442. rpl = getattr(_M.get("linear"), "RowParallelLinear", None)
  443. if rpl is not None and not isinstance(attn.wo_b, rpl):
  444. errs.append(f"layer {i}: wo_b is {type(attn.wo_b).__name__}")
  445. if not (attn.wo_b.reduce_results and attn.wo_b.tp_size == world and attn.attn_tp_size == world):
  446. errs.append(f"layer {i}: wo_b reduce/tp")
  447. if getattr(mlp, "_shared_expert_tp1", False) or getattr(mlp, "_enable_a2a_moe", False):
  448. errs.append(f"layer {i}: TP1 shared expert or a2a MoE")
  449. if mlp.tp_size != world:
  450. errs.append(f"layer {i}: moe tp {mlp.tp_size}")
  451. if getattr(layer, "dsa_enable_prefill_cp", False) or layer.use_fused_mhc_post_pre:
  452. errs.append(f"layer {i}: prefill CP / fused post-pre")
  453. if layer.engram is not None and layer.engram.embed.tp_size != world:
  454. errs.append(f"layer {i}: engram tp")
  455. if errs:
  456. raise RuntimeError("DSV41_PREFILL_SP: unsupported configuration, refusing to run: " + "; ".join(errs))
  457. if FP8 and MODE == "shard" and world > 1:
  458. fp8_layers = [i for i in range(model.start_layer, last)
  459. if fp8_eligible_attn(model.layers[i].self_attn)]
  460. if _M["v4"].get_tp_group().rank_in_group == 0:
  461. print(f"DSV41_PREFILL_SP fp8 attention gather on layers {fp8_layers} "
  462. f"({len(fp8_layers)} of {last - model.start_layer}; the rest gather bf16)", flush=True)
  463. _STATIC["checked"] = True
  464. def _plan(model, hidden_states, forward_batch):
  465. if MODE == "off":
  466. return None
  467. v4 = _M["v4"]
  468. if torch.compiler.is_compiling() or (torch.cuda.is_available()
  469. and torch.cuda.is_current_stream_capturing()):
  470. return None
  471. if not forward_batch.forward_mode.is_extend_without_speculative():
  472. return None
  473. if v4.is_in_breakable_cuda_graph() or v4.is_cp_active(forward_batch):
  474. return None
  475. if v4.get_attn_tp_context().input_scattered:
  476. return None
  477. if not (hidden_states.dim() == 3 and hidden_states.dtype == torch.bfloat16
  478. and (hidden_states.is_cuda or not _REQUIRE_CUDA) and hidden_states.is_contiguous()):
  479. return None
  480. group = v4.get_tp_group()
  481. rows, world = hidden_states.shape[0], group.world_size
  482. if not eligible_rows(rows, world):
  483. return None
  484. if not _STATIC["checked"]:
  485. _static_check(model, world)
  486. sharded = MODE == "shard"
  487. p = _Plan(rows, world, group.rank_in_group, group, sharded, EXACT and sharded)
  488. if rows not in _SEEN_M:
  489. _SEEN_M.add(rows)
  490. if p.rank == 0 and len(_SEEN_M) <= 8:
  491. print(f"DSV41_PREFILL_SP {MODE}{' exact' if p.exact else ''}: chunk of {rows} rows -> "
  492. f"{'shards of ' + str(p.shard) if sharded else 'reduce-scatter + all-gather'}",
  493. flush=True)
  494. return p
  495. # ------------------------------------------------------------------------------------------
  496. # Engram on the shard
  497. # ------------------------------------------------------------------------------------------
  498. def _wkv_shard(p, engram, emb):
  499. """kv rows [lo, hi) from the shard's lookup rows, bit-identical to the full-M GEMM."""
  500. if not WKV_SHARD:
  501. return engram.wkv(_gather_rows(p, emb).flatten(-2))[0][p.lo:p.hi]
  502. key = (engram.layer_hash_index, p.rows)
  503. ok = _WKV_OK.get(key)
  504. if ok is None:
  505. full = engram.wkv(_gather_rows(p, emb).flatten(-2))[0]
  506. mine = full[p.lo:p.hi].clone()
  507. del full # the [M, 25600] output is not held across the agreement
  508. part = engram.wkv(emb.flatten(-2))[0]
  509. local = bool(torch.equal(mine, part))
  510. ok = _WKV_OK[key] = _agree_min(p, local)
  511. if p.rank == 0 and (len(_WKV_OK) <= 4 or not ok):
  512. print(f"DSV41_PREFILL_SP wkv (engram {engram.layer_hash_index}, M={p.rows}): shard GEMM "
  513. f"{'bit-exact on every rank -> ON' if ok else 'differs -> full-M GEMM'} "
  514. f"(rank 0 local {local})", flush=True)
  515. return part if ok else mine
  516. if ok:
  517. return engram.wkv(emb.flatten(-2))[0]
  518. return engram.wkv(_gather_rows(p, emb).flatten(-2))[0][p.lo:p.hi]
  519. def _engram_lookup_partial(engram, ids):
  520. embed = engram.embed
  521. if embed._shared: # host-shared table: every rank already has every row
  522. return embed(ids), False
  523. return embed._owned_rows(ids), True
  524. def _sp_engram(p, engram, x, ids, forward_batch=None):
  525. """Stock Engram.forward on a shard of x: lookup on all rows, reduce, wkv + gate on rows."""
  526. emb, partial = _engram_lookup_partial(engram, ids)
  527. if p.sharded:
  528. emb = _reduce_rows(p, emb) if partial else emb[p.lo:p.hi]
  529. kv = _wkv_shard(p, engram, emb)
  530. out = engram.apply_gate(x, kv.contiguous())
  531. if _comparing():
  532. with _Inner():
  533. xg = _gather_rows(p, x)
  534. # slices are cloned at once: the [M, ...] references are not held across collectives
  535. ref_kv = engram.wkv(_gather_rows(p, emb).flatten(-2))[0][p.lo:p.hi].clone()
  536. ref = engram(xg, ids, forward_batch, cp_all_tokens=False)[p.lo:p.hi].clone()
  537. del xg
  538. _cmp("engram wkv", kv, ref_kv)
  539. _cmp("engram out", out, ref, {"x": x, "kv": kv})
  540. return out
  541. if partial: # P0: reduce-scatter + all-gather in place of the all-reduce
  542. emb = _reduce_out(p, emb)
  543. kv, _ = engram.wkv(emb.flatten(-2))
  544. return engram.apply_gate(x, kv)
  545. # ------------------------------------------------------------------------------------------
  546. # the layer loop (copy of DeepseekV4Model._forward_layers_hc_pre_from_prev, hash-pinned)
  547. # ------------------------------------------------------------------------------------------
  548. def _sp_forward_layers(self, p, positions, hidden_states, forward_batch, input_ids,
  549. input_ids_global, capture_dspark, dspark_aux_hidden_states):
  550. v4 = _M["v4"]
  551. assert self.pp_group.world_size == 1, "pre-mix hand-off across PP is not wired"
  552. hash_ids = None
  553. if self.engram_hasher is not None:
  554. hash_ids = self.engram_hasher(input_ids, forward_batch)
  555. tail = None
  556. attn_backend = None
  557. if self.late_layer_start is not None and forward_batch.forward_mode.is_extend_without_speculative():
  558. self._check_late_layer_tail_readers(forward_batch)
  559. attn_backend = v4.get_attn_backend()
  560. tail = attn_backend.tail_forward_metadata.late_layer_tail
  561. in_region = True
  562. R = hidden_states[p.lo:p.hi] if p.sharded else hidden_states
  563. ids_rows = None
  564. saved_full = None
  565. prev_pre = None
  566. precomputed_attn = None
  567. _ctx.plan = p
  568. try:
  569. for i in range(self.start_layer, self.end_layer):
  570. if tail is not None and i == self.late_layer_start:
  571. saved_full = attn_backend.enter_late_layer_tail(forward_batch)
  572. _ctx.plan = None
  573. in_region = False
  574. if p.sharded:
  575. R_sh, pre_sh = R, prev_pre
  576. R, prev_pre = _tail_rows(p, tail, R), _tail_rows(p, tail, prev_pre)
  577. if _ctx.dbg is not None and _ctx.dbg.compare:
  578. _cmp("tail rows R", R, tail.rows(_gather_rows(p, R_sh)))
  579. _cmp("tail rows pre", prev_pre, tail.rows(_gather_rows(p, pre_sh)))
  580. del R_sh, pre_sh
  581. else:
  582. R, prev_pre = tail.rows(R), tail.rows(prev_pre)
  583. input_ids, input_ids_global = tail.rows(input_ids), tail.rows(input_ids_global)
  584. positions = tail.positions
  585. if hash_ids is not None:
  586. hash_ids = tail.rows(hash_ids)
  587. engram = self.layers[i].engram
  588. if engram is not None:
  589. precomputed_attn = None
  590. before_engram = R
  591. ids = hash_ids[:, engram.layer_hash_index]
  592. if in_region:
  593. R = _sp_engram(p, engram, R, ids, forward_batch)
  594. else:
  595. R = engram(R, ids, forward_batch, cp_all_tokens=False)
  596. if self.config.model_type == "deepseek_v41" and self.config.vision_n_layers > 0:
  597. if in_region and p.sharded:
  598. if ids_rows is None:
  599. ids_rows = input_ids[p.lo:p.hi]
  600. vis = ids_rows
  601. else:
  602. vis = input_ids
  603. R = torch.where((vis == self.config.image_token_id)[:, None, None],
  604. before_engram, R)
  605. if capture_dspark and i in self.dspark_layers_to_capture:
  606. aux = R
  607. if in_region and p.sharded:
  608. aux = _tail_rows(p, tail, R) if tail is not None else _gather_rows(p, R)
  609. elif tail is not None and i < self.late_layer_start:
  610. aux = tail.rows(aux)
  611. dspark_aux_hidden_states.append(aux.mean(dim=1))
  612. ctx = (
  613. nullcontext()
  614. if v4.check_cuda_graph_backend(v4.Phase.PREFILL, v4.Backend.TC_PIECEWISE)
  615. else v4.get_global_expert_distribution_recorder().with_current_layer(i)
  616. )
  617. # next_norm / next_input only serve decode and verify (<= 8 rows): never here.
  618. next_input = []
  619. with ctx:
  620. R, prev_pre = self.layers[i].forward_hc_pre_from_prev(
  621. positions=positions,
  622. hidden_states=R,
  623. input_ids=input_ids,
  624. forward_batch=forward_batch,
  625. input_ids_global=input_ids_global,
  626. prev_pre=prev_pre,
  627. precomputed_attn=precomputed_attn,
  628. next_norm=None,
  629. next_input=next_input,
  630. )
  631. precomputed_attn = next_input[0] if next_input else None
  632. finally:
  633. _ctx.plan = None
  634. if saved_full is not None:
  635. attn_backend.exit_late_layer_tail(saved_full, forward_batch)
  636. return R, prev_pre, tail
  637. if p.sharded:
  638. R, prev_pre = _gather_rows(p, R), _gather_rows(p, prev_pre)
  639. return R, prev_pre, None
  640. # ------------------------------------------------------------------------------------------
  641. # stage 2: MXFP8 attention-input gather
  642. # ------------------------------------------------------------------------------------------
  643. _FP8_M = {} # M -> (ok, scale shape, scale dtype) agreed over TP
  644. def fp8_eligible_attn(attn):
  645. """Attention whose prefill path reads x only through wqkv_a (plus shape / dtype)."""
  646. v = getattr(attn, "_dsv41_sp_fp8", None)
  647. if v is None:
  648. try:
  649. v = bool(getattr(attn, "compressor", None) is None
  650. and getattr(attn, "indexer", None) is None
  651. and getattr(attn, "fuse_wqa_wkv", False)
  652. and getattr(attn, "wqkv_a", None) is not None
  653. and attn.accepts_mxfp8_swizzled_input())
  654. except Exception: # noqa: BLE001
  655. v = False
  656. attn._dsv41_sp_fp8 = v
  657. return v
  658. def _mxfp8_quantize(x, swizzled):
  659. from sglang.srt.layers.quantization.fp8_utils import flashinfer_mxfp8_quantize
  660. return flashinfer_mxfp8_quantize(x, is_sf_swizzled_layout=swizzled, alignment=32)
  661. def swizzle_128x4(s_lin):
  662. """[M, G] linear ue8m0 scales -> the 128x4 tiled layout (rows padded to 128 with zeros):
  663. tile (m // 128, g // 4) in row-tile-major order, inside a tile [m % 32][(m % 128) // 32][g % 4]."""
  664. m, g = s_lin.shape
  665. assert g % 4 == 0
  666. mt = (m + 127) // 128
  667. if mt * 128 != m:
  668. s_lin = torch.cat([s_lin, s_lin.new_zeros((mt * 128 - m, g))])
  669. return s_lin.view(mt, 4, 32, g // 4, 4).permute(0, 3, 2, 1, 4).contiguous().view(-1)
  670. def _mxfp8_input_cls():
  671. from sglang.srt.layers.quantization.mxfp8_input import Mxfp8SwizzledInput
  672. return Mxfp8SwizzledInput
  673. def mxfp8_gather(p, x):
  674. """This rank's bf16 shard [S, K] -> the whole chunk's (fp8 [M, K], linear scales [M, K/32])."""
  675. s_rows, k = x.shape
  676. q, sf = _mxfp8_quantize(x.contiguous(), False)
  677. sf = sf.view(torch.uint8).reshape(-1)[: s_rows * (k // 32)].view(s_rows, k // 32)
  678. qg = _gather_rows(p, q.view(torch.uint8)).view(q.dtype)
  679. sg = _gather_rows(p, sf)
  680. return qg, sg
  681. def _fp8_input(p, attn, x):
  682. """Pre-quantized attention input for this chunk, or None (bf16 gather) with the bf16 rows
  683. when the first-chunk check had to gather them anyway."""
  684. state = _FP8_M.get(p.rows)
  685. if state is not None and not state[0]:
  686. return None, None
  687. qg, sg = mxfp8_gather(p, x)
  688. sw = swizzle_128x4(sg)
  689. if state is None:
  690. xb = _gather_rows(p, x)
  691. rq, rs = _mxfp8_quantize(xb, True)
  692. rs_u8 = rs.view(torch.uint8).reshape(-1)
  693. local = bool(rq.shape == qg.shape and torch.equal(rq.view(torch.uint8), qg.view(torch.uint8))
  694. and rs_u8.numel() == sw.numel() and torch.equal(rs_u8, sw))
  695. if local:
  696. xq = _mxfp8_input_cls()(qg, sw.view(rs.dtype).view(rs.shape))
  697. with _Inner():
  698. local = bool(torch.equal(attn.wqkv_a(xb)[0], attn.wqkv_a(xq)[0]))
  699. ok = _agree_min(p, local)
  700. _FP8_M[p.rows] = state = (ok, tuple(rs.shape), rs.dtype)
  701. if p.rank == 0 and (len(_FP8_M) <= 8 or not ok):
  702. print(f"DSV41_PREFILL_SP fp8 attention input (M={p.rows}): MXFP8 bytes + scales and wqkv_a "
  703. f"output {'equal the stock bf16 path on every rank -> ON' if ok else 'differ -> bf16 gather'}"
  704. f" (rank 0 local {local})", flush=True)
  705. if not ok:
  706. return None, xb
  707. return _mxfp8_input_cls()(qg, sw.view(state[2]).view(state[1])), None
  708. # ------------------------------------------------------------------------------------------
  709. # attention / MoE: gather in, reduce-scatter out
  710. # ------------------------------------------------------------------------------------------
  711. def _hook_wo_b(attn):
  712. wo_b = attn.wo_b
  713. if getattr(wo_b, "_dsv41_sp_hooked", False):
  714. return
  715. orig = wo_b.forward
  716. def forward(input_, skip_all_reduce=False, *a, **kw):
  717. if _ctx.skip_wo_b is not wo_b:
  718. return orig(input_, skip_all_reduce, *a, **kw)
  719. out = orig(input_, True, *a, **kw)
  720. if _comparing():
  721. # stock: the same GEMM with the engine's own all-reduce, vs our all-reduce of the partial
  722. ref = orig(input_, False, *a, **kw)[0]
  723. ours = _ctx.plan.group.all_reduce(out[0].clone())
  724. _cmp("wo_b all-reduce", ours, ref)
  725. return out
  726. wo_b.forward = forward
  727. wo_b._dsv41_sp_hooked = True
  728. def _make_attn_forward(orig):
  729. def forward(self, x, positions, forward_batch, x_quant=None):
  730. p = _ctx.plan
  731. if (p is None or _ctx.inner or x_quant is not None or not isinstance(x, torch.Tensor)
  732. or x.dim() != 2 or x.shape[0] != p.in_rows):
  733. if _ctx.dbg is None or _ctx.inner:
  734. return orig(self, x, positions, forward_batch, x_quant)
  735. _rec("ain", x, True)
  736. o = orig(self, x, positions, forward_batch, x_quant)
  737. _rec("aout", o, True)
  738. return o
  739. _hook_wo_b(self)
  740. xq = xb = None
  741. if p.sharded and FP8 and fp8_eligible_attn(self):
  742. xq, xb = _fp8_input(p, self, x)
  743. if xq is not None:
  744. # shape / dtype only: this layer's attention reads x solely through wqkv_a(x_quant).
  745. # NaN, so any other read would poison the output instead of passing silently.
  746. xf = x.new_full((1, 1), float("nan")).expand(p.rows, x.shape[1])
  747. if _ctx.dbg is not None:
  748. xb = _gather_rows(p, x)
  749. _rec("ain", xb, True)
  750. if _comparing():
  751. rq, rs = _mxfp8_quantize(xb, True)
  752. _cmp("attn x mxfp8 data", xq[0].view(torch.uint8), rq.view(torch.uint8))
  753. _cmp("attn x mxfp8 scales", xq[1].view(torch.uint8).reshape(-1),
  754. rs.view(torch.uint8).reshape(-1))
  755. with _Inner():
  756. _cmp("attn wqkv_a", self.wqkv_a(xq)[0], self.wqkv_a(xb)[0])
  757. del rq, rs
  758. del xb
  759. else:
  760. xf = xb if xb is not None else (_gather_rows(p, x) if p.sharded else x)
  761. if _ctx.dbg is not None:
  762. _rec("ain", xf, True)
  763. _ctx.inner += 1
  764. _ctx.skip_wo_b = self.wo_b
  765. try:
  766. o = orig(self, xf, positions, forward_batch, xq)
  767. finally:
  768. _ctx.inner -= 1
  769. _ctx.skip_wo_b = None
  770. if o.shape[0] != p.rows:
  771. raise RuntimeError(f"DSV41_PREFILL_SP: attention returned {tuple(o.shape)} for {p.rows} rows")
  772. out = _reduce_out(p, o)
  773. if _ctx.dbg is not None:
  774. _rec("aout", out)
  775. return out
  776. forward.__wrapped__ = orig
  777. return forward
  778. def _make_moe_forward(orig):
  779. def forward(self, hidden_states, *args, **kwargs):
  780. p = _ctx.plan
  781. if (p is None or _ctx.inner or not isinstance(hidden_states, torch.Tensor)
  782. or hidden_states.dim() != 2 or hidden_states.shape[0] != p.in_rows
  783. or kwargs.get("skip_shared_experts", False)):
  784. if _ctx.dbg is None or _ctx.inner or not isinstance(hidden_states, torch.Tensor):
  785. return orig(self, hidden_states, *args, **kwargs)
  786. _rec("min", hidden_states, True)
  787. o = orig(self, hidden_states, *args, **kwargs)
  788. _rec("mout", o, True)
  789. return o
  790. v4 = _M["v4"]
  791. xf = _gather_rows(p, hidden_states) if p.sharded else hidden_states
  792. if _mega_moe(self, xf):
  793. raise RuntimeError("DSV41_PREFILL_SP: mega MoE path is not wired")
  794. if _ctx.dbg is not None:
  795. _rec("min", xf, True)
  796. x_ref = xf.clone() if _comparing() else None # the MoE may write its input in place
  797. _ctx.inner += 1
  798. try:
  799. with v4.get_forward().scoped(mlp_reduce_scatter=True):
  800. out = orig(self, xf, *args, **kwargs)
  801. finally:
  802. _ctx.inner -= 1
  803. if out.shape[0] != p.rows:
  804. raise RuntimeError(f"DSV41_PREFILL_SP: MoE returned {tuple(out.shape)} for {p.rows} rows")
  805. if x_ref is not None:
  806. # stock: the same MoE on the same rows with its own post-experts all-reduce
  807. ours = p.group.all_reduce(out.clone())
  808. with _Inner():
  809. ref = orig(self, x_ref, *args, **kwargs)
  810. _cmp("moe all-reduce", ours, ref)
  811. del x_ref, ref, ours
  812. res = _reduce_out(p, out)
  813. if _ctx.dbg is not None:
  814. _rec("mout", res)
  815. return res
  816. forward.__wrapped__ = orig
  817. return forward
  818. # ------------------------------------------------------------------------------------------
  819. # hc gates keyed on the logical row count (stage 1 only)
  820. # ------------------------------------------------------------------------------------------
  821. def _combine_norm_fused(layer, x, apply_pre, norm, m):
  822. """The engine's _hc_combine fused-norm gate, evaluated at row count m."""
  823. v4 = _M["v4"]
  824. from sglang.srt.batch_invariant_ops import is_batch_invariant_mode_enabled
  825. return bool(
  826. x.is_cuda
  827. and v4.get_platform().is_blackwell
  828. and (0 < m <= 8 or (layer.config.model_type == "deepseek_v41" and 4096 <= m <= 65536))
  829. and layer.hc_mult == 4
  830. and x.flatten(1).shape[1] == 20480
  831. and x.dtype == norm.weight.dtype == torch.bfloat16
  832. and apply_pre.stride(1) == 1
  833. and not norm.cast_x_before_out_mul
  834. and norm.variance_size_override is None
  835. and not is_batch_invariant_mode_enabled()
  836. )
  837. def hc_combine_norm_rows(x_flat, pre, weight, eps, logical_m):
  838. """hc_combine_norm on a row slice with the kernel the full chunk (logical_m rows) uses."""
  839. hcn = _M["hcn"]
  840. m = x_flat.shape[0]
  841. assert 4096 <= logical_m <= 65536 and x_flat.shape == (m, 20480)
  842. assert pre.shape == (m, 4) and pre.stride(1) == 1
  843. assert weight.shape == (5120,) and weight.is_contiguous()
  844. assert x_flat.dtype == weight.dtype == torch.bfloat16 and x_flat.stride(1) == 1
  845. y = torch.empty((m, 5120), dtype=x_flat.dtype, device=x_flat.device)
  846. hcn._hc_combine_norm_prefill[(m,)](
  847. x_flat, pre, weight, y, x_flat.stride(0), pre.stride(0), eps, num_warps=4
  848. )
  849. return y
  850. def _make_hc_combine(orig):
  851. def _hc_combine(self, x, apply_pre, norm, stats_stream=None, quantized=None, normalized=None,
  852. precomputed=None):
  853. p = _ctx.plan
  854. if (p is None or _ctx.inner or not p.sharded or x.shape[0] != p.shard
  855. or precomputed is not None or normalized is not None or stats_stream is not None):
  856. return orig(self, x, apply_pre, norm, stats_stream, quantized, normalized, precomputed)
  857. y = _sp_combine(self, orig, p, x, apply_pre, norm, quantized)
  858. if _comparing():
  859. with _Inner():
  860. xg = _gather_rows(p, x)
  861. pg = _gather_rows(p, apply_pre) if apply_pre is not None else None
  862. ref = orig(self, xg, pg, norm, None, None, None, None)[p.lo:p.hi].clone()
  863. del xg, pg
  864. which = "attn" if norm is getattr(self, "input_layernorm", None) else "ffn"
  865. _cmp(f"hc_combine {which}{'' if apply_pre is not None else ' (stream 0)'}", y, ref,
  866. {"x": x, "pre": apply_pre})
  867. return y
  868. def _sp_combine(self, orig, p, x, apply_pre, norm, quantized):
  869. if apply_pre is None:
  870. return orig(self, x, apply_pre, norm, None, quantized, None, None)
  871. m = p.rows
  872. full = _combine_norm_fused(self, x, apply_pre, norm, m)
  873. here = _combine_norm_fused(self, x, apply_pre, norm, x.shape[0])
  874. if full == here:
  875. return orig(self, x, apply_pre, norm, None, quantized, None, None)
  876. x_flat = x.flatten(1)
  877. if full:
  878. return hc_combine_norm_rows(x_flat, apply_pre, norm.weight, norm.variance_epsilon, m)
  879. return norm(_M["mhc"].hc_combine(x_flat, apply_pre, self.hc_mult, x.dtype))
  880. _hc_combine.__wrapped__ = orig
  881. return _hc_combine
  882. def _post_split_h(layer, x, residual, post, comb, m):
  883. v4 = _M["v4"]
  884. return bool(
  885. v4._is_cuda
  886. and v4.get_platform().is_blackwell
  887. and layer.hc_pre_from_prev_sublayer
  888. and layer.hc_mult == 4
  889. and x.shape[1] == 5120
  890. and m <= 384
  891. and x.dtype == residual.dtype == torch.bfloat16
  892. and post.dtype == comb.dtype == torch.float32
  893. and all(t.is_contiguous() for t in (x, residual, post, comb))
  894. )
  895. def _make_hc_post(orig):
  896. def hc_post(self, x, residual, post, comb):
  897. p = _ctx.plan
  898. if p is None or _ctx.inner or not p.sharded or x.shape[0] != p.shard:
  899. return orig(self, x, residual, post, comb)
  900. out = _sp_post(self, x, residual, post, comb, p)
  901. if _comparing():
  902. with _Inner():
  903. ref = orig(self, _gather_rows(p, x), _gather_rows(p, residual), _gather_rows(p, post),
  904. _gather_rows(p, comb))[p.lo:p.hi].clone()
  905. _cmp("hc_post", out, ref, {"x": x, "post": post, "comb": comb})
  906. return out
  907. def _sp_post(self, x, residual, post, comb, p):
  908. v4 = _M["v4"]
  909. full = _post_split_h(self, x, residual, post, comb, p.rows)
  910. if full == _post_split_h(self, x, residual, post, comb, x.shape[0]):
  911. return orig(self, x, residual, post, comb)
  912. if full:
  913. return v4.mhc_post_split_h(x, residual, post, comb)
  914. # the full chunk skips split-h: the rest of the engine's CUDA chain
  915. if v4.envs.SGLANG_OPT_USE_FLASHINFER_MHC.get():
  916. from flashinfer.mhc import mhc_post
  917. return mhc_post(x, residual, post, comb)
  918. if v4.envs.SGLANG_OPT_USE_TILELANG_MHC_POST.get():
  919. if v4.get_platform().is_sm90 and 1 <= p.rows <= 64:
  920. return v4.mhc_post_split_h(x, residual, post, comb)
  921. return _M["mhc"].mhc_post(x, residual, post, comb)
  922. raise RuntimeError("DSV41_PREFILL_SP: hc_post fallback chain not wired for this platform")
  923. hc_post.__wrapped__ = orig
  924. return hc_post
  925. def _make_hc_mix_stats(orig):
  926. """Debug compare only: the stats on the shard vs the stock stats on the gathered rows."""
  927. def _hc_mix_stats(self, x, hc_fn, hc_scale, hc_base, stats_stream=None):
  928. out = orig(self, x, hc_fn, hc_scale, hc_base, stats_stream)
  929. p = _ctx.plan
  930. if (_comparing() and not _ctx.inner and stats_stream is None and x.shape[0] == p.shard
  931. and p.shard != p.rows):
  932. with _Inner():
  933. ref = orig(self, _gather_rows(p, x), hc_fn, hc_scale, hc_base, None)
  934. which = "attn" if hc_fn is getattr(self, "hc_attn_fn", None) else "ffn"
  935. _cmp(f"hc_mix_stats {which}", tuple(out), tuple(r[p.lo:p.hi].clone() for r in ref))
  936. return out
  937. _hc_mix_stats.__wrapped__ = orig
  938. return _hc_mix_stats
  939. # ------------------------------------------------------------------------------------------
  940. # install
  941. # ------------------------------------------------------------------------------------------
  942. def install(model_mod):
  943. """sglang.srt.models.deepseek_v4. Must run BEFORE hc_prefill_fused.install, so that its
  944. 'stock' _hc_combine / hc_post are these logical-row-aware ones."""
  945. if MODE == "off" and not DEBUG:
  946. return
  947. if getattr(model_mod, "_dsv41_prefill_sp", False):
  948. return
  949. _load_modules(model_mod)
  950. check_engine()
  951. if MODE == "shard":
  952. _check_hc_prefill_fused()
  953. Model, Layer = model_mod.DeepseekV4Model, model_mod.DeepseekV4DecoderLayer
  954. MoE = _M["v2"].DeepseekV2MoE
  955. stock_layers = Model._forward_layers_hc_pre_from_prev
  956. def _forward_layers_hc_pre_from_prev(self, positions, hidden_states, forward_batch, input_ids,
  957. input_ids_global, capture_dspark, dspark_aux_hidden_states):
  958. p = _plan(self, hidden_states, forward_batch)
  959. d = _debug_begin(self, hidden_states, forward_batch, input_ids, p) if DEBUG else None
  960. _ctx.dbg = d
  961. try:
  962. if p is None:
  963. out = stock_layers(self, positions, hidden_states, forward_batch, input_ids,
  964. input_ids_global, capture_dspark, dspark_aux_hidden_states)
  965. else:
  966. out = _sp_forward_layers(self, p, positions, hidden_states, forward_batch, input_ids,
  967. input_ids_global, capture_dspark, dspark_aux_hidden_states)
  968. finally:
  969. _ctx.dbg = None
  970. if d is not None:
  971. _debug_end(d, out)
  972. return out
  973. _forward_layers_hc_pre_from_prev.__wrapped__ = stock_layers
  974. Model._forward_layers_hc_pre_from_prev = _forward_layers_hc_pre_from_prev
  975. model_mod.MQALayer.forward = _make_attn_forward(model_mod.MQALayer.forward)
  976. MoE.forward = _make_moe_forward(MoE.forward)
  977. if MODE == "shard":
  978. Layer._hc_combine = _make_hc_combine(Layer._hc_combine)
  979. Layer.hc_post = _make_hc_post(Layer.hc_post)
  980. if "compare" in DEBUG:
  981. Layer._hc_mix_stats = _make_hc_mix_stats(Layer._hc_mix_stats)
  982. if DEBUG:
  983. Layer.forward_hc_pre_from_prev = _make_layer_forward(Layer.forward_hc_pre_from_prev)
  984. print(f"DSV41_PREFILL_SP DEBUG {sorted(DEBUG)} ARMED for the first {DEBUG_CHUNKS} extend "
  985. f"forwards{' (dumps in ' + DEBUG_DUMP + ')' if DEBUG_DUMP else ''}: NOT FOR PRODUCTION",
  986. flush=True)
  987. model_mod._dsv41_prefill_sp = True
  988. if MODE == "off":
  989. return
  990. what = ("reduce-scatter + all-gather at wo_b / MoE / Engram (comm only, nothing sharded)"
  991. if MODE == "comm" else
  992. f"rows sharded over TP on the full-row layers, bf16 gathers"
  993. f"{', EXACT (all-reduce + keep the quarter)' if EXACT else ''}"
  994. f"{', MXFP8 attention-input gathers (checked per chunk size)' if FP8 else ''}"
  995. f", wkv {'on the shard (self-checked)' if WKV_SHARD else 'full-M'}")
  996. print(f"DSV41_PREFILL_SP={_RAW} ARMED: prefill chunks >= {MIN_ROWS} rows: {what}", flush=True)
  997. if EXACT and MODE == "comm":
  998. print("DSV41_PREFILL_SP: DSV41_PREFILL_SP_EXACT has no effect in comm mode", flush=True)