patch_dspark_b12xwoa.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python3
  2. """Patch dspark.py so the DSpark drafter uses the fused b12x WO projection
  3. (identical to the main model) instead of the broken DeepGEMM fp8_einsum path.
  4. Why: the DeepGEMM fp8_einsum route for the drafter's wo_a is broken in this
  5. fork (rank/tiled-scale problems). Patching it to at least boot was possible,
  6. but draft acceptance was ~0% (the numerically-approximate einsum makes the
  7. drafter's low-rank output projection wrong, so its proposals never match the
  8. target). The main model computes wo_a correctly via the fused
  9. `pack_wo_projection_fp8_block_scaled_weights_mxfp8` +
  10. `wo_projection_inv_rope_mxfp8` path, which natively handles the tiled scale.
  11. We give the drafter the same path: it does inv_rope + wo_a + wo_b in one
  12. kernel, so the drafter's projections become numerically correct.
  13. Imports torch is present in dspark.py (module uses it).
  14. """
  15. import pathlib
  16. P = pathlib.Path(
  17. "/opt/env/lib/python3.12/site-packages/vllm/models/deepseek_v4/nvidia/dspark.py"
  18. )
  19. src = P.read_text()
  20. # 1) Replace the broken DeepGEMM einsum tail (inv_rope + wo_a einsum + wo_b)
  21. # with the fused b12x WO projection. `out` is (num_tokens, n_local_heads,
  22. # head_dim) bf16 — the layout `_apply_b12x_wo_projection` expects.
  23. OLD_TAIL = """ out_fp8, out_scale = fused_inv_rope_fp8_quant(
  24. out,
  25. positions,
  26. self.rotary_emb.cos_sin_cache,
  27. n_groups=self.n_local_groups,
  28. heads_per_group=self.n_local_heads // self.n_local_groups,
  29. nope_dim=self.nope_head_dim,
  30. rope_dim=self.rope_head_dim,
  31. tma_aligned_scales=self._tma_aligned_scales,
  32. )
  33. projected = torch.empty(
  34. (batch_size * block_size, self.n_local_groups, self.o_lora_rank),
  35. dtype=self.dtype,
  36. device=out.device,
  37. )
  38. torch.ops.vllm.deepseek_v4_fp8_einsum(
  39. out_fp8,
  40. out_scale,
  41. self.wo_a.weight,
  42. self.wo_a.weight_scale_inv,
  43. projected,
  44. "bhr,hdr->bhd",
  45. list(self._einsum_recipe),
  46. )
  47. return _linear_no_bias(self.wo_b, projected.flatten(1).to(self.dtype))"""
  48. NEW_TAIL = """ # sm121-dspark38: fused b12x WO projection (same as the main model)
  49. # replaces the broken DeepGEMM fp8_einsum path. Restores correct
  50. # drafter projection numerics -> real DSpark draft acceptance. The
  51. # kernel applies inv_rope + wo_a + wo_b in one fused pass.
  52. return self._apply_b12x_wo_projection(out, positions)"""
  53. assert src.count(OLD_TAIL) == 1, f"einsum tail matches: got {src.count(OLD_TAIL)}"
  54. src = src.replace(OLD_TAIL, NEW_TAIL)
  55. # 2) Insert the b12x WO-projection methods into DeepSeekV4DSparkAttention
  56. # (right before its forward_dspark, identified by the preceding line).
  57. METHODS = """ return q, self._maybe_quant_dequant_kv(kv)
  58. def setup_b12x_wo_projection(self) -> None:
  59. if getattr(self, "_b12x_wo_projection_weights", None) is not None:
  60. return
  61. groups, group_width, rank, hidden = self._validate_wo_projection_tensors()
  62. from b12x.gemm.wo_projection import (
  63. pack_wo_projection_fp8_block_scaled_weights_mxfp8,
  64. )
  65. self._b12x_wo_projection_weights = (
  66. pack_wo_projection_fp8_block_scaled_weights_mxfp8(
  67. self.wo_a.weight.detach(),
  68. self.wo_a.weight_scale_inv.detach(),
  69. self.wo_b.weight.detach(),
  70. self.wo_b.weight_scale_inv.detach(),
  71. groups=groups,
  72. group_width=group_width,
  73. rank=rank,
  74. hidden=hidden,
  75. )
  76. )
  77. def _validate_wo_projection_tensors(self):
  78. groups = self.n_local_groups
  79. group_width = (self.n_local_heads // groups) * self.head_dim
  80. rank = self.o_lora_rank
  81. hidden = self.hidden_size
  82. shapes = {
  83. "wo_a.weight": (groups * rank, group_width),
  84. "wo_b.weight": (hidden, groups * rank),
  85. "wo_a.scale": (
  86. groups * ((rank + 127) // 128),
  87. (group_width + 127) // 128,
  88. ),
  89. "wo_b.scale": (
  90. (hidden + 127) // 128,
  91. (groups * rank + 127) // 128,
  92. ),
  93. }
  94. for name, t in (
  95. ("wo_a.weight", self.wo_a.weight),
  96. ("wo_b.weight", self.wo_b.weight),
  97. ("wo_a.scale", self.wo_a.weight_scale_inv),
  98. ("wo_b.scale", self.wo_b.weight_scale_inv),
  99. ):
  100. exp = shapes[name]
  101. if tuple(t.shape) != exp:
  102. raise RuntimeError(
  103. f"b12x WO {name} shape {tuple(t.shape)} != expected {exp}"
  104. )
  105. return groups, group_width, rank, hidden
  106. def _apply_b12x_wo_projection(
  107. self, o: torch.Tensor, positions: torch.Tensor
  108. ) -> torch.Tensor:
  109. num_tokens = int(o.shape[0])
  110. if num_tokens == 0:
  111. return torch.empty(
  112. (0, self.hidden_size), dtype=o.dtype, device=o.device
  113. )
  114. if o.dtype != torch.bfloat16:
  115. raise RuntimeError(
  116. "sm121 b12x WO projection requires bf16 attention output, "
  117. f"got {o.dtype}"
  118. )
  119. if getattr(self, "_b12x_wo_projection_weights", None) is None:
  120. self.setup_b12x_wo_projection()
  121. from b12x.gemm.wo_projection import (
  122. WOProjectionScratchCaps,
  123. plan_wo_projection_scratch,
  124. wo_projection_inv_rope_mxfp8,
  125. )
  126. groups, group_width, rank, hidden = self._validate_wo_projection_tensors()
  127. plan = plan_wo_projection_scratch(
  128. WOProjectionScratchCaps(
  129. device=o.device,
  130. max_tokens=max(1, num_tokens),
  131. groups=groups,
  132. group_width=group_width,
  133. rank=rank,
  134. hidden=hidden,
  135. dtype=o.dtype,
  136. )
  137. )
  138. scratch = tuple(
  139. torch.empty(shape, dtype=dtype, device=o.device)
  140. for shape, dtype in plan.shapes_and_dtypes()
  141. )
  142. binding = plan.bind_inv_rope(
  143. scratch=scratch,
  144. o=o,
  145. positions=positions,
  146. cos_sin_cache=self.rotary_emb.cos_sin_cache,
  147. weights=self._b12x_wo_projection_weights,
  148. heads_per_group=self.n_local_heads // groups,
  149. nope_dim=self.nope_head_dim,
  150. rope_dim=self.rope_head_dim,
  151. )
  152. out = wo_projection_inv_rope_mxfp8(binding=binding)
  153. if self.wo_b.reduce_results and self.wo_b.tp_size > 1:
  154. from vllm.distributed import tensor_model_parallel_all_reduce
  155. out = tensor_model_parallel_all_reduce(out)
  156. return out
  157. def forward_dspark("""
  158. ANCHOR = """ return q, self._maybe_quant_dequant_kv(kv)
  159. def forward_dspark("""
  160. assert src.count(ANCHOR) == 1, f"method insert anchor: got {src.count(ANCHOR)}"
  161. P.write_text(src.replace(ANCHOR, METHODS))
  162. print("patched dspark.py: drafter wo_a routed through fused b12x WO projection")
  163. # Remove the now-unused einsum imports? Not required — harmless if referenced
  164. # elsewhere. Leave as-is.