sm120-return-lse.patch 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. --- a/vllm/v1/attention/backends/mla/flashinfer_mla_sparse_sm120.py
  2. +++ b/vllm/v1/attention/backends/mla/flashinfer_mla_sparse_sm120.py
  3. @@ -32,6 +32,13 @@
  4. class FlashInferMLASparseSM120Impl(SparseMLAAttentionImpl[FlashInferMLASparseMetadata]):
  5. """SM120 FlashInfer sparse-MLA implementation."""
  6. + # glm52-dcp patch: FlashInfer's trtllm_batch_decode_with_kv_cache_mla
  7. + # accepts return_lse (verified in flashinfer 0.6.14), so this impl CAN
  8. + # return the decode LSE that DCP's cross-rank merge requires. Upstream
  9. + # never plumbed it through; see forward_mqa below. Mirrors the parent
  10. + # FlashInferMLASparseImpl's LSE handling.
  11. + can_return_lse_for_decode = True
  12. +
  13. def __init__(
  14. self,
  15. num_heads: int,
  16. @@ -126,8 +133,12 @@
  17. ),
  18. )
  19. + # glm52-dcp patch: size the out buffer from the query's actual head
  20. + # count, not self.num_heads. Under DCP the MLA decode all-gathers
  21. + # queries across ranks, so each rank sees the FULL head count (64)
  22. + # rather than the TP-local slice (16) — self.num_heads is stale here.
  23. output = q.new_empty(
  24. - (num_actual_toks, self.num_heads, self.kv_lora_rank),
  25. + (num_actual_toks, q.shape[1], self.kv_lora_rank),
  26. dtype=q.dtype,
  27. )
  28. @@ -153,5 +164,27 @@
  29. bmm2_scale=1.0,
  30. sparse_mla_top_k=attn_metadata.topk_tokens,
  31. kv_scale_format=self.kv_scale_format,
  32. + # glm52-dcp patch: ask the kernel for the decode LSE when DCP
  33. + # needs it for the cross-rank merge (base class sets this flag
  34. + # only when dcp_world_size > 1).
  35. + return_lse=self.need_to_return_lse_for_decode,
  36. )
  37. + if self.need_to_return_lse_for_decode:
  38. + # With return_lse=True flashinfer returns (out, lse).
  39. + assert isinstance(out, tuple), (
  40. + "expected (out, lse) tuple from flashinfer when return_lse=True"
  41. + )
  42. + o, lse = out
  43. + o = o.view(-1, o.shape[-2], o.shape[-1])
  44. + # Reuse the parent impl's LSE normalization + empty-row masking
  45. + # conventions (the shared DCP reducer expects (num_tokens, num_heads)
  46. + # and -inf LSE on rows with no valid top-k indices).
  47. + from vllm.v1.attention.backends.mla.flashinfer_mla_sparse import (
  48. + FlashInferMLASparseImpl as _ParentImpl,
  49. + )
  50. + lse = _ParentImpl._normalize_lse(lse, o.shape[0], o.shape[1])
  51. + empty_rows = (topk_indices_physical == -1).all(dim=-1)
  52. + o.masked_fill_(empty_rows.view(-1, 1, 1), 0.0)
  53. + lse.masked_fill_(empty_rows.view(-1, 1), float("-inf"))
  54. + return o, lse
  55. return out.squeeze(1), None