mxfp8_b12x.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. """Route SGLang's block-fp8-as-MXFP8 dense linears to a FlashInfer backend that
  2. suits GB10 (SM121).
  3. SGLang's --fp8-gemm-backend only knows the cutlass / cute-dsl / trtllm MXFP8
  4. kernels. The CUTLASS SM120 kernel uses a 128x32x128 tile: at M=6 (DSpark verify
  5. at batch 1) it pads M to 128 and streams 32 weight columns per tile, which the
  6. 2026-09-10 profile measured at 50-75 GB/s on every projection (52 ms of a 118 ms
  7. decode step, the same as the Triton kernel it replaced). FlashInfer's b12x
  8. warp-level MMA kernel has 16|32 x 64|128 tiles for small M and is what
  9. mm_mxfp8's own 'auto' heuristic prefers on SM120/121; 'cudnn' is the other
  10. SM12x option. DSV41_MXFP8_BACKEND selects it (default b12x; 'cutlass' or ''
  11. restores SGLang's choice). A shape the chosen backend rejects falls back to
  12. cutlass for the rest of the process, logged once.
  13. """
  14. import logging
  15. import os
  16. import torch
  17. logger = logging.getLogger(__name__)
  18. _REJECTED = set()
  19. def install(module):
  20. want = os.environ.get('DSV41_MXFP8_BACKEND', 'b12x').strip()
  21. if want in ('', 'cutlass', 'off', '0'):
  22. return
  23. original = module.flashinfer_mxfp8_blockscaled_linear
  24. def linear(input, weight, weight_scale, input_scale=None, bias=None,
  25. output_dtype=None, backend='cutlass', pin_tactic=False):
  26. key = (int(weight.shape[0]), int(weight.shape[1]))
  27. if backend == 'cutlass' and key not in _REJECTED:
  28. try:
  29. return original(input, weight, weight_scale, input_scale, bias,
  30. output_dtype, backend=want, pin_tactic=pin_tactic)
  31. except Exception as exc: # shape/backend rejection: keep serving
  32. _REJECTED.add(key)
  33. logger.warning('DSV41 MXFP8 backend %r rejected N=%s K=%s (%s); '
  34. 'cutlass for this shape', want, key[0], key[1], exc)
  35. return original(input, weight, weight_scale, input_scale, bias,
  36. output_dtype, backend=backend, pin_tactic=pin_tactic)
  37. module.flashinfer_mxfp8_blockscaled_linear = linear
  38. # TP=3 pads heads 64->96 and o_groups 8->12, so rank 2's wq_b / wo_b shards are
  39. # entirely padding: zero weights with zero block scales. A zero scale is not a
  40. # power of two, so block_fp8_scale_to_mxfp8_e8m0 raised and those 86 layers stayed
  41. # on the Triton kernel (22 ms/step on rank 2, the slowest rank). The fp8 values in
  42. # such blocks are zero, so any scale gives the same product; use 1.0 (e8m0 code 127).
  43. original_encode = module.block_fp8_scale_to_mxfp8_e8m0
  44. def encode(weight_scale, weight_shape, weight_block_size):
  45. scale = weight_scale.detach()
  46. if scale.numel() and bool((scale <= 0).any()):
  47. scale = torch.where(scale > 0, scale, torch.ones_like(scale))
  48. return original_encode(scale, weight_shape, weight_block_size)
  49. module.block_fp8_scale_to_mxfp8_e8m0 = encode
  50. logger.warning('DSV41 MXFP8 dense linears routed to FlashInfer backend %r '
  51. '(zero block scales of padded shards encoded as 1.0)', want)
  52. def _repair_padded_scales(layer, block_size):
  53. """Set every block scale that is not a positive power of two to 1.0 when its
  54. weight block is entirely zero (a TP-padded shard). Returns a diagnostic string
  55. for anything that was, or could not be, repaired; None when nothing was wrong."""
  56. ws = getattr(layer, 'weight_scale_inv', None)
  57. w = getattr(layer, 'weight', None)
  58. if ws is None or w is None or ws.data.ndim != 2:
  59. return None
  60. s = ws.data
  61. if s.dtype != torch.float32:
  62. return f'scale dtype {s.dtype} (not float32), left alone'
  63. f = s.contiguous()
  64. bits = f.view(torch.int32)
  65. bad = ~(((bits & 0x7FFFFF) == 0) & (f > 0))
  66. if not bool(bad.any()):
  67. return None
  68. n, k = w.shape
  69. bn, bk = block_size
  70. sn, sk = f.shape
  71. if n % bn or k % bk or sn != n // bn or sk != k // bk:
  72. return (f'{int(bad.sum())} bad scales but shape {tuple(w.shape)} / {tuple(f.shape)} '
  73. f'does not tile by {block_size}, left alone')
  74. zero = ((w.data.view(torch.uint8) & 0x7F) == 0).view(n // bn, bn, k // bk, bk).all(3).all(1)
  75. fix = bad & zero
  76. vals = f[bad][:4].tolist()
  77. if bool(fix.any()):
  78. f[fix] = 1.0
  79. s.copy_(f)
  80. return (f'{int(bad.sum())} invalid scales (e.g. {vals}), {int(fix.sum())} on all-zero '
  81. f'weight blocks set to 1.0, {int((bad & ~zero).sum())} on non-zero blocks left alone')
  82. def install_fp8(module):
  83. """Hook Fp8LinearMethod._prepare_block_fp8_as_mxfp8 so padded shards pass the e8m0
  84. check whatever their (never written) scale memory holds, and log what was found."""
  85. if os.environ.get('DSV41_MXFP8_BACKEND', 'b12x').strip() in ('', 'cutlass', 'off', '0'):
  86. return
  87. cls = getattr(module, 'Fp8LinearMethod', None)
  88. if cls is None or not hasattr(cls, '_prepare_block_fp8_as_mxfp8'):
  89. logger.warning('DSV41: Fp8LinearMethod._prepare_block_fp8_as_mxfp8 not found; no scale repair')
  90. return
  91. original = cls._prepare_block_fp8_as_mxfp8
  92. def prepare(self, layer):
  93. try:
  94. note = _repair_padded_scales(layer, self.weight_block_size)
  95. except Exception as exc: # never let diagnostics break loading
  96. note = f'repair failed: {exc}'
  97. if note:
  98. logger.warning('DSV41 block scales for %s: %s', getattr(layer, 'prefix', layer.__class__.__name__), note)
  99. return original(self, layer)
  100. cls._prepare_block_fp8_as_mxfp8 = prepare
  101. logger.warning('DSV41 padded-shard block-scale repair installed')