tp3pad.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. # SPDX-License-Identifier: Apache-2.0
  2. """Zero-pad GLM-5.3-Flash weights at load time so uneven dims split at TP=3.
  3. The served config.json carries padded sizes (num_attention_heads,
  4. linear_num_heads, moe_intermediate_size) plus ``tp_pad_orig`` with the
  5. checkpoint's real ones. Every checkpoint tensor that is sized by one of those
  6. dims is zero-extended here, before vLLM's loaders narrow it per rank, so the
  7. extra heads / intermediate columns land at the end (the last rank's slice).
  8. Zero rows/cols are exact no-ops:
  9. - MLA: a zero head has q=0 and zero kv_b rows (so V=0) and zero o_proj cols.
  10. - KDA: q=k=v=0 keeps the recurrent state at 0, the gated RMSNorm of a zero
  11. vector is 0, and the o_proj cols are 0.
  12. - MoE / shared expert: gate=up=0 gives silu(0)*0=0, down cols are 0.
  13. NVFP4 tensors: packed uint8 zero = two FP4 +0 codes, fp8 block scale 0.
  14. """
  15. import torch
  16. from vllm.logger import init_logger
  17. logger = init_logger(__name__)
  18. def _pad(t: torch.Tensor, dim: int, size: int) -> torch.Tensor:
  19. if t.shape[dim] == size:
  20. return t
  21. assert t.shape[dim] < size, (tuple(t.shape), dim, size)
  22. src = t.view(torch.uint8) if t.element_size() == 1 else t
  23. shape = list(src.shape)
  24. shape[dim] = size
  25. out = src.new_zeros(shape)
  26. out.narrow(dim, 0, src.shape[dim]).copy_(src)
  27. return out.view(t.dtype) if t.element_size() == 1 else out
  28. def _cols(name: str, t: torch.Tensor, logical: int) -> int:
  29. """Physical width of a padded input dim for this tensor's format."""
  30. if name.endswith(".weight_scale"):
  31. return logical // 16
  32. if t.dtype == torch.uint8:
  33. return logical // 2
  34. return logical
  35. def pad_weights(weights, config):
  36. orig = getattr(config, "tp_pad_orig", None)
  37. if not orig:
  38. yield from weights
  39. return
  40. H0, H1 = orig["num_attention_heads"], config.num_attention_heads
  41. K0, K1 = orig["linear_num_heads"], config.linear_num_heads
  42. I0, I1 = orig["moe_intermediate_size"], config.moe_intermediate_size
  43. qk = config.qk_nope_head_dim + config.qk_rope_head_dim
  44. kvb = config.qk_nope_head_dim + config.v_head_dim
  45. v = config.v_head_dim
  46. hd = config.linear_head_dim
  47. ns = config.n_shared_experts or 1
  48. n = 0
  49. for name, t in weights:
  50. new = t
  51. if ".self_attn." in name and ".indexer." not in name:
  52. s = name.rsplit(".self_attn.", 1)[1]
  53. if s == "q_b_proj.weight" and t.shape[0] == H0 * qk:
  54. new = _pad(t, 0, H1 * qk)
  55. elif s == "kv_b_proj.weight" and t.shape[0] == H0 * kvb:
  56. new = _pad(t, 0, H1 * kvb)
  57. elif s == "o_proj.weight" and t.shape[1] == H0 * v:
  58. new = _pad(t, 1, H1 * v)
  59. elif s == "o_proj.weight" and t.shape[1] == K0 * hd:
  60. new = _pad(t, 1, K1 * hd)
  61. elif s in ("q_proj.weight", "k_proj.weight", "v_proj.weight",
  62. "f_b_proj.weight", "g_b_proj.weight", "dt_bias",
  63. "q_conv1d.weight", "k_conv1d.weight", "v_conv1d.weight") \
  64. and t.shape[0] == K0 * hd:
  65. new = _pad(t, 0, K1 * hd)
  66. elif s in ("b_proj.weight", "A_log") and t.shape[0] == K0:
  67. new = _pad(t, 0, K1)
  68. elif ".mlp.experts." in name or ".mlp.shared_experts." in name:
  69. i0, i1 = (I0 * ns, I1 * ns) if ".shared_experts." in name else (I0, I1)
  70. if name.endswith((".weight", ".weight_scale")):
  71. if (".gate_proj." in name or ".up_proj." in name) and t.shape[0] == i0:
  72. new = _pad(t, 0, i1)
  73. elif ".down_proj." in name and t.shape[1] == _cols(name, t, i0):
  74. new = _pad(t, 1, _cols(name, t, i1))
  75. if new is not t:
  76. n += 1
  77. yield name, new
  78. logger.info("tp3pad: zero-padded %d tensors (heads %d->%d, kda %d->%d, moe I %d->%d)",
  79. n, H0, H1, K0, K1, I0, I1)