| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Triton iHC pre/post kernels for HY V4. |
| 4 | |
| 5 | Adapted from the SGLang HY V4 implementation: |
| 6 | https://github.com/sgl-project/sglang/pull/36805 |
| 7 | """ |
| 8 | |
| 9 | import torch |
| 10 | |
| 11 | from vllm import envs |
| 12 | from vllm.platforms import current_platform |
| 13 | from vllm.triton_utils import HAS_TRITON, tl, triton |
| 14 | |
| 15 | _BLOCK_K = 1024 |
| 16 | _BLOCK_D = 1024 |
| 17 | |
| 18 | |
| 19 | def triton_ihc_supported(x: torch.Tensor) -> bool: |
| 20 | """Return whether the in-tree Triton path can run for this input.""" |
| 21 | return ( |
| 22 | HAS_TRITON |
| 23 | and current_platform.is_cuda() |
| 24 | and x.is_cuda |
| 25 | and x.dtype in (torch.float16, torch.bfloat16) |
| 26 | and not envs.VLLM_BATCH_INVARIANT |
| 27 | ) |
| 28 | |
| 29 | |
| 30 | @triton.jit |
| 31 | def _ihc_pre_stage1( |
| 32 | x_ptr, |
| 33 | weight_ptr, |
| 34 | partial_ptr, |
| 35 | K_TOTAL: tl.constexpr, |
| 36 | HC_MULT: tl.constexpr, |
| 37 | HC_POW2: tl.constexpr, |
| 38 | NUM_SPLITS: tl.constexpr, |
| 39 | BLOCK_K: tl.constexpr, |
| 40 | PARTIAL_STRIDE: tl.constexpr, |
| 41 | ): |
| 42 | token_idx = tl.program_id(0).to(tl.int64) |
| 43 | split_idx = tl.program_id(1) |
| 44 | hc_idx = tl.arange(0, HC_POW2) |
| 45 | hc_mask = hc_idx < HC_MULT |
| 46 | |
| 47 | k_offsets = split_idx * BLOCK_K + tl.arange(0, BLOCK_K) |
| 48 | k_mask = k_offsets < K_TOTAL |
| 49 | x = tl.load( |
| 50 | x_ptr + token_idx * K_TOTAL + k_offsets, |
| 51 | mask=k_mask, |
| 52 | other=0.0, |
| 53 | ).to(tl.float32) |
| 54 | sum_squares = tl.sum(x * x, axis=0) |
| 55 | |
| 56 | weight_offsets = hc_idx[:, None] * K_TOTAL + k_offsets[None, :] |
| 57 | weight_mask = hc_mask[:, None] & k_mask[None, :] |
| 58 | pre_mix = tl.sum( |
| 59 | tl.load(weight_ptr + weight_offsets, mask=weight_mask, other=0.0) * x[None, :], |
| 60 | axis=1, |
| 61 | ) |
| 62 | post_mix = tl.sum( |
| 63 | tl.load( |
| 64 | weight_ptr + HC_MULT * K_TOTAL + weight_offsets, |
| 65 | mask=weight_mask, |
| 66 | other=0.0, |
| 67 | ) |
| 68 | * x[None, :], |
| 69 | axis=1, |
| 70 | ) |
| 71 | |
| 72 | partial = partial_ptr + (token_idx * NUM_SPLITS + split_idx) * PARTIAL_STRIDE |
| 73 | tl.store(partial, sum_squares) |
| 74 | tl.store(partial + 1 + hc_idx, pre_mix, mask=hc_mask) |
| 75 | tl.store(partial + 1 + HC_POW2 + hc_idx, post_mix, mask=hc_mask) |
| 76 | |
| 77 | |
| 78 | @triton.jit |
| 79 | def _ihc_pre_stage2( |
| 80 | x_ptr, |
| 81 | partial_ptr, |
| 82 | scale_ptr, |
| 83 | base_ptr, |
| 84 | output_ptr, |
| 85 | post_ptr, |
| 86 | HIDDEN_SIZE: tl.constexpr, |
| 87 | K_TOTAL: tl.constexpr, |
| 88 | HC_MULT: tl.constexpr, |
| 89 | HC_POW2: tl.constexpr, |
| 90 | NUM_SPLITS: tl.constexpr, |
| 91 | PARTIAL_STRIDE: tl.constexpr, |
| 92 | BLOCK_D: tl.constexpr, |
| 93 | MAGNITUDE: tl.constexpr, |
| 94 | NORM_EPS: tl.constexpr, |
| 95 | HC_EPS: tl.constexpr, |
| 96 | ): |
| 97 | token_idx = tl.program_id(0).to(tl.int64) |
| 98 | hidden_block_idx = tl.program_id(1) |
| 99 | hc_idx = tl.arange(0, HC_POW2) |
| 100 | hc_mask = hc_idx < HC_MULT |
| 101 | |
| 102 | partial_row = partial_ptr + token_idx * NUM_SPLITS * PARTIAL_STRIDE |
| 103 | sum_squares = tl.zeros((), dtype=tl.float32) |
| 104 | pre_mix = tl.zeros((HC_POW2,), dtype=tl.float32) |
| 105 | post_mix = tl.zeros((HC_POW2,), dtype=tl.float32) |
| 106 | for split_idx in tl.static_range(NUM_SPLITS): |
| 107 | partial = partial_row + split_idx * PARTIAL_STRIDE |
| 108 | sum_squares += tl.load(partial) |
| 109 | pre_mix += tl.load(partial + 1 + hc_idx, mask=hc_mask, other=0.0) |
| 110 | post_mix += tl.load( |
| 111 | partial + 1 + HC_POW2 + hc_idx, |
| 112 | mask=hc_mask, |
| 113 | other=0.0, |
| 114 | ) |
| 115 | |
| 116 | reciprocal_rms = tl.rsqrt(sum_squares / K_TOTAL + NORM_EPS) |
| 117 | pre_scale = tl.load(scale_ptr) |
| 118 | post_scale = tl.load(scale_ptr + 1) |
| 119 | pre_base = tl.load(base_ptr + hc_idx, mask=hc_mask, other=0.0) |
| 120 | post_base = tl.load(base_ptr + HC_MULT + hc_idx, mask=hc_mask, other=0.0) |
| 121 | pre = tl.sigmoid(pre_mix * reciprocal_rms * pre_scale + pre_base) + HC_EPS |
| 122 | |
| 123 | if hidden_block_idx == 0: |
| 124 | post = ( |
| 125 | MAGNITUDE * tl.sigmoid(post_mix * reciprocal_rms * post_scale + post_base) |
| 126 | + HC_EPS |
| 127 | ) |
| 128 | tl.store( |
| 129 | post_ptr + token_idx * HC_MULT + hc_idx, |
| 130 | post, |
| 131 | mask=hc_mask, |
| 132 | ) |
| 133 | |
| 134 | hidden_offsets = hidden_block_idx * BLOCK_D + tl.arange(0, BLOCK_D) |
| 135 | hidden_mask = hidden_offsets < HIDDEN_SIZE |
| 136 | x_row = x_ptr + token_idx * K_TOTAL |
| 137 | output = tl.zeros((BLOCK_D,), dtype=tl.float32) |
| 138 | for channel_idx in tl.static_range(HC_MULT): |
| 139 | channel = tl.load( |
| 140 | x_row + channel_idx * HIDDEN_SIZE + hidden_offsets, |
| 141 | mask=hidden_mask, |
| 142 | other=0.0, |
| 143 | ).to(tl.float32) |
| 144 | channel_gate = tl.sum(tl.where(hc_idx == channel_idx, pre, 0.0), axis=0) |
| 145 | output += channel_gate * channel |
| 146 | |
| 147 | tl.store( |
| 148 | output_ptr + token_idx * HIDDEN_SIZE + hidden_offsets, |
| 149 | output.to(output_ptr.dtype.element_ty), |
| 150 | mask=hidden_mask, |
| 151 | ) |
| 152 | |
| 153 | |
| 154 | @triton.jit |
| 155 | def _ihc_post_kernel( |
| 156 | x_ptr, |
| 157 | residual_ptr, |
| 158 | post_ptr, |
| 159 | output_ptr, |
| 160 | HIDDEN_SIZE: tl.constexpr, |
| 161 | HC_MULT: tl.constexpr, |
| 162 | HC_POW2: tl.constexpr, |
| 163 | BLOCK_D: tl.constexpr, |
| 164 | ): |
| 165 | token_idx = tl.program_id(0).to(tl.int64) |
| 166 | hidden_block_idx = tl.program_id(1) |
| 167 | hc_idx = tl.arange(0, HC_POW2) |
| 168 | hc_mask = hc_idx < HC_MULT |
| 169 | post = tl.load( |
| 170 | post_ptr + token_idx * HC_MULT + hc_idx, |
| 171 | mask=hc_mask, |
| 172 | other=0.0, |
| 173 | ) |
| 174 | |
| 175 | hidden_offsets = hidden_block_idx * BLOCK_D + tl.arange(0, BLOCK_D) |
| 176 | hidden_mask = hidden_offsets < HIDDEN_SIZE |
| 177 | x = tl.load( |
| 178 | x_ptr + token_idx * HIDDEN_SIZE + hidden_offsets, |
| 179 | mask=hidden_mask, |
| 180 | other=0.0, |
| 181 | ).to(tl.float32) |
| 182 | residual_row = residual_ptr + token_idx * HC_MULT * HIDDEN_SIZE |
| 183 | output_row = output_ptr + token_idx * HC_MULT * HIDDEN_SIZE |
| 184 | for channel_idx in tl.static_range(HC_MULT): |
| 185 | residual = tl.load( |
| 186 | residual_row + channel_idx * HIDDEN_SIZE + hidden_offsets, |
| 187 | mask=hidden_mask, |
| 188 | other=0.0, |
| 189 | ).to(tl.float32) |
| 190 | channel_gate = tl.sum(tl.where(hc_idx == channel_idx, post, 0.0), axis=0) |
| 191 | output = channel_gate * x + residual |
| 192 | tl.store( |
| 193 | output_row + channel_idx * HIDDEN_SIZE + hidden_offsets, |
| 194 | output.to(output_ptr.dtype.element_ty), |
| 195 | mask=hidden_mask, |
| 196 | ) |
| 197 | |
| 198 | |
| 199 | def triton_ihc_pre( |
| 200 | x: torch.Tensor, |
| 201 | weight: torch.Tensor, |
| 202 | scale: torch.Tensor, |
| 203 | base: torch.Tensor, |
| 204 | magnitude: float, |
| 205 | hc_eps: float, |
| 206 | norm_eps: float, |
| 207 | ) -> tuple[torch.Tensor, torch.Tensor]: |
| 208 | """Reduce iHC channels and produce the post gates.""" |
| 209 | assert x.dim() == 3 |
| 210 | assert x.is_cuda and weight.is_cuda and scale.is_cuda and base.is_cuda |
| 211 | assert weight.dtype == torch.float32 |
| 212 | assert scale.dtype == torch.float32 and base.dtype == torch.float32 |
| 213 | |
| 214 | x = x.contiguous() |
| 215 | assert weight.is_contiguous() |
| 216 | scale = scale.contiguous() |
| 217 | base = base.contiguous() |
| 218 | num_tokens, hc_mult, hidden_size = x.shape |
| 219 | k_total = hc_mult * hidden_size |
| 220 | assert weight.shape == (2 * hc_mult, k_total) |
| 221 | assert scale.shape == (2,) |
| 222 | assert base.shape == (2 * hc_mult,) |
| 223 | |
| 224 | if num_tokens == 0: |
| 225 | return ( |
| 226 | torch.empty((0, hidden_size), dtype=x.dtype, device=x.device), |
| 227 | torch.empty((0, hc_mult), dtype=torch.float32, device=x.device), |
| 228 | ) |
| 229 | |
| 230 | output = torch.empty((num_tokens, hidden_size), dtype=x.dtype, device=x.device) |
| 231 | post = torch.empty((num_tokens, hc_mult), dtype=torch.float32, device=x.device) |
| 232 | hc_pow2 = triton.next_power_of_2(hc_mult) |
| 233 | num_splits = triton.cdiv(k_total, _BLOCK_K) |
| 234 | partial_stride = 1 + 2 * hc_pow2 |
| 235 | partial = torch.empty( |
| 236 | (num_tokens, num_splits, partial_stride), |
| 237 | dtype=torch.float32, |
| 238 | device=x.device, |
| 239 | ) |
| 240 | |
| 241 | _ihc_pre_stage1[(num_tokens, num_splits)]( |
| 242 | x, |
| 243 | weight, |
| 244 | partial, |
| 245 | K_TOTAL=k_total, |
| 246 | HC_MULT=hc_mult, |
| 247 | HC_POW2=hc_pow2, |
| 248 | NUM_SPLITS=num_splits, |
| 249 | BLOCK_K=_BLOCK_K, |
| 250 | PARTIAL_STRIDE=partial_stride, |
| 251 | num_warps=8, |
| 252 | enable_fp_fusion=False, |
| 253 | ) |
| 254 | _ihc_pre_stage2[(num_tokens, triton.cdiv(hidden_size, _BLOCK_D))]( |
| 255 | x, |
| 256 | partial, |
| 257 | scale, |
| 258 | base, |
| 259 | output, |
| 260 | post, |
| 261 | HIDDEN_SIZE=hidden_size, |
| 262 | K_TOTAL=k_total, |
| 263 | HC_MULT=hc_mult, |
| 264 | HC_POW2=hc_pow2, |
| 265 | NUM_SPLITS=num_splits, |
| 266 | PARTIAL_STRIDE=partial_stride, |
| 267 | BLOCK_D=_BLOCK_D, |
| 268 | MAGNITUDE=magnitude, |
| 269 | NORM_EPS=norm_eps, |
| 270 | HC_EPS=hc_eps, |
| 271 | num_warps=4, |
| 272 | enable_fp_fusion=False, |
| 273 | ) |
| 274 | return output, post |
| 275 | |
| 276 | |
| 277 | def triton_ihc_post( |
| 278 | x: torch.Tensor, |
| 279 | residual: torch.Tensor, |
| 280 | post: torch.Tensor, |
| 281 | ) -> torch.Tensor: |
| 282 | """Scatter a sub-block output back over the iHC residual channels.""" |
| 283 | assert x.dim() == 2 |
| 284 | assert x.is_cuda and residual.is_cuda and post.is_cuda |
| 285 | assert post.dtype == torch.float32 |
| 286 | |
| 287 | x = x.contiguous() |
| 288 | residual = residual.contiguous() |
| 289 | post = post.contiguous() |
| 290 | num_tokens, hidden_size = x.shape |
| 291 | hc_mult = post.shape[-1] |
| 292 | assert residual.shape == (num_tokens, hc_mult, hidden_size) |
| 293 | assert post.shape == (num_tokens, hc_mult) |
| 294 | |
| 295 | if num_tokens == 0: |
| 296 | return torch.empty((0, hc_mult, hidden_size), dtype=x.dtype, device=x.device) |
| 297 | |
| 298 | output = torch.empty( |
| 299 | (num_tokens, hc_mult, hidden_size), dtype=x.dtype, device=x.device |
| 300 | ) |
| 301 | _ihc_post_kernel[(num_tokens, triton.cdiv(hidden_size, _BLOCK_D))]( |
| 302 | x, |
| 303 | residual, |
| 304 | post, |
| 305 | output, |
| 306 | HIDDEN_SIZE=hidden_size, |
| 307 | HC_MULT=hc_mult, |
| 308 | HC_POW2=triton.next_power_of_2(hc_mult), |
| 309 | BLOCK_D=_BLOCK_D, |
| 310 | num_warps=4, |
| 311 | enable_fp_fusion=False, |
| 312 | ) |
| 313 | return output |