glm53_deterministic_topk.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python3
  2. """Make the kpool indexer's top-k the same on every run.
  3. The prefill and decode top-k kernels return the right pools, but in a
  4. run-dependent order, and when pools tie at the k-th score they keep a
  5. run-dependent subset of the tied ones. The pools expand to token ids in list
  6. order and the sparse MLA kernel sums keys in that order, so either difference
  7. changes the rounding. The next layer's indexer then picks different keys, and
  8. identical requests past 2048 tokens drifted apart by up to 9.8 nats.
  9. The kernels are asked for TIE_MARGIN extra candidates. A Triton pass reads the
  10. candidates' scores, keeps the top k with ties going to the lowest pool id, and
  11. writes the ids in ascending order. A row whose tie runs past the extra
  12. candidates is rescanned in full.
  13. The persistent and cooperative decode kernels only take k in (512, 1024, 2048),
  14. so with those backends decode rows are only sorted.
  15. Same contract as the other patchers: each anchor exactly once, or the build
  16. fails.
  17. """
  18. from pathlib import Path
  19. PATH = Path("/usr/local/lib/python3.12/dist-packages/vllm/models/glm5next/nvidia/sparse_indexer.py")
  20. HELPER_ANCHOR = "@eager_break_during_capture\ndef sparse_attn_indexer_kpool(\n"
  21. HELPER = '''from vllm.triton_utils import tl, triton
  22. # Extra candidates asked of the top-k kernel. Pools tied at the k-th score are
  23. # almost always among them.
  24. _TIE_MARGIN = 32
  25. @triton.jit
  26. def _order_key(x):
  27. # float32 -> int64 in [0, 2^32) with the same order as the floats.
  28. b = x.to(tl.int32, bitcast=True)
  29. return (b ^ ((b >> 31) & 0x7FFFFFFF)).to(tl.int64) + 2147483648
  30. @triton.jit
  31. def _select_topk_rows_kernel(
  32. cand_ptr, cand_stride, logits_ptr, logits_stride, ks_ptr, ke_ptr,
  33. out_ptr, out_stride, K, KM,
  34. HAS_KS: tl.constexpr, BLOCK: tl.constexpr, SCAN: tl.constexpr,
  35. ):
  36. row = tl.program_id(0).to(tl.int64)
  37. if HAS_KS:
  38. ks = tl.load(ks_ptr + row).to(tl.int64)
  39. else:
  40. ks = tl.zeros((), dtype=tl.int64)
  41. ke = tl.load(ke_ptr + row).to(tl.int64)
  42. lrow = logits_ptr + row * logits_stride + ks
  43. orow = out_ptr + row * out_stride
  44. offs = tl.arange(0, BLOCK)
  45. pad = 2147483647
  46. ids = tl.load(cand_ptr + row * cand_stride + offs, mask=offs < KM, other=-1)
  47. ids = tl.sort(tl.where(ids < 0, pad, ids))
  48. valid = ids != pad
  49. n_valid = tl.sum(valid.to(tl.int32))
  50. u = tl.where(valid, _order_key(tl.load(lrow + ids, mask=valid, other=0.0)), -1)
  51. # The k-th largest key: the largest t with at least K keys >= t.
  52. thr = tl.zeros((), dtype=tl.int64)
  53. for bit in tl.static_range(31, -1, -1):
  54. t = thr | (1 << bit)
  55. thr = tl.where(tl.sum((u >= t).to(tl.int32)) >= K, t, thr)
  56. tied = u == thr
  57. need = K - tl.sum((u > thr).to(tl.int32))
  58. if n_valid <= K:
  59. keep = valid
  60. else:
  61. keep = (u > thr) | (tied & (tl.cumsum(tied.to(tl.int32), axis=0) <= need))
  62. lowest = tl.min(tl.where(valid, u, 1 << 40))
  63. if (n_valid == KM) & (lowest == thr):
  64. # The tie may continue past the candidates. Every score above it is
  65. # a candidate, so walk the row in id order and take everything above
  66. # it plus the lowest-id tied pools.
  67. base = 0
  68. for start in range(0, ke - ks, SCAN):
  69. c = start + tl.arange(0, SCAN)
  70. m = c < ke - ks
  71. uc = _order_key(tl.load(lrow + c, mask=m, other=0.0))
  72. eq = m & (uc == thr)
  73. take = m & ((uc > thr) | (eq & (tl.cumsum(eq.to(tl.int32), axis=0) <= need)))
  74. tl.store(orow + base + tl.cumsum(take.to(tl.int32), axis=0) - 1, c.to(tl.int32), mask=take)
  75. base += tl.sum(take.to(tl.int32))
  76. need -= tl.sum(eq.to(tl.int32))
  77. else:
  78. n = tl.sum(keep.to(tl.int32))
  79. tl.store(orow + tl.cumsum(keep.to(tl.int32), axis=0) - 1, ids, mask=keep)
  80. tl.store(orow + offs, tl.full((BLOCK,), -1, tl.int32), mask=(offs >= n) & (offs < K))
  81. def _select_topk_rows(cand, logits, ks, ke, out):
  82. """Write each row's top out.shape[1] ids to out in ascending order, lowest id first among equal scores.
  83. cand holds an exact top-(k + _TIE_MARGIN) per row as ids relative to ks,
  84. -1 padded. logits[r, ks[r]:ke[r]] are row r's scores; ks=None means 0.
  85. """
  86. if cand.shape[0] == 0:
  87. return
  88. _select_topk_rows_kernel[(cand.shape[0],)](
  89. cand, cand.stride(0), logits, logits.stride(0), ke if ks is None else ks, ke,
  90. out, out.stride(0), out.shape[1], cand.shape[1],
  91. HAS_KS=ks is not None, BLOCK=triton.next_power_of_2(cand.shape[1]), SCAN=1024,
  92. )
  93. @triton.jit
  94. def _sort_topk_rows_kernel(ptr, stride, n, BLOCK: tl.constexpr):
  95. row = tl.program_id(0)
  96. offs = tl.arange(0, BLOCK)
  97. pad = 2147483647
  98. v = tl.load(ptr + row * stride + offs, mask=offs < n, other=pad)
  99. v = tl.sort(tl.where(v < 0, pad, v))
  100. tl.store(ptr + row * stride + offs, tl.where(v == pad, -1, v), mask=offs < n)
  101. def _sort_topk_rows(t: torch.Tensor) -> None:
  102. """Order each row's ids ascending in place, keeping the -1 padding last."""
  103. if t.shape[0] == 0:
  104. return
  105. _sort_topk_rows_kernel[(t.shape[0],)](
  106. t, t.stride(0), t.shape[1], BLOCK=triton.next_power_of_2(t.shape[1])
  107. )
  108. '''
  109. PREFILL = """ torch.ops._C.top_k_per_row_prefill(
  110. logits,
  111. chunk.cu_seqlen_ks,
  112. chunk.cu_seqlen_ke,
  113. topk_dst,
  114. num_rows,
  115. logits.stride(0),
  116. logits.stride(1),
  117. select_k,
  118. )
  119. """
  120. PREFILL_NEW = """ cand = torch.full(
  121. (num_rows, select_k + _TIE_MARGIN), -1, dtype=torch.int32, device=logits.device
  122. )
  123. torch.ops._C.top_k_per_row_prefill(
  124. logits,
  125. chunk.cu_seqlen_ks,
  126. chunk.cu_seqlen_ke,
  127. cand,
  128. num_rows,
  129. logits.stride(0),
  130. logits.stride(1),
  131. select_k + _TIE_MARGIN,
  132. )
  133. _select_topk_rows(cand, logits, chunk.cu_seqlen_ks, chunk.cu_seqlen_ke, topk_dst)
  134. """
  135. DECODE = """ get_indexer_topk(topk_backend)(
  136. logits,
  137. seq_lens,
  138. next_n,
  139. topk_dst,
  140. select_k,
  141. attn_metadata_narrowed.max_seq_len,
  142. )
  143. """
  144. DECODE_NEW = """ if topk_backend == "per_row":
  145. # 2-D seq_lens hold each row's own length, as the kernel reads them.
  146. cand = torch.full(
  147. (num_rows, select_k + _TIE_MARGIN), -1, dtype=torch.int32, device=logits.device
  148. )
  149. get_indexer_topk(topk_backend)(
  150. logits,
  151. seq_lens,
  152. next_n,
  153. cand,
  154. select_k + _TIE_MARGIN,
  155. attn_metadata_narrowed.max_seq_len,
  156. )
  157. _select_topk_rows(cand, logits, None, seq_lens.reshape(-1)[:num_rows], topk_dst)
  158. else:
  159. """ + "".join(" " + line + "\n" for line in DECODE.splitlines()) + """ _sort_topk_rows(topk_dst)
  160. """
  161. text = PATH.read_text()
  162. if "def _select_topk_rows(" in text:
  163. print("[deterministic-topk] already applied")
  164. else:
  165. for name, anchor in (("helper", HELPER_ANCHOR), ("prefill", PREFILL), ("decode", DECODE)):
  166. n = text.count(anchor)
  167. assert n == 1, f"[deterministic-topk] {name} anchor matched {n} times; stock tree changed"
  168. text = text.replace(HELPER_ANCHOR, HELPER + HELPER_ANCHOR)
  169. text = text.replace(PREFILL, PREFILL_NEW)
  170. text = text.replace(DECODE, DECODE_NEW)
  171. PATH.write_text(text)
  172. print("[deterministic-topk] kpool top-k: lowest pool id wins ties, rows sorted (prefill and decode)")