patch_mla.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """NoPE-MLA rope pad (env-gated): make GLM-5.3-style qk_rope_head_dim=0 models
  2. ride the DeepSeek 512+64 fp8_ds_mla path on SM120/121 by (a) constructing the
  3. inner MLAAttention with rope=pe_pad so the KV-cache spec/kernels see head 576,
  4. and (b) zero-padding q and k_pe just before the attention call. Zeros are
  5. rotation-free and contribute exactly 0 to every logit — mathematically a no-op.
  6. Active only when VLLM_MLA_NOPE_PAD_ROPE=1 and the layer is genuinely NoPE."""
  7. import re, sys
  8. P = "/usr/local/lib/python3.12/dist-packages/vllm/model_executor/layers/mla.py"
  9. src = open(P).read()
  10. if "VLLM_MLA_NOPE_PAD_ROPE" in src:
  11. print("already patched"); sys.exit(0)
  12. a1 = " self.qk_head_dim = qk_nope_head_dim + qk_rope_head_dim\n"
  13. r1 = a1 + (
  14. " import os as _os\n"
  15. " self.pe_pad = 0\n"
  16. " if qk_rope_head_dim == 0 and _os.environ.get(\"VLLM_MLA_NOPE_PAD_ROPE\", \"0\") == \"1\":\n"
  17. " self.pe_pad = 64\n"
  18. )
  19. assert src.count(a1) == 1, f"anchor1 count {src.count(a1)}"
  20. src = src.replace(a1, r1)
  21. a2 = """ qk_nope_head_dim=self.qk_nope_head_dim,
  22. qk_rope_head_dim=self.qk_rope_head_dim,"""
  23. r2 = """ qk_nope_head_dim=self.qk_nope_head_dim,
  24. qk_rope_head_dim=self.qk_rope_head_dim + self.pe_pad,"""
  25. assert src.count(a2) == 1, f"anchor2 count {src.count(a2)}"
  26. src = src.replace(a2, r2)
  27. a3 = " attn_out = self.mla_attn(\n"
  28. r3 = (
  29. " if self.pe_pad:\n"
  30. " q = torch.nn.functional.pad(q, (0, self.pe_pad))\n"
  31. " k_pe = q.new_zeros((k_pe.shape[0], 1, self.pe_pad))\n"
  32. ) + a3
  33. assert src.count(a3) == 1, f"anchor3 count {src.count(a3)}"
  34. src = src.replace(a3, r3)
  35. open(P, "w").write(src)
  36. import py_compile; py_compile.compile(P, doraise=True)
  37. print("mla.py NoPE-pad patch applied + compiles")
  38. # ---- part 2: SM120 sparse decode must be told the REAL topk table width ----
  39. # glm5_next kpool indexer builds tables of width index_topk + kpool alignment
  40. # (2048+128=2176); the impl passed config topk (2048), tripping flashinfer
  41. # wrapper shape check. Pass the actual table width for both top_k and
  42. # max_seq_len (padding entries are -1 and skipped, same as short-seq padding).
  43. P2 = "/usr/local/lib/python3.12/dist-packages/vllm/v1/attention/backends/mla/flashinfer_mla_sparse_sm120.py"
  44. src2 = open(P2).read()
  45. if "topk_indices_physical.shape[-1]" in src2:
  46. print("sm120 width patch already applied")
  47. else:
  48. b1 = " max_seq_len=attn_metadata.topk_tokens,"
  49. n1 = " max_seq_len=topk_indices_physical.shape[-1],"
  50. b2 = " sparse_mla_top_k=attn_metadata.topk_tokens,"
  51. n2 = " sparse_mla_top_k=topk_indices_physical.shape[-1],"
  52. assert src2.count(b1) == 1, f"sm120 anchor1 count {src2.count(b1)}"
  53. assert src2.count(b2) == 1, f"sm120 anchor2 count {src2.count(b2)}"
  54. src2 = src2.replace(b1, n1).replace(b2, n2)
  55. open(P2, "w").write(src2)
  56. py_compile.compile(P2, doraise=True)
  57. print("sm120 topk width patch applied + compiles")
  58. # ---- part 3: allowlist the glm5_next topk width in the SM120 decode dispatch ----
  59. # glm5_next tables are index_topk + kpool tail rounded to BLOCK_N=128 => 2176.
  60. # The dsv3_2 decode binding derives topk and num_splits dynamically from
  61. # indices.shape[-1] (2176 = 34*64, tile-aligned); the frozenset is a
  62. # tested-shape allowlist, not a compiled-variant limit. Extend it.
  63. P3 = "/usr/local/lib/python3.12/dist-packages/flashinfer/mla/_sparse_mla_sm120.py"
  64. src3 = open(P3).read()
  65. if "(8, 2176)" in src3 or "GLM5_NEXT_WIDTH" in src3:
  66. print("sm120 dispatch allowlist already extended")
  67. else:
  68. a = "_DECODE_DSV3_2_PAGE_BLOCK_SIZE = 64"
  69. add = ("# GLM5_NEXT_WIDTH: 2048 topk + kpool tail rounded to 128 -> 2176 (34x64 tiles)\n"
  70. "_DECODE_DSV3_2_DISPATCH = _DECODE_DSV3_2_DISPATCH | frozenset(\n"
  71. " {(h, 2176) for h in (8, 16, 32, 64, 128)}\n"
  72. ")\n")
  73. assert src3.count(a) == 1, f"fi anchor count {src3.count(a)}"
  74. src3 = src3.replace(a, add + a)
  75. open(P3, "w").write(src3)
  76. py_compile.compile(P3, doraise=True)
  77. print("sm120 dispatch allowlist extended + compiles")
  78. # ---- part 4: compact glm5_next topk table to the compiled 2048 width ----
  79. # Backend validation pins index_topk=2048; buffer is 2048 + (kpool-1) tail
  80. # rounded to 2176. Compiled decode kernel only takes 2048. Keep the top
  81. # (2048 - tail) ranked entries + the always-select tail (never duplicated:
  82. # tail tokens come from the incomplete pool, excluded from pool selection).
  83. # Attention is permutation-invariant over the gathered set, so order is free.
  84. src4 = open(P2).read()
  85. if "_glm_kpool_tail" in src4:
  86. print("sm120 tail-compaction already applied")
  87. else:
  88. c1 = " self.kv_scale_format = _kv_scale_format_for_model(model_type)\n"
  89. r1 = c1 + (
  90. " _kp = 1\n"
  91. " if vllm_config.model_config is not None:\n"
  92. " _kp = int(getattr(vllm_config.model_config.hf_text_config, \"index_kpool\", 1) or 1)\n"
  93. " self._glm_kpool_tail = max(0, _kp - 1)\n"
  94. )
  95. assert src4.count(c1) == 1, f"c1 count {src4.count(c1)}"
  96. src4 = src4.replace(c1, r1)
  97. c2 = """ NUM_TOPK_TOKENS=topk_indices.shape[1],
  98. ),
  99. )
  100. """
  101. r2 = c2 + (
  102. " _w = topk_indices_physical.shape[-1]\n"
  103. " _k = attn_metadata.topk_tokens\n"
  104. " if _w > _k:\n"
  105. " _t = min(self._glm_kpool_tail, _w - _k)\n"
  106. " topk_indices_physical = torch.cat(\n"
  107. " (\n"
  108. " topk_indices_physical[..., : _k - _t],\n"
  109. " topk_indices_physical[..., _k : _k + _t],\n"
  110. " ),\n"
  111. " dim=-1,\n"
  112. " ).contiguous()\n"
  113. )
  114. assert src4.count(c2) == 1, f"c2 count {src4.count(c2)}"
  115. src4 = src4.replace(c2, r2)
  116. open(P2, "w").write(src4)
  117. py_compile.compile(P2, doraise=True)
  118. print("sm120 tail-compaction applied + compiles")