host.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. """Host-side helpers for the CuTeDSL W4A16 MoE path."""
  2. from __future__ import annotations
  3. from dataclasses import dataclass
  4. import torch
  5. from b12x.moe.fused.activations import (
  6. SUPPORTED_MOE_ACTIVATIONS,
  7. is_gated_moe_activation,
  8. normalize_moe_activation,
  9. )
  10. _W4A16_ALLOWED_ROUTED_SIZES = (8, 16, 32, 48, 64)
  11. _ROUTED_SIZE_TARGET_FILL = 0.9
  12. _SUPPORTED_ACTIVATIONS = SUPPORTED_MOE_ACTIVATIONS
  13. @dataclass(frozen=True)
  14. class W4A16PackedShape:
  15. num_experts: int
  16. hidden_size: int
  17. intermediate_size: int
  18. w13_rows: int
  19. is_gated: bool
  20. @dataclass(frozen=True)
  21. class W4A16PackedBuffers:
  22. intermediate_cache13: torch.Tensor
  23. intermediate_cache2: torch.Tensor
  24. output: torch.Tensor
  25. fc1_c_tmp: torch.Tensor | None = None
  26. fc2_c_tmp: torch.Tensor | None = None
  27. packed_route_indices: torch.Tensor | None = None
  28. block_expert_ids: torch.Tensor | None = None
  29. packed_route_count: torch.Tensor | None = None
  30. expert_offsets: torch.Tensor | None = None
  31. @dataclass(frozen=True)
  32. class W4A16BufferPlan:
  33. routed_rows: int
  34. fc1_cols: int
  35. route_slots: int
  36. route_blocks: int
  37. fc1_c_tmp_elements: int
  38. fc2_c_tmp_elements: int
  39. intermediate_cache13_elements: int
  40. intermediate_cache2_elements: int
  41. def validate_activation(activation: str) -> bool:
  42. activation = normalize_moe_activation(activation)
  43. return is_gated_moe_activation(activation)
  44. def validate_w4a16_packed_inputs(
  45. w13_fp4: torch.Tensor,
  46. w13_global_scale: torch.Tensor,
  47. w2_fp4: torch.Tensor,
  48. w2_global_scale: torch.Tensor,
  49. *,
  50. activation: str,
  51. ) -> W4A16PackedShape:
  52. is_gated = validate_activation(activation)
  53. if w13_fp4.dtype != torch.uint8 or w2_fp4.dtype != torch.uint8:
  54. raise TypeError("packed FP4 weights must be torch.uint8")
  55. if (
  56. w13_global_scale.dtype != torch.float32
  57. or w2_global_scale.dtype != torch.float32
  58. ):
  59. raise TypeError("global scales must be torch.float32")
  60. num_experts = int(w13_fp4.shape[0])
  61. hidden_size = int(w2_fp4.shape[1])
  62. intermediate_size = int(w2_fp4.shape[2] * 2)
  63. w13_rows = intermediate_size * (2 if is_gated else 1)
  64. if tuple(w13_fp4.shape) != (num_experts, w13_rows, hidden_size // 2):
  65. raise ValueError(
  66. f"expected w13_fp4 shape {(num_experts, w13_rows, hidden_size // 2)}, "
  67. f"got {tuple(w13_fp4.shape)}"
  68. )
  69. if tuple(w2_fp4.shape) != (num_experts, hidden_size, intermediate_size // 2):
  70. raise ValueError(
  71. f"expected w2_fp4 shape {(num_experts, hidden_size, intermediate_size // 2)}, "
  72. f"got {tuple(w2_fp4.shape)}"
  73. )
  74. return W4A16PackedShape(
  75. num_experts=num_experts,
  76. hidden_size=hidden_size,
  77. intermediate_size=intermediate_size,
  78. w13_rows=w13_rows,
  79. is_gated=is_gated,
  80. )
  81. def validate_nf3_moe_inputs(
  82. w13_codes: torch.Tensor,
  83. w13_scale: torch.Tensor,
  84. w2_codes: torch.Tensor,
  85. w2_scale: torch.Tensor,
  86. *,
  87. activation: str,
  88. ) -> W4A16PackedShape:
  89. """Validate NF3 ("nf3_2p1") code/scale inputs to the packer.
  90. Codes are integer tensors of values 0..7 in kernel-native output order:
  91. w13_codes [E, 2*I, hidden] w2_codes [E, hidden, I]
  92. Scales are per-K/32-group ``t_s`` floats:
  93. w13_scale [E, 2*I, hidden//32] w2_scale [E, hidden, I//32]
  94. The NF3 runtime contract requires K % 32 == 0 and N % 64 == 0 for both
  95. GEMMs (the scale byte encoding and the 64-N tile packing).
  96. """
  97. is_gated = validate_activation(activation)
  98. _int_dtypes = (torch.int8, torch.uint8, torch.int16, torch.int32, torch.int64)
  99. if w13_codes.dtype not in _int_dtypes or w2_codes.dtype not in _int_dtypes:
  100. raise TypeError("NF3 codes must be integer tensors of values 0..7")
  101. num_experts = int(w2_codes.shape[0])
  102. hidden_size = int(w2_codes.shape[1])
  103. intermediate_size = int(w2_codes.shape[2])
  104. w13_rows = intermediate_size * (2 if is_gated else 1)
  105. if tuple(w13_codes.shape) != (num_experts, w13_rows, hidden_size):
  106. raise ValueError(
  107. f"expected w13_codes shape {(num_experts, w13_rows, hidden_size)}, "
  108. f"got {tuple(w13_codes.shape)}"
  109. )
  110. if tuple(w2_codes.shape) != (num_experts, hidden_size, intermediate_size):
  111. raise ValueError(
  112. f"expected w2_codes shape {(num_experts, hidden_size, intermediate_size)}, "
  113. f"got {tuple(w2_codes.shape)}"
  114. )
  115. for name, size_k, size_n in (
  116. ("w13", hidden_size, w13_rows),
  117. ("w2", intermediate_size, hidden_size),
  118. ):
  119. if int(size_k) % 32 != 0:
  120. raise ValueError(f"NF3 {name} requires K % 32 == 0, got {size_k}")
  121. if int(size_n) % 64 != 0:
  122. raise ValueError(f"NF3 {name} requires N % 64 == 0, got {size_n}")
  123. if tuple(w13_scale.shape) != (num_experts, w13_rows, hidden_size // 32):
  124. raise ValueError(
  125. f"expected w13_scale shape {(num_experts, w13_rows, hidden_size // 32)}, "
  126. f"got {tuple(w13_scale.shape)}"
  127. )
  128. if tuple(w2_scale.shape) != (num_experts, hidden_size, intermediate_size // 32):
  129. raise ValueError(
  130. "expected w2_scale shape "
  131. f"{(num_experts, hidden_size, intermediate_size // 32)}, "
  132. f"got {tuple(w2_scale.shape)}"
  133. )
  134. return W4A16PackedShape(
  135. num_experts=num_experts,
  136. hidden_size=hidden_size,
  137. intermediate_size=intermediate_size,
  138. w13_rows=w13_rows,
  139. is_gated=is_gated,
  140. )
  141. def unswizzle_block_scale(
  142. swizzled_scale: torch.Tensor, rows: int, cols_blocks: int
  143. ) -> torch.Tensor:
  144. cols_padded = ((cols_blocks + 3) // 4) * 4
  145. rows_padded = ((rows + 127) // 128) * 128
  146. unswizzled = swizzled_scale.view(torch.float8_e4m3fn).reshape(
  147. rows_padded // 128,
  148. cols_padded // 4,
  149. 32,
  150. 4,
  151. 4,
  152. )
  153. unswizzled = unswizzled.permute(0, 3, 2, 1, 4).contiguous()
  154. unswizzled = unswizzled.reshape(rows_padded, cols_padded)
  155. return unswizzled[:rows, :cols_blocks].to(torch.float32)
  156. def unswizzle_expert_scales(
  157. swizzled: torch.Tensor,
  158. *,
  159. rows: int,
  160. cols: int,
  161. ) -> torch.Tensor:
  162. if swizzled.dtype != torch.float8_e4m3fn:
  163. swizzled = swizzled.view(torch.float8_e4m3fn)
  164. scales = [
  165. unswizzle_block_scale(swizzled[e], rows, cols // 16).to(torch.float8_e4m3fn)
  166. for e in range(swizzled.shape[0])
  167. ]
  168. return torch.stack(scales, dim=0).contiguous()
  169. def reorder_w13_to_gate_up(
  170. w13: torch.Tensor,
  171. w13_scale: torch.Tensor,
  172. *,
  173. intermediate_size: int,
  174. ) -> tuple[torch.Tensor, torch.Tensor]:
  175. half = int(intermediate_size)
  176. return (
  177. torch.cat([w13[:, half:], w13[:, :half]], dim=1).contiguous(),
  178. torch.cat([w13_scale[:, half:], w13_scale[:, :half]], dim=1).contiguous(),
  179. )
  180. def select_route_block_size_m(m: int, topk: int, num_experts: int) -> int:
  181. avg_routes_per_expert = (int(m) * int(topk)) / int(num_experts)
  182. for routed_size in _W4A16_ALLOWED_ROUTED_SIZES:
  183. if avg_routes_per_expert < _ROUTED_SIZE_TARGET_FILL * routed_size:
  184. return routed_size
  185. return _W4A16_ALLOWED_ROUTED_SIZES[-1]
  186. def max_packed_route_slots(numel: int, block_size: int, num_experts: int) -> int:
  187. max_packed_routes = int(numel) + int(num_experts) * (int(block_size) - 1)
  188. if int(numel) < int(num_experts):
  189. max_packed_routes = min(
  190. int(numel) * int(block_size),
  191. max_packed_routes,
  192. )
  193. return max_packed_routes
  194. def route_pack_numel_capacity(numel: int, topk: int = 1) -> int:
  195. topk = max(int(topk), 1)
  196. tokens = (max(int(numel), 1) + topk - 1) // topk
  197. return route_pack_token_capacity(tokens, topk) * topk
  198. def route_pack_token_capacity(tokens: int, topk: int) -> int:
  199. del topk
  200. return 1 << (max(int(tokens), 1) - 1).bit_length()
  201. def max_w4a16_route_capacity(routed_rows: int, num_experts: int) -> tuple[int, int]:
  202. route_slots = 0
  203. route_blocks = 0
  204. for block_size in _W4A16_ALLOWED_ROUTED_SIZES:
  205. slots = max_packed_route_slots(
  206. int(routed_rows), int(block_size), int(num_experts)
  207. )
  208. route_slots = max(route_slots, slots)
  209. route_blocks = max(
  210. route_blocks, (slots + int(block_size) - 1) // int(block_size)
  211. )
  212. return max(route_slots, 1), max(route_blocks, 1)
  213. def packed_gemm_scratch_elements(
  214. *,
  215. size_n: int,
  216. route_slots: int,
  217. moe_block_size: int,
  218. sms: int,
  219. ) -> int:
  220. elements = min(
  221. int(size_n) * int(route_slots),
  222. int(sms) * 4 * int(moe_block_size) * 256,
  223. )
  224. if moe_block_size == 8:
  225. elements *= 2
  226. return max(elements, 1)
  227. def plan_w4a16_buffers(
  228. prepared,
  229. *,
  230. m: int,
  231. topk: int,
  232. route_num_experts: int | None = None,
  233. sms: int,
  234. ) -> W4A16BufferPlan:
  235. routed_rows = int(m) * int(topk)
  236. route_num_experts = (
  237. int(prepared.num_experts)
  238. if route_num_experts is None
  239. else int(route_num_experts)
  240. )
  241. intermediate_size = int(prepared.intermediate_size)
  242. hidden_size = int(prepared.hidden_size)
  243. fc1_cols = (2 if prepared.is_gated else 1) * intermediate_size
  244. block_size_m = select_route_block_size_m(m, topk, route_num_experts)
  245. route_slots = max_packed_route_slots(routed_rows, block_size_m, route_num_experts)
  246. route_blocks = (route_slots + block_size_m - 1) // block_size_m
  247. scratch_sms = int(sms)
  248. return W4A16BufferPlan(
  249. routed_rows=routed_rows,
  250. fc1_cols=fc1_cols,
  251. route_slots=route_slots,
  252. route_blocks=route_blocks,
  253. fc1_c_tmp_elements=packed_gemm_scratch_elements(
  254. size_n=fc1_cols,
  255. route_slots=route_slots,
  256. moe_block_size=block_size_m,
  257. sms=scratch_sms,
  258. ),
  259. fc2_c_tmp_elements=packed_gemm_scratch_elements(
  260. size_n=hidden_size,
  261. route_slots=route_slots,
  262. moe_block_size=block_size_m,
  263. sms=scratch_sms,
  264. ),
  265. intermediate_cache13_elements=routed_rows * max(fc1_cols, hidden_size),
  266. intermediate_cache2_elements=routed_rows * intermediate_size,
  267. )
  268. def make_w4a16_packed_buffers(
  269. prepared,
  270. *,
  271. m: int,
  272. topk: int,
  273. dtype: torch.dtype,
  274. device: torch.device,
  275. route_num_experts: int | None = None,
  276. ) -> W4A16PackedBuffers:
  277. route_num_experts = (
  278. int(prepared.num_experts)
  279. if route_num_experts is None
  280. else int(route_num_experts)
  281. )
  282. sms = int(torch.cuda.get_device_properties(device).multi_processor_count)
  283. plan = plan_w4a16_buffers(
  284. prepared,
  285. m=m,
  286. topk=topk,
  287. route_num_experts=route_num_experts,
  288. sms=sms,
  289. )
  290. fc1_c_tmp = torch.empty(
  291. (plan.fc1_c_tmp_elements,),
  292. dtype=torch.float32,
  293. device=device,
  294. )
  295. fc2_c_tmp = torch.empty(
  296. (plan.fc2_c_tmp_elements,),
  297. dtype=torch.float32,
  298. device=device,
  299. )
  300. return W4A16PackedBuffers(
  301. intermediate_cache13=torch.empty(
  302. (plan.intermediate_cache13_elements,),
  303. dtype=dtype,
  304. device=device,
  305. ),
  306. intermediate_cache2=torch.empty(
  307. (plan.routed_rows, int(prepared.intermediate_size)),
  308. dtype=dtype,
  309. device=device,
  310. ),
  311. output=torch.empty((m, prepared.hidden_size), dtype=dtype, device=device),
  312. fc1_c_tmp=fc1_c_tmp,
  313. fc2_c_tmp=fc2_c_tmp,
  314. packed_route_indices=torch.empty(
  315. (plan.route_slots,), dtype=torch.int32, device=device
  316. ),
  317. block_expert_ids=torch.empty(
  318. (plan.route_blocks,), dtype=torch.int32, device=device
  319. ),
  320. packed_route_count=torch.empty((1,), dtype=torch.int32, device=device),
  321. expert_offsets=torch.empty(
  322. (route_num_experts + 1,), dtype=torch.int32, device=device
  323. ),
  324. )
  325. __all__ = [
  326. "W4A16BufferPlan",
  327. "W4A16PackedBuffers",
  328. "W4A16PackedShape",
  329. "make_w4a16_packed_buffers",
  330. "max_w4a16_route_capacity",
  331. "packed_gemm_scratch_elements",
  332. "max_packed_route_slots",
  333. "plan_w4a16_buffers",
  334. "reorder_w13_to_gate_up",
  335. "route_pack_numel_capacity",
  336. "route_pack_token_capacity",
  337. "select_route_block_size_m",
  338. "unswizzle_block_scale",
  339. "unswizzle_expert_scales",
  340. "validate_activation",
  341. "validate_nf3_moe_inputs",
  342. "validate_w4a16_packed_inputs",
  343. ]