tp3_pad.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. """Pad DeepSeek-V4.1 so TP=3 / EP=3 can load on 3 Sparks.
  2. Checkpoint dims that do not divide 3:
  3. vocab 129280, heads 64, o_groups 8, dspark experts 128.
  4. Pads: vocab via embedding pad_to=lcm(64,tp); heads 64→96 and groups 8→12
  5. so local heads=32 (FlashInfer SM120 sparse-MLA only instantiates 8/16/32/64/128);
  6. draft experts 128→129.
  7. Checkpoint tensors are smaller; loaders copy the real slice and zero-pad.
  8. """
  9. from __future__ import annotations
  10. import logging
  11. import math
  12. import os
  13. import torch
  14. logger = logging.getLogger(__name__)
  15. def _tp() -> int:
  16. return int(os.environ.get('TP_SIZE', os.environ.get('TP', '3')))
  17. def enabled() -> bool:
  18. if os.environ.get('DSV41_TP_PAD', '1') in ('0', 'false', 'off'):
  19. return False
  20. return _tp() > 1
  21. def _ceil_to(n: int, d: int) -> int:
  22. return ((n + d - 1) // d) * d
  23. def _pad_fill(param) -> float:
  24. """Fill value for padded slices: 1.0 for fp32 block scales, 0 for weights."""
  25. return 1.0 if param.dtype == torch.float32 else 0.0
  26. def pad_dsv41_hf_config(hf) -> None:
  27. if hf is None:
  28. return
  29. text = getattr(hf, 'text_config', None) or hf
  30. tp = _tp()
  31. heads = int(getattr(text, 'num_attention_heads', 0) or 0)
  32. groups = int(getattr(text, 'o_groups', 0) or 0)
  33. if heads and groups:
  34. per = max(heads // groups, 1)
  35. new_heads, new_groups = heads, groups
  36. if new_heads % tp:
  37. new_groups = _ceil_to(groups, tp)
  38. new_heads = new_groups * per
  39. # SM120 sparse-MLA kernels only exist for local head counts in this set.
  40. legal_local = (8, 16, 32, 64, 128)
  41. local = new_heads // tp
  42. if local not in legal_local:
  43. target_local = next((h for h in legal_local if h >= local), legal_local[-1])
  44. new_heads = target_local * tp
  45. new_groups = new_heads // per
  46. if new_groups * per != new_heads:
  47. new_groups = _ceil_to(new_groups, tp)
  48. new_heads = new_groups * per
  49. if new_heads != heads or new_groups != groups:
  50. text.o_groups = new_groups
  51. text.num_attention_heads = new_heads
  52. if hasattr(hf, 'num_attention_heads'):
  53. hf.num_attention_heads = new_heads
  54. logger.warning(
  55. 'DSV41 TP pad: num_attention_heads %s→%s, o_groups %s→%s '
  56. '(tp=%s, local_heads=%s)',
  57. heads, new_heads, groups, new_groups, tp, new_heads // tp,
  58. )
  59. draft = int(getattr(text, 'dspark_n_routed_experts', 0) or 0)
  60. if draft and draft % tp:
  61. new_draft = _ceil_to(draft, tp)
  62. text.dspark_n_routed_experts = new_draft
  63. logger.warning(
  64. 'DSV41 TP pad: dspark_n_routed_experts %s→%s (tp=%s)',
  65. draft, new_draft, tp,
  66. )
  67. vis = getattr(hf, 'vision_config', None)
  68. if vis is not None:
  69. vh = int(getattr(vis, 'num_attention_heads', 0) or getattr(vis, 'num_heads', 0) or 0)
  70. if vh and vh % tp:
  71. nv = _ceil_to(vh, tp)
  72. if hasattr(vis, 'num_attention_heads'):
  73. vis.num_attention_heads = nv
  74. if hasattr(vis, 'num_heads'):
  75. vis.num_heads = nv
  76. logger.warning('DSV41 TP pad: vision heads %s→%s', vh, nv)
  77. def _install_vocab_pad() -> None:
  78. import sglang.srt.layers.vocab_parallel_embedding as vpe
  79. orig = vpe.pad_vocab_size
  80. def pad_vocab_size(vocab_size: int, pad_to: int = vpe.DEFAULT_VOCAB_PADDING_SIZE) -> int:
  81. tp = _tp()
  82. if tp > 1:
  83. pad_to = math.lcm(int(pad_to), tp)
  84. return orig(vocab_size, pad_to)
  85. vpe.pad_vocab_size = pad_vocab_size
  86. def _install_config_pad() -> None:
  87. from sglang.srt.configs.model_config import ModelConfig
  88. orig = ModelConfig.__init__
  89. def wrapped(self, *args, **kwargs):
  90. orig(self, *args, **kwargs)
  91. try:
  92. pad_dsv41_hf_config(getattr(self, 'hf_config', None))
  93. text = getattr(getattr(self, 'hf_config', None), 'text_config', None)
  94. if text is not None:
  95. pad_dsv41_hf_config(text)
  96. if hasattr(self, 'hf_text_config') and self.hf_text_config is not None:
  97. self.hf_text_config.num_attention_heads = int(text.num_attention_heads)
  98. if hasattr(self.hf_text_config, 'o_groups'):
  99. self.hf_text_config.o_groups = int(text.o_groups)
  100. if hasattr(self, 'num_attention_heads'):
  101. self.num_attention_heads = int(text.num_attention_heads)
  102. except Exception:
  103. logger.exception('DSV41 TP config pad failed')
  104. ModelConfig.__init__ = wrapped
  105. def _install_column_pad() -> None:
  106. from sglang.srt.layers.utils import pad_or_narrow_weight
  107. from sglang.srt.layers.parameter import _ColumnvLLMParameter
  108. from sglang.srt.layers.linear import ColumnParallelLinear
  109. orig_col = _ColumnvLLMParameter.load_column_parallel_weight
  110. def load_column_parallel_weight(self, loaded_weight, tp_rank, use_presharded_weights=False):
  111. if use_presharded_weights:
  112. return orig_col(self, loaded_weight, tp_rank, use_presharded_weights)
  113. shard_size = self.data.shape[self.output_dim]
  114. start = tp_rank * shard_size
  115. end = start + shard_size
  116. dim = self.output_dim
  117. if end > loaded_weight.shape[dim] or start >= loaded_weight.shape[dim]:
  118. real = max(0, loaded_weight.shape[dim] - start)
  119. loaded_weight = pad_or_narrow_weight(loaded_weight, dim, start, shard_size)
  120. if loaded_weight.dtype == torch.float32 and real < loaded_weight.shape[dim]:
  121. # block scales: the padded region must stay a positive power of two
  122. loaded_weight = loaded_weight.clone()
  123. loaded_weight.narrow(dim, real, loaded_weight.shape[dim] - real).fill_(1.0)
  124. else:
  125. loaded_weight = loaded_weight.narrow(dim, start, shard_size)
  126. if self.data.shape != loaded_weight.shape:
  127. # Padded rows of a weight are zero; padded rows of a block scale
  128. # (fp32) are 1.0 so they stay positive powers of two and the
  129. # MXFP8 re-encoding accepts the layer (0 * 1 is still 0).
  130. self.data.fill_(_pad_fill(self.data))
  131. # overlap copy if pad_or_narrow returned a short tensor
  132. slices = tuple(slice(0, min(a, b)) for a, b in zip(self.data.shape, loaded_weight.shape))
  133. self.data[slices].copy_(loaded_weight[slices])
  134. return
  135. self.data.copy_(loaded_weight)
  136. _ColumnvLLMParameter.load_column_parallel_weight = load_column_parallel_weight
  137. orig_lin = ColumnParallelLinear.weight_loader
  138. def column_weight_loader(self, param, loaded_weight):
  139. output_dim = getattr(param, 'output_dim', None)
  140. if (
  141. output_dim is not None
  142. and not getattr(param, 'use_bitsandbytes_4bit', False)
  143. and not getattr(self, 'use_presharded_weights', False)
  144. ):
  145. shard_size = param.data.shape[output_dim]
  146. start = self.tp_rank * shard_size
  147. end = start + shard_size
  148. if end > loaded_weight.shape[output_dim]:
  149. real = loaded_weight.shape[output_dim] - start
  150. loaded_weight = pad_or_narrow_weight(
  151. loaded_weight, output_dim, start, shard_size
  152. )
  153. if loaded_weight.dtype == torch.float32 and 0 <= real < shard_size:
  154. # block scales: padded region must stay a power of two
  155. loaded_weight.narrow(output_dim, real, shard_size - real).fill_(1.0)
  156. if param.data.shape == loaded_weight.shape:
  157. param.data.copy_(loaded_weight)
  158. return
  159. return orig_lin(self, param, loaded_weight)
  160. ColumnParallelLinear.weight_loader = column_weight_loader
  161. def _install_default_loader_pad() -> None:
  162. import sglang.srt.model_loader.weight_utils as wu
  163. orig = wu.default_weight_loader
  164. def default_weight_loader(param, loaded_weight):
  165. try:
  166. return orig(param, loaded_weight)
  167. except AssertionError:
  168. pdata = param.data
  169. pdata.zero_()
  170. if loaded_weight.numel() == 0:
  171. return
  172. slices = tuple(
  173. slice(0, min(a, b)) for a, b in zip(pdata.shape, loaded_weight.shape)
  174. )
  175. pdata[slices].copy_(loaded_weight[slices])
  176. wu.default_weight_loader = default_weight_loader
  177. def _install_moe_padded_loading() -> None:
  178. try:
  179. from sglang.srt.layers.moe.fused_moe_triton.layer import FusedMoE
  180. except Exception:
  181. return
  182. orig = FusedMoE.use_padded_loading.fget if hasattr(FusedMoE.use_padded_loading, 'fget') else None
  183. def use_padded_loading(self) -> bool:
  184. if orig is not None and orig(self):
  185. return True
  186. return True
  187. try:
  188. FusedMoE.use_padded_loading = property(use_padded_loading)
  189. except Exception:
  190. logger.warning('could not force FusedMoE.use_padded_loading')
  191. def install() -> None:
  192. if not enabled():
  193. return
  194. _install_vocab_pad()
  195. _install_config_pad()
  196. _install_column_pad()
  197. _install_default_loader_pad()
  198. _install_moe_padded_loading()
  199. logger.warning('DSV41 TP pad installed (tp=%s)', _tp())