mimo26-dflash-adaptive.diff 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. diff --git a/python/sglang/srt/layers/attention/flashattention_backend.py b/python/sglang/srt/layers/attention/flashattention_backend.py
  2. index ee187bade0..3c3e69d841 100644
  3. --- a/python/sglang/srt/layers/attention/flashattention_backend.py
  4. +++ b/python/sglang/srt/layers/attention/flashattention_backend.py
  5. @@ -3744,3 +3744,85 @@ def cdiv(a: int, b: int) -> int:
  6. @torch._dynamo.disable()
  7. def merge_state_v2_wrapper(o, s_a, o_exp, s_b):
  8. return merge_state_v2(o, s_a, o_exp, s_b)
  9. +
  10. +
  11. +# mimo26: adaptive DFLASH verify width. A second, narrower target-verify graph
  12. +# runner shares this backend; it must not reallocate the full-width runner's
  13. +# graph buffers, and narrow verifies need their own metadata (static
  14. +# cu_seqlens_q stride, per-bs metadata objects) and width.
  15. +import contextlib as _mimo26_contextlib
  16. +
  17. +
  18. +@_mimo26_contextlib.contextmanager
  19. +def _mimo26_verify_width(self, forward_batch):
  20. + spec_info = getattr(forward_batch, "spec_info", None)
  21. + width = getattr(spec_info, "num_tokens_per_req", -1) if spec_info is not None else -1
  22. + full = getattr(self, "_mimo26_full_width", None) or self.speculative_num_draft_tokens
  23. + if (
  24. + not forward_batch.forward_mode.is_target_verify()
  25. + or self.topk > 1
  26. + or width is None
  27. + or width <= 0
  28. + or not full
  29. + or width == full
  30. + ):
  31. + yield
  32. + return
  33. + saved_width = self.speculative_num_draft_tokens
  34. + saved_meta = self.target_verify_metadata
  35. + base = saved_meta
  36. + if isinstance(base, dict) and "cache_seqlens" in base:
  37. + cache = self.__dict__.setdefault("_mimo26_tv_meta", {})
  38. + meta = cache.get(width)
  39. + if meta is None:
  40. + max_bs = base["cache_seqlens"].shape[0]
  41. + meta = {
  42. + k: torch.zeros_like(v)
  43. + for k, v in base.items()
  44. + if isinstance(k, str) and k not in ("cu_seqlens_q", "strided_indices")
  45. + }
  46. + meta["strided_indices"] = base["strided_indices"]
  47. + meta["cu_seqlens_q"] = torch.arange(
  48. + 0, max_bs * width + 1, step=width, dtype=torch.int32, device=self.device
  49. + )
  50. + cache[width] = meta
  51. + self.target_verify_metadata = meta
  52. + self.speculative_num_draft_tokens = width
  53. + try:
  54. + yield
  55. + finally:
  56. + self.speculative_num_draft_tokens = saved_width
  57. + self.target_verify_metadata = saved_meta
  58. +
  59. +
  60. +def _mimo26_wrap_metadata(name):
  61. + orig = getattr(FlashAttentionBackend, name)
  62. +
  63. + def wrapper(self, forward_batch, *args, **kwargs):
  64. + with _mimo26_verify_width(self, forward_batch):
  65. + return orig(self, forward_batch, *args, **kwargs)
  66. +
  67. + wrapper.__name__ = name
  68. + wrapper.__doc__ = orig.__doc__
  69. + return wrapper
  70. +
  71. +
  72. +def _mimo26_wrap_graph_state():
  73. + orig = FlashAttentionBackend.init_cuda_graph_state
  74. +
  75. + def init_cuda_graph_state(self, max_bs: int, max_num_tokens: int):
  76. + prev = getattr(self, "_mimo26_graph_state_dims", None)
  77. + if prev is not None and prev[0] >= max_bs and prev[1] >= max_num_tokens:
  78. + return
  79. + orig(self, max_bs, max_num_tokens)
  80. + self._mimo26_graph_state_dims = (max_bs, max_num_tokens)
  81. + self._mimo26_full_width = self.speculative_num_draft_tokens
  82. + self._mimo26_tv_meta = {}
  83. +
  84. + init_cuda_graph_state.__doc__ = orig.__doc__
  85. + return init_cuda_graph_state
  86. +
  87. +
  88. +for _mimo26_name in ("init_forward_metadata", "init_forward_metadata_out_graph"):
  89. + setattr(FlashAttentionBackend, _mimo26_name, _mimo26_wrap_metadata(_mimo26_name))
  90. +FlashAttentionBackend.init_cuda_graph_state = _mimo26_wrap_graph_state()
  91. diff --git a/python/sglang/srt/layers/attention/triton_backend.py b/python/sglang/srt/layers/attention/triton_backend.py
  92. index 979828200d..b037bc7f63 100644
  93. --- a/python/sglang/srt/layers/attention/triton_backend.py
  94. +++ b/python/sglang/srt/layers/attention/triton_backend.py
  95. @@ -1054,6 +1054,18 @@ class TritonAttnBackend(AttentionBackend):
  96. kv_indices_buf: Optional[torch.Tensor] = None,
  97. cuda_graph_num_kv_splits_buf: Optional[torch.Tensor] = None,
  98. ):
  99. + # mimo26: a second (narrower) target-verify runner reuses the buffers the
  100. + # first runner's graphs already reference instead of reallocating them.
  101. + prev = getattr(self, "_mimo26_graph_state_dims", None)
  102. + if (
  103. + prev is not None
  104. + and prev[0] >= max_bs
  105. + and prev[1] >= max_num_tokens
  106. + and kv_indices_buf is None
  107. + and cuda_graph_num_kv_splits_buf is None
  108. + ):
  109. + return
  110. + self._mimo26_graph_state_dims = (max_bs, max_num_tokens)
  111. self.cuda_graph_attn_logits = torch.zeros(
  112. (max_num_tokens, self.num_head, self.max_kv_splits, self.v_head_dim),
  113. dtype=torch.float32,
  114. diff --git a/python/sglang/srt/model_executor/model_runner.py b/python/sglang/srt/model_executor/model_runner.py
  115. index 423b5b1a6b..4780938911 100644
  116. --- a/python/sglang/srt/model_executor/model_runner.py
  117. +++ b/python/sglang/srt/model_executor/model_runner.py
  118. @@ -1846,10 +1846,16 @@ class ModelRunner:
  119. if self.device == "cpu"
  120. else forward_batch.forward_mode.is_cuda_graph
  121. )
  122. + graph_runner = self.decode_cuda_graph_runner
  123. + alt_runners = getattr(self, "mimo26_alt_verify_runners", None)
  124. + if alt_runners and forward_batch.forward_mode.is_target_verify():
  125. + # mimo26: adaptive DFLASH verify width selects its own captured graphs.
  126. + width = getattr(forward_batch.spec_info, "num_tokens_per_req", -1)
  127. + graph_runner = alt_runners.get(width, graph_runner)
  128. can_run_graph = bool(
  129. mode_check()
  130. - and self.decode_cuda_graph_runner
  131. - and self.decode_cuda_graph_runner.can_run_graph(forward_batch)
  132. + and graph_runner
  133. + and graph_runner.can_run_graph(forward_batch)
  134. )
  135. if (
  136. @@ -1862,7 +1868,7 @@ class ModelRunner:
  137. # Replay cuda graph if applicable
  138. if can_run_graph:
  139. - ret = self.decode_cuda_graph_runner.execute(
  140. + ret = graph_runner.execute(
  141. forward_batch,
  142. pp_proxy_tensors=pp_proxy_tensors,
  143. )
  144. diff --git a/python/sglang/srt/speculative/dflash_worker_v2.py b/python/sglang/srt/speculative/dflash_worker_v2.py
  145. index bb757c07be..62d213e2fe 100644
  146. --- a/python/sglang/srt/speculative/dflash_worker_v2.py
  147. +++ b/python/sglang/srt/speculative/dflash_worker_v2.py
  148. @@ -672,6 +672,67 @@ class DFlashWorkerV2(BaseSpecWorker):
  149. self._draft_worker.init_cuda_graphs(
  150. capture_decode_cuda_graph=capture_decode_cuda_graph
  151. )
  152. + self._mimo26_setup_adaptive()
  153. +
  154. + def _mimo26_setup_adaptive(self):
  155. + self._mimo26_widths = []
  156. + self._mimo26_full_block = int(self.block_size)
  157. + self._mimo26_narrow_active = False
  158. + if os.environ.get("MIMO26_DFLASH_ADAPTIVE", "0") != "1":
  159. + return
  160. + small = int(os.environ.get("MIMO26_DFLASH_SMALL_WIDTH", "4"))
  161. + self._mimo26_lo = float(os.environ.get("MIMO26_DFLASH_LO", "1.8"))
  162. + self._mimo26_hi = float(os.environ.get("MIMO26_DFLASH_HI", "2.6"))
  163. + self._mimo26_alpha = float(os.environ.get("MIMO26_DFLASH_ALPHA", "0.3"))
  164. + full = self._mimo26_full_block
  165. + tmr = self.target_worker.model_runner
  166. + if not (1 < small < full) or tmr.decode_cuda_graph_runner is None:
  167. + logger.warning("mimo26: adaptive DFLASH verify width disabled (small=%d full=%d)", small, full)
  168. + return
  169. + original = tmr.decode_num_tokens_per_req
  170. + tmr.decode_num_tokens_per_req = lambda **kwargs: small
  171. + try:
  172. + runner = tmr._decode_cuda_graph_runner_cls()(tmr)
  173. + finally:
  174. + tmr.decode_num_tokens_per_req = original
  175. + tmr.mimo26_alt_verify_runners = {small: runner}
  176. + self._mimo26_widths = [small]
  177. + logger.info(
  178. + "mimo26: adaptive DFLASH verify width %d/%d (lo=%.2f hi=%.2f alpha=%.2f)",
  179. + small, full, self._mimo26_lo, self._mimo26_hi, self._mimo26_alpha,
  180. + )
  181. +
  182. + def _mimo26_choose_width(self, batch) -> int:
  183. + """Full block if any request is accepting well, else the narrow width.
  184. +
  185. + Uses the scheduler's per-request counters (identical on every TP rank, one
  186. + step behind under overlap scheduling): an EMA of correct drafts per verify,
  187. + with hysteresis between MIMO26_DFLASH_LO and MIMO26_DFLASH_HI.
  188. + """
  189. + full = self._mimo26_full_block
  190. + if getattr(self, "_selector_sample", None) is not None:
  191. + return full
  192. + use_full = False
  193. + for req in batch.reqs:
  194. + state = getattr(req, "_mimo26_adaptive", None)
  195. + verify_ct = int(getattr(req, "spec_verify_ct", 0))
  196. + correct = int(getattr(req, "spec_num_correct_drafts", 0))
  197. + if state is None:
  198. + state = [verify_ct, correct, None, True]
  199. + req._mimo26_adaptive = state
  200. + steps = verify_ct - state[0]
  201. + if steps > 0:
  202. + rate = (correct - state[1]) / steps
  203. + ema = state[2]
  204. + state[2] = rate if ema is None else (1.0 - self._mimo26_alpha) * ema + self._mimo26_alpha * rate
  205. + state[0], state[1] = verify_ct, correct
  206. + if state[2] is not None:
  207. + if state[3] and state[2] < self._mimo26_lo:
  208. + state[3] = False
  209. + elif not state[3] and state[2] >= self._mimo26_hi:
  210. + state[3] = True
  211. + use_full = use_full or state[3]
  212. + return full if use_full else self._mimo26_widths[0]
  213. def _prewarm_batch_size(self, block_size: int) -> int:
  214. """Largest batch the non-greedy verify path can see in one step."""
  215. @@ -2234,6 +2295,16 @@ class DFlashWorkerV2(BaseSpecWorker):
  216. pp_proxy_tensors=None,
  217. ) -> GenerationBatchResult:
  218. self._validate_phase1_sampling_support(batch)
  219. + if getattr(self, "_mimo26_narrow_active", False):
  220. + # mimo26: undo the previous step's narrow verify width.
  221. + self.block_size = self._mimo26_full_block
  222. + self._use_triton_accept_bonus = self._mimo26_saved_triton
  223. + self._mimo26_narrow_active = False
  224. + if getattr(self, "_mimo26_default_runner", None) is not None:
  225. + self.target_worker.model_runner.decode_cuda_graph_runner = (
  226. + self._mimo26_default_runner
  227. + )
  228. + self._mimo26_default_runner = None
  229. if batch.forward_mode.is_extend() or batch.is_extend_in_batch:
  230. # Target prefill: capture DFlash aux hidden states for prompt tokens.
  231. @@ -2633,6 +2704,29 @@ class DFlashWorkerV2(BaseSpecWorker):
  232. draft_tokens[:, 0].copy_(block_ids[:, 0])
  233. draft_tokens[:, 1:].copy_(draft_next)
  234. + # mimo26: adaptive verify width. The draft always proposes a full block;
  235. + # low-acceptance steps verify only the first k columns, which reads far
  236. + # fewer MoE expert bytes per step.
  237. + if getattr(self, "_mimo26_widths", None):
  238. + verify_width = self._mimo26_choose_width(batch)
  239. + if verify_width < block_size:
  240. + draft_tokens = draft_tokens[:, :verify_width].contiguous()
  241. + positions = positions_2d[:, :verify_width].reshape(-1)
  242. + verify_out_cache_loc_2d = verify_out_cache_loc_2d[:, :verify_width].contiguous()
  243. + verify_out_cache_loc = verify_out_cache_loc_2d.reshape(-1)
  244. + block_size = verify_width
  245. + self._mimo26_saved_triton = self._use_triton_accept_bonus
  246. + self._use_triton_accept_bonus = False
  247. + self.block_size = verify_width
  248. + self._mimo26_narrow_active = True
  249. + # Verify planning and replay both read decode_cuda_graph_runner;
  250. + # point it at the narrow graphs for this step only.
  251. + target_runner = self.target_worker.model_runner
  252. + self._mimo26_default_runner = target_runner.decode_cuda_graph_runner
  253. + target_runner.decode_cuda_graph_runner = (
  254. + target_runner.mimo26_alt_verify_runners[verify_width]
  255. + )
  256. +
  257. # Must stay ahead of the target verify launch below.
  258. grammar_tree = (
  259. GrammarTree.from_linear_chain(draft_tokens) if batch.has_grammar else None
  260. @@ -2697,6 +2791,11 @@ class DFlashWorkerV2(BaseSpecWorker):
  261. is_verify=True,
  262. skip_attn_backend_init=True if not _is_npu else None,
  263. )
  264. + if getattr(self, "_mimo26_default_runner", None) is not None:
  265. + self.target_worker.model_runner.decode_cuda_graph_runner = (
  266. + self._mimo26_default_runner
  267. + )
  268. + self._mimo26_default_runner = None
  269. logits_output = target_out.logits_output
  270. can_run_cuda_graph = target_out.can_run_cuda_graph
  271. diff --git a/python/sglang/srt/speculative/spec_info.py b/python/sglang/srt/speculative/spec_info.py
  272. index 1cb443feb8..c0ae84c1ae 100644
  273. --- a/python/sglang/srt/speculative/spec_info.py
  274. +++ b/python/sglang/srt/speculative/spec_info.py
  275. @@ -510,7 +510,12 @@ def create_dummy_verify_input(
  276. spec_info = DFlashVerifyInput(
  277. draft_token=None,
  278. positions=None,
  279. - draft_token_num=spec.speculative_num_draft_tokens,
  280. + # mimo26: a narrower target-verify runner captures with its own width.
  281. + draft_token_num=(
  282. + spec.speculative_num_draft_tokens
  283. + if is_draft_worker
  284. + else num_tokens_per_req
  285. + ),
  286. custom_mask=None,
  287. capture_hidden_mode=(
  288. CaptureHiddenMode.NULL if is_draft_worker else CaptureHiddenMode.FULL