mhc_tilelang.py 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. import torch
  4. from vllm.utils.math_utils import cdiv
  5. from vllm.utils.torch_utils import direct_register_custom_op
  6. # The fused decode kernel reads the mHC projection weights (hc_*_fn) in bf16.
  7. # The checkpoint stores them in bf16 and the model holds them in fp32, so the
  8. # copy is exact, and it halves the kernel's weight reads. The larger-batch path
  9. # keeps the fp32 weight for DeepGEMM's tf32 GEMM. The first call per weight is
  10. # the pre-capture profile run, so no conversion lands inside a CUDA graph.
  11. _FN_BF16: dict[int, torch.Tensor] = {}
  12. def _fn_bf16(fn: torch.Tensor) -> torch.Tensor:
  13. t = _FN_BF16.get(fn.data_ptr())
  14. if t is None or t.shape != fn.shape:
  15. if torch.cuda.is_current_stream_capturing():
  16. # Correct, but the conversion would then replay every step.
  17. from vllm.logger import init_logger
  18. init_logger(__name__).warning("mHC bf16 weight first converted inside a CUDA graph capture")
  19. t = _FN_BF16[fn.data_ptr()] = fn.to(torch.bfloat16)
  20. return t
  21. def _torch_hc_prenorm_gemm(
  22. x: torch.Tensor,
  23. fn: torch.Tensor,
  24. out: torch.Tensor,
  25. sqrsum: torch.Tensor,
  26. ) -> None:
  27. assert out.shape[0] == 1
  28. assert sqrsum.shape[0] == 1
  29. x_float = x.float()
  30. out[0].copy_(x_float @ fn.t())
  31. sqrsum[0].copy_(x_float.square().sum(dim=-1))
  32. def _hc_prenorm_gemm_outputs(
  33. x: torch.Tensor,
  34. fn: torch.Tensor,
  35. *,
  36. hidden_size: int,
  37. hc_mult: int,
  38. use_tilelang_fallback: bool = True,
  39. ) -> tuple[torch.Tensor, torch.Tensor]:
  40. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  41. compute_num_split,
  42. )
  43. from vllm.utils.deep_gemm import (
  44. is_deep_gemm_supported,
  45. tf32_hc_prenorm_gemm,
  46. )
  47. use_deep_gemm = is_deep_gemm_supported() or not use_tilelang_fallback
  48. num_tokens = x.shape[0]
  49. n_splits = (
  50. compute_num_split(64, x.shape[1], cdiv(num_tokens, 64)) if use_deep_gemm else 1
  51. )
  52. out = torch.empty(
  53. n_splits,
  54. num_tokens,
  55. fn.shape[0],
  56. dtype=torch.float32,
  57. device=x.device,
  58. )
  59. sqrsum = torch.empty(
  60. n_splits,
  61. num_tokens,
  62. dtype=torch.float32,
  63. device=x.device,
  64. )
  65. if use_deep_gemm:
  66. tf32_hc_prenorm_gemm(x, fn, out, sqrsum, n_splits)
  67. else:
  68. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  69. _HC_PRENORM_GEMM_TILELANG_KERNEL,
  70. )
  71. _HC_PRENORM_GEMM_TILELANG_KERNEL(
  72. x,
  73. fn,
  74. out,
  75. sqrsum,
  76. hidden_size,
  77. hc_mult,
  78. )
  79. return out, sqrsum
  80. def mhc_pre_delayed_tilelang(
  81. residual: torch.Tensor,
  82. fn: torch.Tensor,
  83. hc_scale: torch.Tensor,
  84. hc_base: torch.Tensor,
  85. rms_eps: float,
  86. hc_pre_eps: float,
  87. hc_sinkhorn_eps: float,
  88. hc_post_mult_value: float,
  89. sinkhorn_repeat: int,
  90. pre_mix: torch.Tensor | None = None,
  91. x: torch.Tensor | None = None,
  92. norm_weight: torch.Tensor | None = None,
  93. norm_eps: float = 1e-6,
  94. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
  95. """Run mHC pre with a carried pre-mix and return the next pre-mix.
  96. Args:
  97. residual: BF16 residual streams of shape (tokens, hc_mult, hidden_size).
  98. fn: FP32 projection of shape (hc_mult * (hc_mult + 2), input_size).
  99. hc_scale: FP32 scales of shape (3,).
  100. hc_base: FP32 bias of shape (hc_mult * (hc_mult + 2),).
  101. rms_eps: RMS normalization epsilon.
  102. hc_pre_eps: Pre-mix epsilon.
  103. hc_sinkhorn_eps: Sinkhorn epsilon.
  104. hc_post_mult_value: Post-mix multiplier.
  105. sinkhorn_repeat: Number of Sinkhorn iterations.
  106. pre_mix: FP32 coefficients from the previous sublayer, or None to
  107. select residual stream zero at model entry.
  108. x: Optional BF16 projection input of shape (tokens, input_size), for
  109. the first layer's broadcast embedding and summed projection.
  110. norm_weight: Optional BF16 RMSNorm weight for the collapsed input.
  111. norm_eps: RMSNorm epsilon for the collapsed input.
  112. Returns:
  113. Post and residual coefficients, optionally normalized BF16 layer input,
  114. and the next FP32 pre-mix, with shapes (tokens, hc_mult, 1),
  115. (tokens, hc_mult, hc_mult), (tokens, hidden_size), and (tokens, hc_mult).
  116. """
  117. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  118. _HC_PRENORM_GEMM_TILELANG_KERNEL,
  119. mhc_pre_big_fuse_tilelang,
  120. )
  121. from vllm.model_executor.kernels.mhc.warmup import (
  122. MHC_PRE_NORM_KERNEL,
  123. compute_mhc_pre_num_splits,
  124. )
  125. from vllm.utils.deep_gemm import (
  126. is_deep_gemm_supported,
  127. tf32_hc_prenorm_gemm,
  128. )
  129. assert residual.ndim == 3 and residual.dtype == torch.bfloat16
  130. assert residual.is_contiguous()
  131. num_tokens, hc_mult, hidden_size = residual.shape
  132. if x is None:
  133. x = residual.view(num_tokens, hc_mult * hidden_size)
  134. assert x.ndim == 2 and x.dtype == torch.bfloat16 and x.is_contiguous()
  135. assert x.shape[0] == num_tokens
  136. input_size = x.shape[1]
  137. mix_size = hc_mult * (hc_mult + 2)
  138. assert fn.shape == (mix_size, input_size) and fn.dtype == torch.float32
  139. assert hc_scale.shape == (3,) and hc_scale.dtype == torch.float32
  140. assert hc_base.shape == (mix_size,) and hc_base.dtype == torch.float32
  141. if pre_mix is not None:
  142. assert pre_mix.shape == (num_tokens, hc_mult)
  143. assert pre_mix.dtype == torch.float32 and pre_mix.is_contiguous()
  144. next_pre_mix = torch.empty(
  145. num_tokens, hc_mult, dtype=torch.float32, device=residual.device
  146. )
  147. post = torch.empty_like(next_pre_mix)
  148. comb = torch.empty(
  149. num_tokens, hc_mult * hc_mult, dtype=torch.float32, device=residual.device
  150. )
  151. layer_input = torch.empty(
  152. num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
  153. )
  154. outputs = (
  155. post.unsqueeze(-1),
  156. comb.view(num_tokens, hc_mult, hc_mult),
  157. layer_input,
  158. next_pre_mix,
  159. )
  160. if num_tokens == 0:
  161. return outputs
  162. use_deep_gemm = is_deep_gemm_supported()
  163. n_splits = (
  164. compute_mhc_pre_num_splits(input_size, num_tokens) if use_deep_gemm else 1
  165. )
  166. mixes = torch.empty(
  167. n_splits, num_tokens, mix_size, dtype=torch.float32, device=residual.device
  168. )
  169. sqrsum = torch.empty(
  170. n_splits, num_tokens, dtype=torch.float32, device=residual.device
  171. )
  172. if use_deep_gemm:
  173. tf32_hc_prenorm_gemm(x, fn, mixes, sqrsum, n_splits)
  174. else:
  175. _HC_PRENORM_GEMM_TILELANG_KERNEL(
  176. x,
  177. fn,
  178. mixes,
  179. sqrsum,
  180. input_size,
  181. 1,
  182. )
  183. if norm_weight is not None:
  184. assert norm_weight.shape == (hidden_size,)
  185. assert norm_weight.dtype == torch.bfloat16 and norm_weight.is_contiguous()
  186. MHC_PRE_NORM_KERNEL(
  187. mixes,
  188. sqrsum,
  189. hc_scale,
  190. hc_base,
  191. residual,
  192. post,
  193. comb,
  194. layer_input,
  195. norm_weight,
  196. pre_mix if pre_mix is not None else post,
  197. next_pre_mix,
  198. layer_input,
  199. hidden_size=hidden_size,
  200. rms_eps=rms_eps,
  201. hc_pre_eps=hc_pre_eps,
  202. hc_sinkhorn_eps=hc_sinkhorn_eps,
  203. hc_post_mult_value=hc_post_mult_value,
  204. sinkhorn_repeat=sinkhorn_repeat,
  205. norm_eps=norm_eps,
  206. hc_mult=hc_mult,
  207. use_pre_mix_in=pre_mix is not None,
  208. save_pre_mix=True,
  209. rms_numel=input_size,
  210. )
  211. return outputs
  212. mhc_pre_big_fuse_tilelang(
  213. mixes,
  214. sqrsum,
  215. hc_scale,
  216. hc_base,
  217. residual,
  218. post,
  219. comb,
  220. layer_input,
  221. pre_mix if pre_mix is not None else post,
  222. next_pre_mix,
  223. layer_input,
  224. hidden_size,
  225. rms_eps,
  226. hc_pre_eps,
  227. hc_sinkhorn_eps,
  228. hc_post_mult_value,
  229. sinkhorn_repeat,
  230. n_splits,
  231. hc_mult,
  232. use_pre_mix_in=pre_mix is not None,
  233. save_pre_mix=True,
  234. rms_numel=input_size,
  235. )
  236. return outputs
  237. def _mhc_pre_delayed_tilelang_fake(
  238. residual: torch.Tensor,
  239. fn: torch.Tensor,
  240. hc_scale: torch.Tensor,
  241. hc_base: torch.Tensor,
  242. rms_eps: float,
  243. hc_pre_eps: float,
  244. hc_sinkhorn_eps: float,
  245. hc_post_mult_value: float,
  246. sinkhorn_repeat: int,
  247. pre_mix: torch.Tensor | None = None,
  248. x: torch.Tensor | None = None,
  249. norm_weight: torch.Tensor | None = None,
  250. norm_eps: float = 1e-6,
  251. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
  252. num_tokens, hc_mult, hidden_size = residual.shape
  253. return (
  254. torch.empty(
  255. num_tokens, hc_mult, 1, dtype=torch.float32, device=residual.device
  256. ),
  257. torch.empty(
  258. num_tokens, hc_mult, hc_mult, dtype=torch.float32, device=residual.device
  259. ),
  260. torch.empty(
  261. num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
  262. ),
  263. torch.empty(num_tokens, hc_mult, dtype=torch.float32, device=residual.device),
  264. )
  265. def mhc_fused_post_pre_delayed_tilelang(
  266. x: torch.Tensor,
  267. residual: torch.Tensor,
  268. post_layer_mix: torch.Tensor,
  269. comb_res_mix: torch.Tensor,
  270. fn: torch.Tensor,
  271. hc_scale: torch.Tensor,
  272. hc_base: torch.Tensor,
  273. rms_eps: float,
  274. hc_pre_eps: float,
  275. hc_sinkhorn_eps: float,
  276. hc_post_mult_value: float,
  277. sinkhorn_repeat: int,
  278. pre_mix: torch.Tensor | None = None,
  279. norm_weight: torch.Tensor | None = None,
  280. norm_eps: float = 1e-6,
  281. capture_aux: bool = False,
  282. ) -> tuple[
  283. torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor
  284. ]:
  285. """Run one mHC post block followed by the next delayed mHC pre block.
  286. Within the fused kernel's token range the post mapping is folded into the
  287. pre-norm GEMM, so the updated residual streams feed the projection from
  288. registers instead of a second pass over global memory. Above it this runs
  289. the same post kernel and split-k GEMM as the unfused pair.
  290. Args:
  291. x: BF16 sublayer output of shape (tokens, hidden_size).
  292. residual: BF16 residual streams of shape (tokens, hc_mult, hidden_size).
  293. post_layer_mix: FP32 post coefficients of shape (tokens, hc_mult, 1).
  294. comb_res_mix: FP32 residual coefficients, (tokens, hc_mult, hc_mult).
  295. fn: FP32 projection of shape (hc_mult * (hc_mult + 2), input_size).
  296. hc_scale: FP32 scales of shape (3,).
  297. hc_base: FP32 bias of shape (hc_mult * (hc_mult + 2),).
  298. rms_eps: RMS normalization epsilon.
  299. hc_pre_eps: Pre-mix epsilon.
  300. hc_sinkhorn_eps: Sinkhorn epsilon.
  301. hc_post_mult_value: Post-mix multiplier.
  302. sinkhorn_repeat: Number of Sinkhorn iterations.
  303. pre_mix: FP32 coefficients from the previous sublayer, or None to
  304. select residual stream zero.
  305. norm_weight: Optional BF16 RMSNorm weight for the collapsed input.
  306. norm_eps: RMSNorm epsilon for the collapsed input.
  307. capture_aux: Also return the mean over the post-mapped streams, which
  308. draft models consume as the target's hidden state. It is folded
  309. into the collapse, which already reads those streams.
  310. Returns:
  311. The post-mapped residual streams, the post and residual coefficients,
  312. the optionally normalized BF16 layer input, the next FP32 pre-mix, and
  313. the BF16 stream mean (empty unless capture_aux), with shapes
  314. (tokens, hc_mult, hidden_size), (tokens, hc_mult, 1),
  315. (tokens, hc_mult, hc_mult), (tokens, hidden_size), (tokens, hc_mult),
  316. and (tokens, hidden_size).
  317. """
  318. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  319. _HC_PRENORM_GEMM_TILELANG_KERNEL,
  320. _MHC_FUSED_TILELANG_KERNEL,
  321. _MHC_POST_TILELANG_KERNEL,
  322. mhc_fused_post_pre_split_config,
  323. mhc_pre_big_fuse_tilelang,
  324. )
  325. from vllm.model_executor.kernels.mhc.warmup import (
  326. MHC_PRE_NORM_KERNEL,
  327. compute_mhc_pre_num_splits,
  328. )
  329. from vllm.utils.deep_gemm import (
  330. is_deep_gemm_supported,
  331. tf32_hc_prenorm_gemm,
  332. )
  333. assert residual.ndim == 3 and residual.dtype == torch.bfloat16
  334. assert residual.is_contiguous()
  335. num_tokens, hc_mult, hidden_size = residual.shape
  336. input_size = hc_mult * hidden_size
  337. mix_size = hc_mult * (hc_mult + 2)
  338. assert x.shape == (num_tokens, hidden_size) and x.dtype == torch.bfloat16
  339. assert x.is_contiguous()
  340. assert post_layer_mix.shape[:2] == (num_tokens, hc_mult)
  341. assert post_layer_mix.dtype == torch.float32 and post_layer_mix.is_contiguous()
  342. assert comb_res_mix.shape == (num_tokens, hc_mult, hc_mult)
  343. assert comb_res_mix.dtype == torch.float32 and comb_res_mix.is_contiguous()
  344. assert fn.shape == (mix_size, input_size) and fn.dtype == torch.float32
  345. assert hc_scale.shape == (3,) and hc_scale.dtype == torch.float32
  346. assert hc_base.shape == (mix_size,) and hc_base.dtype == torch.float32
  347. if pre_mix is not None:
  348. assert pre_mix.shape == (num_tokens, hc_mult)
  349. assert pre_mix.dtype == torch.float32 and pre_mix.is_contiguous()
  350. next_pre_mix = torch.empty(
  351. num_tokens, hc_mult, dtype=torch.float32, device=residual.device
  352. )
  353. post = torch.empty_like(next_pre_mix)
  354. comb = torch.empty(
  355. num_tokens, hc_mult * hc_mult, dtype=torch.float32, device=residual.device
  356. )
  357. layer_input = torch.empty(
  358. num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
  359. )
  360. aux = torch.empty(
  361. num_tokens if capture_aux else 0,
  362. hidden_size,
  363. dtype=torch.bfloat16,
  364. device=residual.device,
  365. )
  366. if num_tokens == 0:
  367. return (
  368. torch.empty_like(residual),
  369. post.unsqueeze(-1),
  370. comb.view(num_tokens, hc_mult, hc_mult),
  371. layer_input,
  372. next_pre_mix,
  373. aux,
  374. )
  375. fused_config = mhc_fused_post_pre_split_config(num_tokens, hidden_size, hc_mult)
  376. if fused_config is not None:
  377. mixes, sqrsum, residual_cur = _MHC_FUSED_TILELANG_KERNEL(
  378. comb_res_mix,
  379. residual,
  380. post_layer_mix.view(num_tokens, hc_mult),
  381. x,
  382. _fn_bf16(fn).view(mix_size, hc_mult, hidden_size),
  383. hc_mult,
  384. hidden_size,
  385. mix_size,
  386. )
  387. else:
  388. residual_cur = torch.empty_like(residual)
  389. _MHC_POST_TILELANG_KERNEL(
  390. comb_res_mix,
  391. residual,
  392. post_layer_mix.view(num_tokens, hc_mult),
  393. x,
  394. residual_cur,
  395. hc_mult,
  396. hidden_size,
  397. )
  398. # The delayed epilogue is compiled per bucketed split count, so the
  399. # projection has to use the same bucket rather than the raw estimate.
  400. use_deep_gemm = is_deep_gemm_supported()
  401. n_splits = (
  402. compute_mhc_pre_num_splits(input_size, num_tokens) if use_deep_gemm else 1
  403. )
  404. mixes = torch.empty(
  405. n_splits, num_tokens, mix_size, dtype=torch.float32, device=residual.device
  406. )
  407. sqrsum = torch.empty(
  408. n_splits, num_tokens, dtype=torch.float32, device=residual.device
  409. )
  410. residual_cur_2d = residual_cur.view(num_tokens, input_size)
  411. if use_deep_gemm:
  412. tf32_hc_prenorm_gemm(residual_cur_2d, fn, mixes, sqrsum, n_splits)
  413. else:
  414. _HC_PRENORM_GEMM_TILELANG_KERNEL(
  415. residual_cur_2d,
  416. fn,
  417. mixes,
  418. sqrsum,
  419. input_size,
  420. 1,
  421. )
  422. outputs = (
  423. residual_cur,
  424. post.unsqueeze(-1),
  425. comb.view(num_tokens, hc_mult, hc_mult),
  426. layer_input,
  427. next_pre_mix,
  428. aux,
  429. )
  430. if norm_weight is not None:
  431. assert norm_weight.shape == (hidden_size,)
  432. assert norm_weight.dtype == torch.bfloat16 and norm_weight.is_contiguous()
  433. MHC_PRE_NORM_KERNEL(
  434. mixes,
  435. sqrsum,
  436. hc_scale,
  437. hc_base,
  438. residual_cur,
  439. post,
  440. comb,
  441. layer_input,
  442. norm_weight,
  443. pre_mix if pre_mix is not None else post,
  444. next_pre_mix,
  445. aux if capture_aux else layer_input,
  446. hidden_size=hidden_size,
  447. rms_eps=rms_eps,
  448. hc_pre_eps=hc_pre_eps,
  449. hc_sinkhorn_eps=hc_sinkhorn_eps,
  450. hc_post_mult_value=hc_post_mult_value,
  451. sinkhorn_repeat=sinkhorn_repeat,
  452. norm_eps=norm_eps,
  453. hc_mult=hc_mult,
  454. use_pre_mix_in=pre_mix is not None,
  455. save_pre_mix=True,
  456. rms_numel=input_size,
  457. write_aux=capture_aux,
  458. )
  459. return outputs
  460. mhc_pre_big_fuse_tilelang(
  461. mixes,
  462. sqrsum,
  463. hc_scale,
  464. hc_base,
  465. residual_cur,
  466. post,
  467. comb,
  468. layer_input,
  469. pre_mix if pre_mix is not None else post,
  470. next_pre_mix,
  471. aux if capture_aux else layer_input,
  472. hidden_size,
  473. rms_eps,
  474. hc_pre_eps,
  475. hc_sinkhorn_eps,
  476. hc_post_mult_value,
  477. sinkhorn_repeat,
  478. mixes.shape[0],
  479. hc_mult,
  480. use_pre_mix_in=pre_mix is not None,
  481. save_pre_mix=True,
  482. rms_numel=input_size,
  483. write_aux=capture_aux,
  484. )
  485. return outputs
  486. def _mhc_fused_post_pre_delayed_tilelang_fake(
  487. x: torch.Tensor,
  488. residual: torch.Tensor,
  489. post_layer_mix: torch.Tensor,
  490. comb_res_mix: torch.Tensor,
  491. fn: torch.Tensor,
  492. hc_scale: torch.Tensor,
  493. hc_base: torch.Tensor,
  494. rms_eps: float,
  495. hc_pre_eps: float,
  496. hc_sinkhorn_eps: float,
  497. hc_post_mult_value: float,
  498. sinkhorn_repeat: int,
  499. pre_mix: torch.Tensor | None = None,
  500. norm_weight: torch.Tensor | None = None,
  501. norm_eps: float = 1e-6,
  502. capture_aux: bool = False,
  503. ) -> tuple[
  504. torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor
  505. ]:
  506. num_tokens, hc_mult, hidden_size = residual.shape
  507. return (
  508. torch.empty_like(residual),
  509. torch.empty(
  510. num_tokens, hc_mult, 1, dtype=torch.float32, device=residual.device
  511. ),
  512. torch.empty(
  513. num_tokens, hc_mult, hc_mult, dtype=torch.float32, device=residual.device
  514. ),
  515. torch.empty(
  516. num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
  517. ),
  518. torch.empty(num_tokens, hc_mult, dtype=torch.float32, device=residual.device),
  519. torch.empty(
  520. num_tokens if capture_aux else 0,
  521. hidden_size,
  522. dtype=torch.bfloat16,
  523. device=residual.device,
  524. ),
  525. )
  526. def mhc_pre_tilelang(
  527. residual: torch.Tensor,
  528. fn: torch.Tensor,
  529. hc_scale: torch.Tensor,
  530. hc_base: torch.Tensor,
  531. rms_eps: float,
  532. hc_pre_eps: float,
  533. hc_sinkhorn_eps: float,
  534. hc_post_mult_value: float,
  535. sinkhorn_repeat: int,
  536. n_splits: int = 1,
  537. norm_weight: torch.Tensor | None = None,
  538. norm_eps: float = 1e-6,
  539. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
  540. """Forward pass for mHC pre block.
  541. Args:
  542. residual: shape (..., hc_mult, hidden_size), dtype torch.bfloat16
  543. fn: shape (hc_mult3, hc_mult * hidden_size), dtype torch.float32
  544. hc_scale: shape (3,), dtype torch.float32
  545. hc_base: shape (hc_mult3,), dtype torch.float32
  546. rms_eps: RMS normalization epsilon
  547. hc_pre_eps: pre-mix epsilon
  548. hc_sinkhorn_eps: sinkhorn epsilon
  549. hc_post_mult_value: post-mix multiplier value
  550. sinkhorn_repeat: number of sinkhorn iterations
  551. n_splits: retained for the shared MHC operator API; the active GEMM
  552. backend selects its split factor internally.
  553. norm_weight: optional RMSNorm weight, shape (hidden_size,), dtype
  554. torch.bfloat16. When provided, RMSNorm is fused into the
  555. layer_input write path of the big_fuse kernel.
  556. norm_eps: epsilon for the fused RMSNorm; only consulted when
  557. norm_weight is given.
  558. Returns:
  559. post_mix: shape (..., hc_mult), dtype torch.float32
  560. comb_mix: shape (..., hc_mult, hc_mult), dtype torch.float32
  561. layer_input: shape (..., hidden_size), dtype torch.bfloat16
  562. """
  563. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  564. _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
  565. )
  566. assert residual.dtype == torch.bfloat16
  567. assert fn.dtype == torch.float32
  568. assert hc_scale.dtype == torch.float32
  569. assert hc_base.dtype == torch.float32
  570. hc_mult = residual.shape[-2]
  571. hidden_size = residual.shape[-1]
  572. hc_mult2 = hc_mult * hc_mult
  573. hc_mult3 = hc_mult * 2 + hc_mult2
  574. hc_hidden_size = hc_mult * hidden_size
  575. assert fn.shape[0] == hc_mult3
  576. assert fn.shape[1] == hc_hidden_size
  577. assert hc_scale.shape == (3,)
  578. assert hc_base.shape == (hc_mult3,)
  579. if norm_weight is not None:
  580. assert norm_weight.shape == (hidden_size,)
  581. if norm_weight.dtype != torch.bfloat16:
  582. norm_weight = norm_weight.to(torch.bfloat16)
  583. if not norm_weight.is_contiguous():
  584. norm_weight = norm_weight.contiguous()
  585. outer_shape = residual.shape[:-2]
  586. residual_flat = residual.view(-1, hc_mult, hidden_size)
  587. num_tokens = residual_flat.shape[0]
  588. post_mix = torch.empty(
  589. num_tokens, hc_mult, dtype=torch.float32, device=residual.device
  590. )
  591. comb_mix = torch.empty(
  592. num_tokens, hc_mult2, dtype=torch.float32, device=residual.device
  593. )
  594. layer_input = torch.empty(
  595. num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
  596. )
  597. gemm_out_mul, gemm_out_sqrsum = _hc_prenorm_gemm_outputs(
  598. residual_flat.view(num_tokens, hc_mult * hidden_size),
  599. fn,
  600. hidden_size=hidden_size,
  601. hc_mult=hc_mult,
  602. )
  603. _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
  604. gemm_out_mul,
  605. gemm_out_sqrsum,
  606. hc_scale,
  607. hc_base,
  608. residual_flat,
  609. post_mix,
  610. comb_mix,
  611. layer_input,
  612. rms_eps,
  613. hc_pre_eps,
  614. hc_sinkhorn_eps,
  615. hc_post_mult_value,
  616. sinkhorn_repeat,
  617. norm_weight=norm_weight,
  618. norm_eps=norm_eps,
  619. )
  620. return (
  621. post_mix.view(*outer_shape, hc_mult, 1),
  622. comb_mix.view(*outer_shape, hc_mult, hc_mult),
  623. layer_input.view(*outer_shape, hidden_size),
  624. )
  625. def _mhc_pre_tilelang_fake(
  626. residual: torch.Tensor,
  627. fn: torch.Tensor,
  628. hc_scale: torch.Tensor,
  629. hc_base: torch.Tensor,
  630. rms_eps: float,
  631. hc_pre_eps: float,
  632. hc_sinkhorn_eps: float,
  633. hc_post_mult_value: float,
  634. sinkhorn_repeat: int,
  635. n_splits: int = 1,
  636. norm_weight: torch.Tensor | None = None,
  637. norm_eps: float = 1e-6,
  638. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
  639. hc_mult = residual.shape[-2]
  640. hidden_size = residual.shape[-1]
  641. outer_shape = residual.shape[:-2]
  642. # Create empty tensors with correct shapes for meta device / shape inference
  643. post_mix = torch.empty(
  644. *outer_shape,
  645. hc_mult,
  646. 1,
  647. dtype=torch.float32,
  648. device=residual.device,
  649. )
  650. comb_mix = torch.empty(
  651. *outer_shape,
  652. hc_mult,
  653. hc_mult,
  654. dtype=torch.float32,
  655. device=residual.device,
  656. )
  657. layer_input = torch.empty(
  658. *outer_shape,
  659. hidden_size,
  660. dtype=torch.bfloat16,
  661. device=residual.device,
  662. )
  663. return post_mix, comb_mix, layer_input
  664. def mhc_pre_broadcast_tilelang(
  665. residual: torch.Tensor,
  666. fn: torch.Tensor,
  667. hc_scale: torch.Tensor,
  668. hc_base: torch.Tensor,
  669. rms_eps: float,
  670. hc_pre_eps: float,
  671. hc_sinkhorn_eps: float,
  672. hc_post_mult_value: float,
  673. sinkhorn_repeat: int,
  674. n_splits: int = 1,
  675. norm_weight: torch.Tensor | None = None,
  676. norm_eps: float = 1e-6,
  677. fn_broadcast: torch.Tensor | None = None,
  678. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
  679. """First-layer mHC pre for a residual broadcast from ``(T, H)``."""
  680. # n_splits is retained for the shared API; split selection is internal.
  681. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  682. _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
  683. )
  684. assert norm_weight is not None, "broadcast mHC pre currently requires fused RMSNorm"
  685. assert residual.dtype == torch.bfloat16
  686. assert residual.dim() == 2
  687. assert fn.dtype == torch.float32
  688. assert hc_scale.dtype == torch.float32
  689. assert hc_base.dtype == torch.float32
  690. hidden_size = residual.shape[-1]
  691. hc_mult = fn.shape[1] // hidden_size
  692. hc_mult2 = hc_mult * hc_mult
  693. hc_mult3 = hc_mult * 2 + hc_mult2
  694. assert fn.shape == (hc_mult3, hc_mult * hidden_size)
  695. assert hc_scale.shape == (3,)
  696. assert hc_base.shape == (hc_mult3,)
  697. assert fn_broadcast is not None
  698. assert fn_broadcast.dtype == torch.float32
  699. assert fn_broadcast.shape == (hc_mult3, hidden_size)
  700. if norm_weight.dtype != torch.bfloat16:
  701. norm_weight = norm_weight.to(torch.bfloat16)
  702. if not norm_weight.is_contiguous():
  703. norm_weight = norm_weight.contiguous()
  704. residual_flat = residual
  705. num_tokens = residual.shape[0]
  706. residual_out = torch.empty(
  707. num_tokens, hc_mult, hidden_size, dtype=torch.bfloat16, device=residual.device
  708. )
  709. post_mix = torch.empty(
  710. num_tokens, hc_mult, dtype=torch.float32, device=residual.device
  711. )
  712. comb_mix = torch.empty(
  713. num_tokens, hc_mult2, dtype=torch.float32, device=residual.device
  714. )
  715. layer_input = torch.empty(
  716. num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
  717. )
  718. gemm_out_mul, gemm_out_sqrsum = _hc_prenorm_gemm_outputs(
  719. residual_flat,
  720. fn_broadcast,
  721. hidden_size=hidden_size,
  722. hc_mult=hc_mult,
  723. use_tilelang_fallback=False,
  724. )
  725. _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
  726. gemm_out_mul,
  727. gemm_out_sqrsum,
  728. hc_scale,
  729. hc_base,
  730. residual_flat,
  731. post_mix,
  732. comb_mix,
  733. layer_input,
  734. rms_eps,
  735. hc_pre_eps,
  736. hc_sinkhorn_eps,
  737. hc_post_mult_value,
  738. sinkhorn_repeat,
  739. residual_out=residual_out,
  740. norm_weight=norm_weight,
  741. norm_eps=norm_eps,
  742. )
  743. return (
  744. residual_out,
  745. post_mix.unsqueeze(-1),
  746. comb_mix.view(num_tokens, hc_mult, hc_mult),
  747. layer_input,
  748. )
  749. def mhc_post_tilelang(
  750. x: torch.Tensor,
  751. residual: torch.Tensor,
  752. post_layer_mix: torch.Tensor,
  753. comb_res_mix: torch.Tensor,
  754. ) -> torch.Tensor:
  755. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  756. _MHC_POST_TILELANG_KERNEL,
  757. )
  758. out = torch.empty_like(residual)
  759. _MHC_POST_TILELANG_KERNEL(
  760. comb_res_mix,
  761. residual,
  762. post_layer_mix.squeeze(-1),
  763. x,
  764. out,
  765. residual.shape[-2],
  766. residual.shape[-1],
  767. )
  768. return out
  769. def mhc_fused_post_pre_tilelang(
  770. x: torch.Tensor,
  771. residual: torch.Tensor,
  772. post_layer_mix: torch.Tensor,
  773. comb_res_mix: torch.Tensor,
  774. fn: torch.Tensor,
  775. hc_scale: torch.Tensor,
  776. hc_base: torch.Tensor,
  777. rms_eps: float,
  778. hc_pre_eps: float,
  779. hc_sinkhorn_eps: float,
  780. hc_post_mult_value: float,
  781. sinkhorn_repeat: int,
  782. n_splits: int = 1,
  783. tile_n: int = 1,
  784. norm_weight: torch.Tensor | None = None,
  785. norm_eps: float = 1e-6,
  786. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
  787. """Run one MHC post block followed by the next MHC pre block.
  788. When ``norm_weight`` is provided, the layer_input_cur output is the
  789. RMSNorm'd activation (fused into the kernel); otherwise it is the
  790. raw pre-norm activation as before.
  791. ``n_splits`` and ``tile_n`` are retained for the shared MHC operator API.
  792. The TileLang path selects both values internally from the runtime shape.
  793. Returns:
  794. residual_cur: post-mapped residual, shape (..., hc_mult, hidden_size)
  795. post_mix_cur: shape (..., hc_mult, 1)
  796. comb_mix_cur: shape (..., hc_mult, hc_mult)
  797. layer_input_cur: shape (..., hidden_size)
  798. """
  799. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  800. _MHC_FUSED_TILELANG_KERNEL,
  801. _MHC_POST_TILELANG_KERNEL,
  802. _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
  803. mhc_fused_post_pre_split_config,
  804. )
  805. assert residual.dtype == torch.bfloat16
  806. assert x.dtype == torch.bfloat16
  807. assert post_layer_mix.dtype == torch.float32
  808. assert comb_res_mix.dtype == torch.float32
  809. assert fn.dtype == torch.float32
  810. assert hc_scale.dtype == torch.float32
  811. assert hc_base.dtype == torch.float32
  812. hc_mult = residual.shape[-2]
  813. hidden_size = residual.shape[-1]
  814. hc_mult2 = hc_mult * hc_mult
  815. hc_mult3 = hc_mult * 2 + hc_mult2
  816. hc_hidden_size = hc_mult * hidden_size
  817. outer_shape = residual.shape[:-2]
  818. assert x.shape == (*outer_shape, hidden_size)
  819. assert post_layer_mix.shape in (
  820. (*outer_shape, hc_mult, 1),
  821. (*outer_shape, hc_mult),
  822. )
  823. assert comb_res_mix.shape == (*outer_shape, hc_mult, hc_mult)
  824. assert fn.shape == (hc_mult3, hc_hidden_size)
  825. assert hc_scale.shape == (3,)
  826. assert hc_base.shape == (hc_mult3,)
  827. if norm_weight is not None:
  828. assert norm_weight.shape == (hidden_size,)
  829. if norm_weight.dtype != torch.bfloat16:
  830. norm_weight = norm_weight.to(torch.bfloat16)
  831. if not norm_weight.is_contiguous():
  832. norm_weight = norm_weight.contiguous()
  833. residual_flat = residual.view(-1, hc_mult, hidden_size)
  834. num_tokens = residual_flat.shape[0]
  835. x_flat = x.view(num_tokens, hidden_size)
  836. post_layer_mix_flat = post_layer_mix.view(num_tokens, hc_mult)
  837. comb_res_mix_flat = comb_res_mix.view(num_tokens, hc_mult, hc_mult)
  838. fused_config = mhc_fused_post_pre_split_config(num_tokens, hidden_size, hc_mult)
  839. post_mix_cur = torch.empty(
  840. num_tokens,
  841. hc_mult,
  842. dtype=torch.float32,
  843. device=residual.device,
  844. )
  845. comb_mix_cur = torch.empty(
  846. num_tokens,
  847. hc_mult2,
  848. dtype=torch.float32,
  849. device=residual.device,
  850. )
  851. layer_input_cur = torch.empty(
  852. num_tokens,
  853. hidden_size,
  854. dtype=torch.bfloat16,
  855. device=residual.device,
  856. )
  857. if fused_config is not None:
  858. gemm_out_mul, gemm_out_sqrsum, residual_cur = _MHC_FUSED_TILELANG_KERNEL(
  859. comb_res_mix_flat,
  860. residual_flat,
  861. post_layer_mix_flat,
  862. x_flat,
  863. _fn_bf16(fn).view(hc_mult3, hc_mult, hidden_size),
  864. hc_mult,
  865. hidden_size,
  866. hc_mult3,
  867. )
  868. _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
  869. gemm_out_mul,
  870. gemm_out_sqrsum,
  871. hc_scale,
  872. hc_base,
  873. residual_cur,
  874. post_mix_cur,
  875. comb_mix_cur,
  876. layer_input_cur,
  877. rms_eps,
  878. hc_pre_eps,
  879. hc_sinkhorn_eps,
  880. hc_post_mult_value,
  881. sinkhorn_repeat,
  882. norm_weight=norm_weight,
  883. norm_eps=norm_eps,
  884. )
  885. else:
  886. residual_cur = torch.empty_like(residual_flat)
  887. _MHC_POST_TILELANG_KERNEL(
  888. comb_res_mix_flat,
  889. residual_flat,
  890. post_layer_mix_flat,
  891. x_flat,
  892. residual_cur,
  893. residual.shape[-2],
  894. residual.shape[-1],
  895. )
  896. gemm_out_mul, gemm_out_sqrsum = _hc_prenorm_gemm_outputs(
  897. residual_cur.view(num_tokens, hc_mult * hidden_size),
  898. fn,
  899. hidden_size=hidden_size,
  900. hc_mult=hc_mult,
  901. )
  902. _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
  903. gemm_out_mul,
  904. gemm_out_sqrsum,
  905. hc_scale,
  906. hc_base,
  907. residual_cur,
  908. post_mix_cur,
  909. comb_mix_cur,
  910. layer_input_cur,
  911. rms_eps,
  912. hc_pre_eps,
  913. hc_sinkhorn_eps,
  914. hc_post_mult_value,
  915. sinkhorn_repeat,
  916. norm_weight=norm_weight,
  917. norm_eps=norm_eps,
  918. )
  919. return (
  920. residual_cur.view(*outer_shape, hc_mult, hidden_size),
  921. post_mix_cur.view(*outer_shape, hc_mult, 1),
  922. comb_mix_cur.view(*outer_shape, hc_mult, hc_mult),
  923. layer_input_cur.view(*outer_shape, hidden_size),
  924. )
  925. def _mhc_fused_post_pre_tilelang_fake(
  926. x: torch.Tensor,
  927. residual: torch.Tensor,
  928. post_layer_mix: torch.Tensor,
  929. comb_res_mix: torch.Tensor,
  930. fn: torch.Tensor,
  931. hc_scale: torch.Tensor,
  932. hc_base: torch.Tensor,
  933. rms_eps: float,
  934. hc_pre_eps: float,
  935. hc_sinkhorn_eps: float,
  936. hc_post_mult_value: float,
  937. sinkhorn_repeat: int,
  938. n_splits: int = 1,
  939. tile_n: int = 1,
  940. norm_weight: torch.Tensor | None = None,
  941. norm_eps: float = 1e-6,
  942. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
  943. hc_mult = residual.shape[-2]
  944. hidden_size = residual.shape[-1]
  945. outer_shape = residual.shape[:-2]
  946. residual_cur = torch.empty_like(residual)
  947. post_mix_cur = torch.empty(
  948. *outer_shape,
  949. hc_mult,
  950. 1,
  951. dtype=torch.float32,
  952. device=residual.device,
  953. )
  954. comb_mix_cur = torch.empty(
  955. *outer_shape,
  956. hc_mult,
  957. hc_mult,
  958. dtype=torch.float32,
  959. device=residual.device,
  960. )
  961. layer_input_cur = torch.empty(
  962. *outer_shape,
  963. hidden_size,
  964. dtype=torch.bfloat16,
  965. device=residual.device,
  966. )
  967. return residual_cur, post_mix_cur, comb_mix_cur, layer_input_cur
  968. def _mhc_post_tilelang_fake(
  969. x: torch.Tensor,
  970. residual: torch.Tensor,
  971. post_layer_mix: torch.Tensor,
  972. comb_res_mix: torch.Tensor,
  973. ) -> torch.Tensor:
  974. return torch.empty_like(residual)
  975. def hc_head_fused_kernel_tilelang(
  976. hs_flat: torch.Tensor,
  977. fn: torch.Tensor,
  978. hc_scale: torch.Tensor,
  979. hc_base: torch.Tensor,
  980. rms_eps: float,
  981. hc_eps: float,
  982. ) -> torch.Tensor:
  983. """Apply the fused hc_head kernel and return the (T, H) bf16 result."""
  984. num_tokens, hc_mult, hidden_size = hs_flat.shape
  985. out = torch.empty(
  986. num_tokens, hidden_size, dtype=torch.bfloat16, device=hs_flat.device
  987. )
  988. if num_tokens == 0:
  989. return out
  990. from vllm.model_executor.kernels.mhc.tilelang_kernels import (
  991. _HC_HEAD_FUSED_TILELANG_KERNEL,
  992. )
  993. _HC_HEAD_FUSED_TILELANG_KERNEL(
  994. hs_flat,
  995. fn,
  996. hc_scale,
  997. hc_base,
  998. out,
  999. hidden_size,
  1000. rms_eps,
  1001. hc_eps,
  1002. hc_mult,
  1003. )
  1004. return out
  1005. def _hc_head_fused_kernel_tilelang_fake(
  1006. hs_flat: torch.Tensor,
  1007. fn: torch.Tensor,
  1008. hc_scale: torch.Tensor,
  1009. hc_base: torch.Tensor,
  1010. rms_eps: float,
  1011. hc_eps: float,
  1012. ) -> torch.Tensor:
  1013. num_tokens, _, hidden_size = hs_flat.shape
  1014. return torch.empty(
  1015. num_tokens, hidden_size, dtype=torch.bfloat16, device=hs_flat.device
  1016. )
  1017. direct_register_custom_op(
  1018. op_name="mhc_pre_delayed_tilelang",
  1019. op_func=mhc_pre_delayed_tilelang,
  1020. mutates_args=[],
  1021. fake_impl=_mhc_pre_delayed_tilelang_fake,
  1022. )
  1023. direct_register_custom_op(
  1024. op_name="mhc_fused_post_pre_delayed_tilelang",
  1025. op_func=mhc_fused_post_pre_delayed_tilelang,
  1026. mutates_args=[],
  1027. fake_impl=_mhc_fused_post_pre_delayed_tilelang_fake,
  1028. )
  1029. direct_register_custom_op(
  1030. op_name="mhc_pre_tilelang",
  1031. op_func=mhc_pre_tilelang,
  1032. mutates_args=[],
  1033. fake_impl=_mhc_pre_tilelang_fake,
  1034. )
  1035. direct_register_custom_op(
  1036. op_name="mhc_post_tilelang",
  1037. op_func=mhc_post_tilelang,
  1038. mutates_args=[],
  1039. fake_impl=_mhc_post_tilelang_fake,
  1040. )
  1041. direct_register_custom_op(
  1042. op_name="mhc_fused_post_pre_tilelang",
  1043. op_func=mhc_fused_post_pre_tilelang,
  1044. mutates_args=[],
  1045. fake_impl=_mhc_fused_post_pre_tilelang_fake,
  1046. )
  1047. direct_register_custom_op(
  1048. op_name="hc_head_fused_kernel_tilelang",
  1049. op_func=hc_head_fused_kernel_tilelang,
  1050. mutates_args=[],
  1051. fake_impl=_hc_head_fused_kernel_tilelang_fake,
  1052. )