Merged
feat(mhc): migrate operator suite #11
wangliweiAdd on Sep 17
feat(mhc): migrate operator suite #11
Merged
Total: 31 files changed+8278-0
| @@ -94,6 +94,32 @@ engram_fused_weight(weight_hidden, weight_embed) | |||
| 94 | -> update grad_weight_hidden and grad_weight_embed in place | 94 | -> update grad_weight_hidden and grad_weight_embed in place |
| 95 | ``` | 95 | ``` |
| 96 | 96 | ||
| 97 | +MHC 训练链路由显式前向和反向原语组成: | ||
| 98 | + | ||
| 99 | +```text | ||
| 100 | +expand_to_mhc_forward(...) | ||
| 101 | + -> mhc_fn_normw_merge_forward(...) | ||
| 102 | + -> mhc_gemm_with_sqrsum_forward(...) | ||
| 103 | + -> mhc_reduce_partials_and_rmsnorm_forward(...) | ||
| 104 | + -> mhc_pre_split_mixes_forward(...) | ||
| 105 | + -> mhc_sinkhorn_forward(...) | ||
| 106 | + -> mhc_pre_apply_mix_forward(...) | ||
| 107 | + -> upper-layer computation | ||
| 108 | + -> mhc_post_forward(...) | ||
| 109 | + | ||
| 110 | +mhc_post_backward(...) | ||
| 111 | + -> upper-layer backward | ||
| 112 | + -> mhc_pre_apply_mix_backward(...) | ||
| 113 | + -> mhc_sinkhorn_backward(...) | ||
| 114 | + -> mhc_pre_split_mixes_backward(...) | ||
| 115 | + -> mhc_reduce_partials_and_rmsnorm_backward(...) | ||
| 116 | + -> mhc_gemm_with_sqrsum_backward(...) | ||
| 117 | + -> mhc_fn_normw_merge_backward(...) | ||
| 118 | + -> expand_to_mhc_backward(...) | ||
| 119 | +``` | ||
| 120 | + | ||
| 121 | +`mhc_head_compute_mix_forward` 和 `mhc_head_compute_mix_backward` 处理 head mixing 系数,`mhc_multilayer_recompute` 融合多层 pre/post mix 重计算。`mhc_pre_big_fuse` 融合 reduce/RMSNorm、split mixes、Sinkhorn 和 pre apply mix 的前向路径。 | ||
| 122 | + | ||
| 97 | ## 扩展方向 | 123 | ## 扩展方向 |
| 98 | 124 | ||
| 99 | - `src/cann_ops_tilelang/quant/`:公共 scaling-factor layout 和 FP8/FP4 类型系统; | 125 | - `src/cann_ops_tilelang/quant/`:公共 scaling-factor layout 和 FP8/FP4 类型系统; |
| @@ -9,6 +9,26 @@ __all__ = [ | |||
| 9 | 'engram_sinkhorn_finalize', | 9 | 'engram_sinkhorn_finalize', |
| 10 | 'engram_sinkhorn_momentum_update', | 10 | 'engram_sinkhorn_momentum_update', |
| 11 | 'engram_sinkhorn_step', | 11 | 'engram_sinkhorn_step', |
| 12 | + 'expand_to_mhc_backward', | ||
| 13 | + 'expand_to_mhc_forward', | ||
| 14 | + 'mhc_fn_normw_merge_backward', | ||
| 15 | + 'mhc_fn_normw_merge_forward', | ||
| 16 | + 'mhc_gemm_with_sqrsum_backward', | ||
| 17 | + 'mhc_gemm_with_sqrsum_forward', | ||
| 18 | + 'mhc_head_compute_mix_backward', | ||
| 19 | + 'mhc_head_compute_mix_forward', | ||
| 20 | + 'mhc_multilayer_recompute', | ||
| 21 | + 'mhc_post_backward', | ||
| 22 | + 'mhc_post_forward', | ||
| 23 | + 'mhc_pre_apply_mix_backward', | ||
| 24 | + 'mhc_pre_apply_mix_forward', | ||
| 25 | + 'mhc_pre_big_fuse', | ||
| 26 | + 'mhc_pre_split_mixes_backward', | ||
| 27 | + 'mhc_pre_split_mixes_forward', | ||
| 28 | + 'mhc_reduce_partials_and_rmsnorm_backward', | ||
| 29 | + 'mhc_reduce_partials_and_rmsnorm_forward', | ||
| 30 | + 'mhc_sinkhorn_backward', | ||
| 31 | + 'mhc_sinkhorn_forward', | ||
| 12 | 'moe_topk_gate', | 32 | 'moe_topk_gate', |
| 13 | 'normalize_weight', | 33 | 'normalize_weight', |
| 14 | 'topk_gate', | 34 | 'topk_gate', |
| @@ -24,4 +44,26 @@ from .engram import ( | |||
| 24 | engram_sinkhorn_momentum_update, | 44 | engram_sinkhorn_momentum_update, |
| 25 | engram_sinkhorn_step, | 45 | engram_sinkhorn_step, |
| 26 | ) | 46 | ) |
| 47 | +from .mhc import ( | ||
| 48 | + expand_to_mhc_backward, | ||
| 49 | + expand_to_mhc_forward, | ||
| 50 | + mhc_fn_normw_merge_backward, | ||
| 51 | + mhc_fn_normw_merge_forward, | ||
| 52 | + mhc_gemm_with_sqrsum_backward, | ||
| 53 | + mhc_gemm_with_sqrsum_forward, | ||
| 54 | + mhc_head_compute_mix_backward, | ||
| 55 | + mhc_head_compute_mix_forward, | ||
| 56 | + mhc_multilayer_recompute, | ||
| 57 | + mhc_post_backward, | ||
| 58 | + mhc_post_forward, | ||
| 59 | + mhc_pre_apply_mix_backward, | ||
| 60 | + mhc_pre_apply_mix_forward, | ||
| 61 | + mhc_pre_big_fuse, | ||
| 62 | + mhc_pre_split_mixes_backward, | ||
| 63 | + mhc_pre_split_mixes_forward, | ||
| 64 | + mhc_reduce_partials_and_rmsnorm_backward, | ||
| 65 | + mhc_reduce_partials_and_rmsnorm_forward, | ||
| 66 | + mhc_sinkhorn_backward, | ||
| 67 | + mhc_sinkhorn_forward, | ||
| 68 | +) | ||
| 27 | from .moe import moe_topk_gate, normalize_weight, topk_gate | 69 | from .moe import moe_topk_gate, normalize_weight, topk_gate |
| @@ -0,0 +1,41 @@ | |||
| 1 | +"""MHC operators.""" | ||
| 2 | + | ||
| 3 | +__all__ = [ | ||
| 4 | + 'expand_to_mhc_backward', | ||
| 5 | + 'expand_to_mhc_forward', | ||
| 6 | + 'mhc_fn_normw_merge_backward', | ||
| 7 | + 'mhc_fn_normw_merge_forward', | ||
| 8 | + 'mhc_gemm_with_sqrsum_backward', | ||
| 9 | + 'mhc_gemm_with_sqrsum_forward', | ||
| 10 | + 'mhc_head_compute_mix_backward', | ||
| 11 | + 'mhc_head_compute_mix_forward', | ||
| 12 | + 'mhc_multilayer_recompute', | ||
| 13 | + 'mhc_post_backward', | ||
| 14 | + 'mhc_post_forward', | ||
| 15 | + 'mhc_pre_apply_mix_backward', | ||
| 16 | + 'mhc_pre_apply_mix_forward', | ||
| 17 | + 'mhc_pre_big_fuse', | ||
| 18 | + 'mhc_pre_split_mixes_backward', | ||
| 19 | + 'mhc_pre_split_mixes_forward', | ||
| 20 | + 'mhc_reduce_partials_and_rmsnorm_backward', | ||
| 21 | + 'mhc_reduce_partials_and_rmsnorm_forward', | ||
| 22 | + 'mhc_sinkhorn_backward', | ||
| 23 | + 'mhc_sinkhorn_forward', | ||
| 24 | +] | ||
| 25 | + | ||
| 26 | +from .expand import expand_to_mhc_backward, expand_to_mhc_forward | ||
| 27 | +from .head_compute_mix import mhc_head_compute_mix_backward, mhc_head_compute_mix_forward | ||
| 28 | +from .multilayer_recompute import mhc_multilayer_recompute | ||
| 29 | +from .norm_fn import ( | ||
| 30 | + mhc_fn_normw_merge_backward, | ||
| 31 | + mhc_fn_normw_merge_forward, | ||
| 32 | + mhc_gemm_with_sqrsum_backward, | ||
| 33 | + mhc_gemm_with_sqrsum_forward, | ||
| 34 | + mhc_reduce_partials_and_rmsnorm_backward, | ||
| 35 | + mhc_reduce_partials_and_rmsnorm_forward, | ||
| 36 | +) | ||
| 37 | +from .post import mhc_post_backward, mhc_post_forward | ||
| 38 | +from .pre_apply_mix import mhc_pre_apply_mix_backward, mhc_pre_apply_mix_forward | ||
| 39 | +from .pre_big_fuse import mhc_pre_big_fuse | ||
| 40 | +from .pre_split_mixes import mhc_pre_split_mixes_backward, mhc_pre_split_mixes_forward | ||
| 41 | +from .sinkhorn import mhc_sinkhorn_backward, mhc_sinkhorn_forward | ||
| @@ -0,0 +1,128 @@ | |||
| 1 | +"""Public interfaces for expanding activations across MHC streams.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +import torch | ||
| 6 | + | ||
| 7 | +from cann_ops_tilelang.config import get_num_vector_cores, require_ascend | ||
| 8 | + | ||
| 9 | +from .expand_kernel import expand_to_mhc_backward_kernel, expand_to_mhc_forward_kernel | ||
| 10 | + | ||
| 11 | +_FORWARD_HIDDEN_ALIGNMENT = 128 | ||
| 12 | +_BACKWARD_HIDDEN_ALIGNMENT = 64 | ||
| 13 | +_FORWARD_NUM_STAGES = 2 | ||
| 14 | +_BACKWARD_NUM_STAGES = 3 | ||
| 15 | +_BACKWARD_HIDDEN_BLOCK = 4096 | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +def _check_npu_bfloat16_tensor( | ||
| 19 | + name: str, | ||
| 20 | + tensor: torch.Tensor, | ||
| 21 | + *, | ||
| 22 | + shape: tuple[int, ...] | None = None, | ||
| 23 | + device: torch.device | None = None, | ||
| 24 | +) -> None: | ||
| 25 | + if not isinstance(tensor, torch.Tensor): | ||
| 26 | + raise TypeError(f'{name} must be a torch.Tensor.') | ||
| 27 | + if tensor.device.type != 'npu': | ||
| 28 | + raise ValueError(f'{name} must be an NPU tensor, got {tensor.device}.') | ||
| 29 | + if device is not None and tensor.device != device: | ||
| 30 | + raise ValueError(f'{name} must be on {device}, got {tensor.device}.') | ||
| 31 | + if tensor.dtype != torch.bfloat16: | ||
| 32 | + raise TypeError(f'{name} must use torch.bfloat16, got {tensor.dtype}.') | ||
| 33 | + if shape is not None and tuple(tensor.shape) != shape: | ||
| 34 | + raise ValueError(f'{name} must have shape {shape}, got {tuple(tensor.shape)}.') | ||
| 35 | + if not tensor.is_contiguous(): | ||
| 36 | + raise ValueError(f'{name} must be contiguous.') | ||
| 37 | + | ||
| 38 | + | ||
| 39 | +def _shares_storage(left: torch.Tensor, right: torch.Tensor) -> bool: | ||
| 40 | + return ( | ||
| 41 | + left.numel() > 0 | ||
| 42 | + and right.numel() > 0 | ||
| 43 | + and left.untyped_storage().data_ptr() == right.untyped_storage().data_ptr() | ||
| 44 | + ) | ||
| 45 | + | ||
| 46 | + | ||
| 47 | +def _forward_hidden_block(num_tokens: int, hidden_size: int) -> int: | ||
| 48 | + is_short = num_tokens <= 512 | ||
| 49 | + if is_short and hidden_size >= 7168: | ||
| 50 | + return 1024 | ||
| 51 | + if is_short or hidden_size <= 4096: | ||
| 52 | + return 512 | ||
| 53 | + return 1024 | ||
| 54 | + | ||
| 55 | + | ||
| 56 | +def expand_to_mhc_forward(x: torch.Tensor, output: torch.Tensor) -> None: | ||
| 57 | + """Copy every BF16 activation row into each MHC stream on Ascend.""" | ||
| 58 | + | ||
| 59 | + require_ascend() | ||
| 60 | + if not isinstance(x, torch.Tensor): | ||
| 61 | + raise TypeError('x must be a torch.Tensor.') | ||
| 62 | + if x.ndim != 2: | ||
| 63 | + raise ValueError(f'x must have shape (num_tokens, hidden_size), got {tuple(x.shape)}.') | ||
| 64 | + num_tokens, hidden_size = x.shape | ||
| 65 | + if hidden_size <= 0 or hidden_size % _FORWARD_HIDDEN_ALIGNMENT: | ||
| 66 | + raise ValueError(f'hidden_size must be a positive multiple of {_FORWARD_HIDDEN_ALIGNMENT}, got {hidden_size}.') | ||
| 67 | + if not isinstance(output, torch.Tensor): | ||
| 68 | + raise TypeError('output must be a torch.Tensor.') | ||
| 69 | + if output.ndim != 3: | ||
| 70 | + raise ValueError('output must have shape (num_tokens, mhc_mult, hidden_size).') | ||
| 71 | + mhc_mult = output.shape[1] | ||
| 72 | + if mhc_mult <= 0: | ||
| 73 | + raise ValueError(f'mhc_mult must be positive, got {mhc_mult}.') | ||
| 74 | + | ||
| 75 | + _check_npu_bfloat16_tensor('x', x) | ||
| 76 | + _check_npu_bfloat16_tensor( | ||
| 77 | + 'output', | ||
| 78 | + output, | ||
| 79 | + shape=(num_tokens, mhc_mult, hidden_size), | ||
| 80 | + device=x.device, | ||
| 81 | + ) | ||
| 82 | + if _shares_storage(output, x): | ||
| 83 | + raise ValueError('output and x must not share storage.') | ||
| 84 | + if num_tokens == 0: | ||
| 85 | + return | ||
| 86 | + | ||
| 87 | + expand_to_mhc_forward_kernel( | ||
| 88 | + x, | ||
| 89 | + output, | ||
| 90 | + hidden_block=_forward_hidden_block(num_tokens, hidden_size), | ||
| 91 | + num_stages=_FORWARD_NUM_STAGES, | ||
| 92 | + num_vec_cores=get_num_vector_cores(), | ||
| 93 | + ) | ||
| 94 | + | ||
| 95 | + | ||
| 96 | +def expand_to_mhc_backward(output_grad: torch.Tensor, x_grad: torch.Tensor) -> None: | ||
| 97 | + """Sum BF16 MHC stream gradients into a caller-owned activation gradient.""" | ||
| 98 | + | ||
| 99 | + require_ascend() | ||
| 100 | + if not isinstance(output_grad, torch.Tensor): | ||
| 101 | + raise TypeError('output_grad must be a torch.Tensor.') | ||
| 102 | + if output_grad.ndim != 3: | ||
| 103 | + raise ValueError('output_grad must have shape (num_tokens, mhc_mult, hidden_size).') | ||
| 104 | + num_tokens, mhc_mult, hidden_size = output_grad.shape | ||
| 105 | + if mhc_mult <= 0: | ||
| 106 | + raise ValueError(f'mhc_mult must be positive, got {mhc_mult}.') | ||
| 107 | + if hidden_size <= 0 or hidden_size % _BACKWARD_HIDDEN_ALIGNMENT: | ||
| 108 | + raise ValueError(f'hidden_size must be a positive multiple of {_BACKWARD_HIDDEN_ALIGNMENT}, got {hidden_size}.') | ||
| 109 | + | ||
| 110 | + _check_npu_bfloat16_tensor('output_grad', output_grad) | ||
| 111 | + _check_npu_bfloat16_tensor( | ||
| 112 | + 'x_grad', | ||
| 113 | + x_grad, | ||
| 114 | + shape=(num_tokens, hidden_size), | ||
| 115 | + device=output_grad.device, | ||
| 116 | + ) | ||
| 117 | + if _shares_storage(x_grad, output_grad): | ||
| 118 | + raise ValueError('x_grad and output_grad must not share storage.') | ||
| 119 | + if num_tokens == 0: | ||
| 120 | + return | ||
| 121 | + | ||
| 122 | + expand_to_mhc_backward_kernel( | ||
| 123 | + output_grad, | ||
| 124 | + x_grad, | ||
| 125 | + hidden_block=_BACKWARD_HIDDEN_BLOCK, | ||
| 126 | + num_stages=_BACKWARD_NUM_STAGES, | ||
| 127 | + num_vec_cores=get_num_vector_cores(), | ||
| 128 | + ) | ||
| @@ -0,0 +1,97 @@ | |||
| 1 | +"""TileLang kernels for expanding activations across MHC streams.""" | ||
| 2 | + | ||
| 3 | +import tilelang | ||
| 4 | +from tilelang.ascend import language as T | ||
| 5 | + | ||
| 6 | +_VECTOR_WIDTH = 64 | ||
| 7 | +_FORWARD_TOKEN_BLOCK = 32 | ||
| 8 | + | ||
| 9 | +_PASS_CONFIGS = { | ||
| 10 | + tilelang.PassConfigKey.TL_DISABLE_LOOP_UNSWITCHING: True, | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +@tilelang.jit(pass_configs=_PASS_CONFIGS) | ||
| 15 | +def expand_to_mhc_forward_kernel(x, output, hidden_block: int, num_stages: int, num_vec_cores: int): | ||
| 16 | + num_tokens = T.dynamic('num_tokens') | ||
| 17 | + hidden_size, mhc_mult = T.const('hidden_size, mhc_mult') | ||
| 18 | + x: T.Tensor[(num_tokens, hidden_size), T.bfloat16] | ||
| 19 | + output: T.Tensor[(num_tokens, mhc_mult, hidden_size), T.bfloat16] | ||
| 20 | + | ||
| 21 | + with T.Kernel(num_vec_cores) as core_id: | ||
| 22 | + x_ub = T.alloc_shared((_FORWARD_TOKEN_BLOCK, hidden_block), T.bfloat16) | ||
| 23 | + T.annotate_buffer_versions({x_ub: num_stages}) | ||
| 24 | + | ||
| 25 | + for token_index, hidden_index in T.Persistent( | ||
| 26 | + [T.ceildiv(num_tokens, _FORWARD_TOKEN_BLOCK), T.ceildiv(hidden_size, hidden_block)], | ||
| 27 | + num_vec_cores, | ||
| 28 | + core_id, | ||
| 29 | + group_size=1, | ||
| 30 | + num_stages=num_stages, | ||
| 31 | + ): | ||
| 32 | + row_base = token_index * _FORWARD_TOKEN_BLOCK | ||
| 33 | + column_base = hidden_index * hidden_block | ||
| 34 | + T.copy(x[row_base, column_base], x_ub) | ||
| 35 | + for mhc_index in T.serial(mhc_mult): | ||
| 36 | + T.copy( | ||
| 37 | + x_ub, | ||
| 38 | + output[ | ||
| 39 | + row_base : row_base + _FORWARD_TOKEN_BLOCK, | ||
| 40 | + mhc_index, | ||
| 41 | + column_base : column_base + hidden_block, | ||
| 42 | + ], | ||
| 43 | + ) | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +@tilelang.jit(pass_configs=_PASS_CONFIGS) | ||
| 47 | +def expand_to_mhc_backward_kernel( | ||
| 48 | + output_grad, | ||
| 49 | + x_grad, | ||
| 50 | + hidden_block: int, | ||
| 51 | + num_stages: int, | ||
| 52 | + num_vec_cores: int, | ||
| 53 | +): | ||
| 54 | + num_tokens = T.dynamic('num_tokens') | ||
| 55 | + hidden_size, mhc_mult = T.const('hidden_size, mhc_mult') | ||
| 56 | + output_grad: T.Tensor[(num_tokens, mhc_mult, hidden_size), T.bfloat16] | ||
| 57 | + x_grad: T.Tensor[(num_tokens, hidden_size), T.bfloat16] | ||
| 58 | + | ||
| 59 | + with T.Kernel(num_vec_cores) as core_id: | ||
| 60 | + output_grad_ub = T.alloc_shared((mhc_mult, hidden_block), T.bfloat16) | ||
| 61 | + x_grad_ub = T.alloc_shared((hidden_block,), T.bfloat16) | ||
| 62 | + T.annotate_buffer_versions({output_grad_ub: num_stages, x_grad_ub: num_stages}) | ||
| 63 | + | ||
| 64 | + for token_index, hidden_index in T.Persistent( | ||
| 65 | + [num_tokens, T.ceildiv(hidden_size, hidden_block)], | ||
| 66 | + num_vec_cores, | ||
| 67 | + core_id, | ||
| 68 | + group_size=1, | ||
| 69 | + num_stages=num_stages, | ||
| 70 | + ): | ||
| 71 | + T.copy(output_grad[token_index, 0, hidden_index * hidden_block], output_grad_ub) | ||
| 72 | + | ||
| 73 | + with T.SimdVF(): | ||
| 74 | + accumulator = T.simd.alloc_var('float32') | ||
| 75 | + ones = T.simd.vdup(1.0, T.float32) | ||
| 76 | + num_chunks = hidden_block // _VECTOR_WIDTH | ||
| 77 | + for chunk in range(num_chunks): | ||
| 78 | + column = chunk * _VECTOR_WIDTH | ||
| 79 | + accumulator = T.simd.vcvt( | ||
| 80 | + T.simd.vld(output_grad_ub[0, column], dist='UNPK_B16'), | ||
| 81 | + T.float32, | ||
| 82 | + part=0, | ||
| 83 | + ) | ||
| 84 | + for mhc_index in T.unroll(mhc_mult - 1, explicit=True): | ||
| 85 | + output_grad_value = T.simd.vcvt( | ||
| 86 | + T.simd.vld(output_grad_ub[mhc_index + 1, column], dist='UNPK_B16'), | ||
| 87 | + T.float32, | ||
| 88 | + part=0, | ||
| 89 | + ) | ||
| 90 | + T.simd.vmula(accumulator, output_grad_value, ones) | ||
| 91 | + T.simd.vsts( | ||
| 92 | + x_grad_ub[column], | ||
| 93 | + T.simd.vcvt(accumulator, T.bfloat16), | ||
| 94 | + dist='PK_B32', | ||
| 95 | + ) | ||
| 96 | + | ||
| 97 | + T.copy(x_grad_ub, x_grad[token_index, hidden_index * hidden_block]) | ||
| @@ -0,0 +1,199 @@ | |||
| 1 | +"""Public interfaces for the MHC head mix transform.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +import math | ||
| 6 | +from numbers import Real | ||
| 7 | + | ||
| 8 | +import torch | ||
| 9 | + | ||
| 10 | +from cann_ops_tilelang.config import get_num_vector_cores, require_ascend | ||
| 11 | + | ||
| 12 | +from .head_compute_mix_kernel import ( | ||
| 13 | + mhc_head_compute_mix_backward_kernel, | ||
| 14 | + mhc_head_compute_mix_forward_kernel, | ||
| 15 | +) | ||
| 16 | + | ||
| 17 | +_MHC_MULT = 4 | ||
| 18 | +_MAX_TOKEN_BLOCK_SIZE = 64 | ||
| 19 | +_ASCEND_TOKEN_BLOCK_SIZE = 512 | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +def _check_npu_fp32_tensor( | ||
| 23 | + name: str, | ||
| 24 | + tensor: torch.Tensor, | ||
| 25 | + *, | ||
| 26 | + shape: tuple[int, ...], | ||
| 27 | + device: torch.device | None = None, | ||
| 28 | +) -> None: | ||
| 29 | + if not isinstance(tensor, torch.Tensor): | ||
| 30 | + raise TypeError(f'{name} must be a torch.Tensor.') | ||
| 31 | + if tensor.device.type != 'npu': | ||
| 32 | + raise ValueError(f'{name} must be an NPU tensor, got {tensor.device}.') | ||
| 33 | + if device is not None and tensor.device != device: | ||
| 34 | + raise ValueError(f'{name} must be on {device}, got {tensor.device}.') | ||
| 35 | + if tensor.dtype != torch.float32: | ||
| 36 | + raise TypeError(f'{name} must use torch.float32, got {tensor.dtype}.') | ||
| 37 | + if tuple(tensor.shape) != shape: | ||
| 38 | + raise ValueError(f'{name} must have shape {shape}, got {tuple(tensor.shape)}.') | ||
| 39 | + if not tensor.is_contiguous(): | ||
| 40 | + raise ValueError(f'{name} must be contiguous.') | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +def _check_eps(value: float) -> float: | ||
| 44 | + if isinstance(value, bool) or not isinstance(value, Real): | ||
| 45 | + raise TypeError('mhc_pre_eps must be a real number.') | ||
| 46 | + result = float(value) | ||
| 47 | + if not math.isfinite(result) or result < 0: | ||
| 48 | + raise ValueError('mhc_pre_eps must be finite and non-negative.') | ||
| 49 | + return result | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def _check_num_vec_cores(value: int) -> int: | ||
| 53 | + if isinstance(value, bool) or not isinstance(value, int): | ||
| 54 | + raise TypeError('num_vec_cores must be an integer.') | ||
| 55 | + detected = get_num_vector_cores() | ||
| 56 | + if value <= 0 or value > detected: | ||
| 57 | + raise ValueError(f'num_vec_cores must be in [1, {detected}], got {value}.') | ||
| 58 | + return value | ||
| 59 | + | ||
| 60 | + | ||
| 61 | +def _choose_token_block_size(token_block_size: int) -> int: | ||
| 62 | + if isinstance(token_block_size, bool) or not isinstance(token_block_size, int): | ||
| 63 | + raise TypeError('token_block_size must be an integer.') | ||
| 64 | + if token_block_size < 0: | ||
| 65 | + raise ValueError(f'token_block_size must be non-negative, got {token_block_size}.') | ||
| 66 | + if token_block_size and (token_block_size > _MAX_TOKEN_BLOCK_SIZE or token_block_size & (token_block_size - 1)): | ||
| 67 | + raise ValueError(f'token_block_size must be a power of two no greater than {_MAX_TOKEN_BLOCK_SIZE}.') | ||
| 68 | + # Match TileKernels-Nightly's Ascend specialization. The public tuning | ||
| 69 | + # argument remains accepted and validated for API compatibility, but the | ||
| 70 | + # Ascend implementation always uses its optimized 512-token tile. | ||
| 71 | + return _ASCEND_TOKEN_BLOCK_SIZE | ||
| 72 | + | ||
| 73 | + | ||
| 74 | +def _shares_storage(left: torch.Tensor, right: torch.Tensor) -> bool: | ||
| 75 | + return ( | ||
| 76 | + left.numel() > 0 | ||
| 77 | + and right.numel() > 0 | ||
| 78 | + and left.untyped_storage().data_ptr() == right.untyped_storage().data_ptr() | ||
| 79 | + ) | ||
| 80 | + | ||
| 81 | + | ||
| 82 | +def _check_output_aliases( | ||
| 83 | + inputs: tuple[tuple[str, torch.Tensor], ...], | ||
| 84 | + outputs: tuple[tuple[str, torch.Tensor], ...], | ||
| 85 | +) -> None: | ||
| 86 | + for output_index, (output_name, output) in enumerate(outputs): | ||
| 87 | + for other_name, other in inputs + outputs[:output_index]: | ||
| 88 | + if _shares_storage(output, other): | ||
| 89 | + raise ValueError(f'{output_name} and {other_name} must not share storage.') | ||
| 90 | + | ||
| 91 | + | ||
| 92 | +def _validate_inputs( | ||
| 93 | + input_mix: torch.Tensor, | ||
| 94 | + mhc_scale: torch.Tensor, | ||
| 95 | + mhc_base: torch.Tensor, | ||
| 96 | +) -> tuple[int, torch.device]: | ||
| 97 | + if not isinstance(input_mix, torch.Tensor): | ||
| 98 | + raise TypeError('input_mix must be a torch.Tensor.') | ||
| 99 | + if input_mix.ndim != 2 or input_mix.shape[1] != _MHC_MULT: | ||
| 100 | + raise ValueError(f'input_mix must have shape (num_tokens, {_MHC_MULT}).') | ||
| 101 | + num_tokens = input_mix.shape[0] | ||
| 102 | + device = input_mix.device | ||
| 103 | + _check_npu_fp32_tensor('input_mix', input_mix, shape=(num_tokens, _MHC_MULT)) | ||
| 104 | + _check_npu_fp32_tensor('mhc_scale', mhc_scale, shape=(1,), device=device) | ||
| 105 | + _check_npu_fp32_tensor('mhc_base', mhc_base, shape=(_MHC_MULT,), device=device) | ||
| 106 | + return num_tokens, device | ||
| 107 | + | ||
| 108 | + | ||
| 109 | +def mhc_head_compute_mix_forward( | ||
| 110 | + input_mix: torch.Tensor, | ||
| 111 | + mhc_scale: torch.Tensor, | ||
| 112 | + mhc_base: torch.Tensor, | ||
| 113 | + output_mix: torch.Tensor, | ||
| 114 | + mhc_pre_eps: float, | ||
| 115 | + token_block_size: int = 0, | ||
| 116 | +) -> None: | ||
| 117 | + """Apply the FP32 affine sigmoid transform for four MHC streams.""" | ||
| 118 | + | ||
| 119 | + eps = _check_eps(mhc_pre_eps) | ||
| 120 | + require_ascend() | ||
| 121 | + num_tokens, device = _validate_inputs(input_mix, mhc_scale, mhc_base) | ||
| 122 | + _check_npu_fp32_tensor( | ||
| 123 | + 'output_mix', | ||
| 124 | + output_mix, | ||
| 125 | + shape=(num_tokens, _MHC_MULT), | ||
| 126 | + device=device, | ||
| 127 | + ) | ||
| 128 | + _check_output_aliases( | ||
| 129 | + (('input_mix', input_mix), ('mhc_scale', mhc_scale), ('mhc_base', mhc_base)), | ||
| 130 | + (('output_mix', output_mix),), | ||
| 131 | + ) | ||
| 132 | + selected_token_block_size = _choose_token_block_size(token_block_size) | ||
| 133 | + if num_tokens == 0: | ||
| 134 | + return | ||
| 135 | + | ||
| 136 | + mhc_head_compute_mix_forward_kernel( | ||
| 137 | + input_mix, | ||
| 138 | + mhc_scale, | ||
| 139 | + mhc_base, | ||
| 140 | + output_mix, | ||
| 141 | + eps, | ||
| 142 | + token_block_size=selected_token_block_size, | ||
| 143 | + ) | ||
| 144 | + | ||
| 145 | + | ||
| 146 | +def mhc_head_compute_mix_backward( | ||
| 147 | + output_mix_grad: torch.Tensor, | ||
| 148 | + input_mix: torch.Tensor, | ||
| 149 | + mhc_scale: torch.Tensor, | ||
| 150 | + mhc_base: torch.Tensor, | ||
| 151 | + input_mix_grad: torch.Tensor, | ||
| 152 | + mhc_scale_grad_partial: torch.Tensor, | ||
| 153 | + mhc_base_grad_partial: torch.Tensor, | ||
| 154 | + num_vec_cores: int, | ||
| 155 | + token_block_size: int = 0, | ||
| 156 | +) -> None: | ||
| 157 | + """Compute input and partial parameter gradients for the MHC head mix.""" | ||
| 158 | + | ||
| 159 | + require_ascend() | ||
| 160 | + num_vec_cores = _check_num_vec_cores(num_vec_cores) | ||
| 161 | + num_tokens, device = _validate_inputs(input_mix, mhc_scale, mhc_base) | ||
| 162 | + tensor_specs = ( | ||
| 163 | + ('output_mix_grad', output_mix_grad, (num_tokens, _MHC_MULT)), | ||
| 164 | + ('input_mix_grad', input_mix_grad, (num_tokens, _MHC_MULT)), | ||
| 165 | + ('mhc_scale_grad_partial', mhc_scale_grad_partial, (num_vec_cores, 1)), | ||
| 166 | + ('mhc_base_grad_partial', mhc_base_grad_partial, (num_vec_cores, _MHC_MULT)), | ||
| 167 | + ) | ||
| 168 | + for name, tensor, shape in tensor_specs: | ||
| 169 | + _check_npu_fp32_tensor(name, tensor, shape=shape, device=device) | ||
| 170 | + | ||
| 171 | + inputs = ( | ||
| 172 | + ('output_mix_grad', output_mix_grad), | ||
| 173 | + ('input_mix', input_mix), | ||
| 174 | + ('mhc_scale', mhc_scale), | ||
| 175 | + ('mhc_base', mhc_base), | ||
| 176 | + ) | ||
| 177 | + outputs = ( | ||
| 178 | + ('input_mix_grad', input_mix_grad), | ||
| 179 | + ('mhc_scale_grad_partial', mhc_scale_grad_partial), | ||
| 180 | + ('mhc_base_grad_partial', mhc_base_grad_partial), | ||
| 181 | + ) | ||
| 182 | + _check_output_aliases(inputs, outputs) | ||
| 183 | + selected_token_block_size = _choose_token_block_size(token_block_size) | ||
| 184 | + if num_tokens == 0: | ||
| 185 | + mhc_scale_grad_partial.zero_() | ||
| 186 | + mhc_base_grad_partial.zero_() | ||
| 187 | + return | ||
| 188 | + | ||
| 189 | + mhc_head_compute_mix_backward_kernel( | ||
| 190 | + output_mix_grad, | ||
| 191 | + input_mix, | ||
| 192 | + mhc_scale, | ||
| 193 | + mhc_base, | ||
| 194 | + input_mix_grad, | ||
| 195 | + mhc_scale_grad_partial, | ||
| 196 | + mhc_base_grad_partial, | ||
| 197 | + token_block_size=selected_token_block_size, | ||
| 198 | + num_vec_cores=num_vec_cores, | ||
| 199 | + ) | ||
| @@ -0,0 +1,230 @@ | |||
| 1 | +"""TileLang kernels for the MHC head mix transform.""" | ||
| 2 | + | ||
| 3 | +import tilelang | ||
| 4 | +from tilelang.ascend import language as T | ||
| 5 | +from tilelang.ascend.language import simd as S | ||
| 6 | + | ||
| 7 | +_VECTOR_WIDTH = 64 | ||
| 8 | +_PASS_CONFIGS = { | ||
| 9 | + tilelang.PassConfigKey.TL_ENABLE_FAST_MATH: True, | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +@T.macro() | ||
| 14 | +def _split_accumulators(base_acc, width: int) -> None: | ||
| 15 | + """Split interleaved component accumulators once in the reduction tree.""" | ||
| 16 | + for component in T.unroll(width, explicit=True): | ||
| 17 | + low, high = S.vdintlv(base_acc[component], base_acc[component]) | ||
| 18 | + base_acc[component] = low | ||
| 19 | + base_acc[component + width] = high | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +@T.macro | ||
| 23 | +def _split_accumulator_tree(base_acc, mhc_mult: int) -> None: | ||
| 24 | + """Separate interleaved accumulators into one accumulator per component.""" | ||
| 25 | + if mhc_mult > 2: | ||
| 26 | + _split_accumulators(base_acc, 2) | ||
| 27 | + if mhc_mult > 4: | ||
| 28 | + _split_accumulators(base_acc, 4) | ||
| 29 | + if mhc_mult > 8: | ||
| 30 | + _split_accumulators(base_acc, 8) | ||
| 31 | + if mhc_mult > 16: | ||
| 32 | + _split_accumulators(base_acc, 16) | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +@tilelang.jit(pass_configs=_PASS_CONFIGS) | ||
| 36 | +def mhc_head_compute_mix_forward_kernel( | ||
| 37 | + input_mix, | ||
| 38 | + mhc_scale, | ||
| 39 | + mhc_base, | ||
| 40 | + output_mix, | ||
| 41 | + mhc_pre_eps: float, | ||
| 42 | + token_block_size: int, | ||
| 43 | +): | ||
| 44 | + num_tokens = T.dynamic('num_tokens') | ||
| 45 | + mhc_mult = T.const('mhc_mult') | ||
| 46 | + | ||
| 47 | + input_mix: T.Tensor[(num_tokens, mhc_mult), T.float32] | ||
| 48 | + mhc_scale: T.Tensor[(1,), T.float32] | ||
| 49 | + mhc_base: T.Tensor[(mhc_mult,), T.float32] | ||
| 50 | + output_mix: T.Tensor[(num_tokens, mhc_mult), T.float32] | ||
| 51 | + | ||
| 52 | + with T.Kernel(T.ceildiv(num_tokens, token_block_size)) as block_id: | ||
| 53 | + input_ub = T.alloc_shared((token_block_size, mhc_mult), T.float32) | ||
| 54 | + output_ub = T.alloc_shared((token_block_size, mhc_mult), T.float32) | ||
| 55 | + scale_ub = T.alloc_shared((8,), T.float32) | ||
| 56 | + base_ub = T.alloc_shared((T.ceildiv(mhc_mult, 8) * 8,), T.float32) | ||
| 57 | + | ||
| 58 | + token_offset = block_id * token_block_size | ||
| 59 | + valid_tokens = T.min(token_block_size, num_tokens - token_offset) | ||
| 60 | + | ||
| 61 | + T.copy(mhc_scale[0], scale_ub[0:1]) | ||
| 62 | + T.copy(mhc_base[0:mhc_mult], base_ub[0:mhc_mult]) | ||
| 63 | + T.copy( | ||
| 64 | + input_mix[token_offset : token_offset + valid_tokens, 0:mhc_mult], | ||
| 65 | + input_ub[0:valid_tokens, 0:mhc_mult], | ||
| 66 | + ) | ||
| 67 | + | ||
| 68 | + with T.SimdVF(): | ||
| 69 | + zeros = S.vdup(T.float32(0.0), T.float32) | ||
| 70 | + ones = S.vdup(T.float32(1.0), T.float32) | ||
| 71 | + scale = S.vld(scale_ub[0], dist='BRC_B32') | ||
| 72 | + base_indices = S.vand( | ||
| 73 | + T.reinterpret(S.vci(T.int32(0), T.int32), 'uint32x64'), | ||
| 74 | + S.vdup(T.uint32(mhc_mult - 1), T.uint32), | ||
| 75 | + ) | ||
| 76 | + base_pattern = S.vgather2(base_ub[0], base_indices) | ||
| 77 | + for vector_id in T.serial(token_block_size * mhc_mult // _VECTOR_WIDTH): | ||
| 78 | + row = vector_id * _VECTOR_WIDTH // mhc_mult | ||
| 79 | + x = S.vld(input_ub[row, 0]) | ||
| 80 | + affine = S.vadd(S.vmul(x, scale), base_pattern) | ||
| 81 | + sigmoid = S.vdiv(ones, S.vadds(S.vexpdif(zeros, affine), 1.0)) | ||
| 82 | + S.vsts(output_ub[row, 0], S.vadds(sigmoid, mhc_pre_eps)) | ||
| 83 | + | ||
| 84 | + T.copy( | ||
| 85 | + output_ub[0:valid_tokens, 0:mhc_mult], | ||
| 86 | + output_mix[token_offset : token_offset + valid_tokens, 0:mhc_mult], | ||
| 87 | + ) | ||
| 88 | + | ||
| 89 | + | ||
| 90 | +@tilelang.jit(pass_configs=_PASS_CONFIGS) | ||
| 91 | +def mhc_head_compute_mix_backward_kernel( | ||
| 92 | + output_mix_grad, | ||
| 93 | + input_mix, | ||
| 94 | + mhc_scale, | ||
| 95 | + mhc_base, | ||
| 96 | + input_mix_grad, | ||
| 97 | + mhc_scale_grad_partial, | ||
| 98 | + mhc_base_grad_partial, | ||
| 99 | + token_block_size: int, | ||
| 100 | + num_vec_cores: int, | ||
| 101 | +): | ||
| 102 | + num_tokens = T.dynamic('num_tokens') | ||
| 103 | + mhc_mult = T.const('mhc_mult') | ||
| 104 | + | ||
| 105 | + output_mix_grad: T.Tensor[(num_tokens, mhc_mult), T.float32] | ||
| 106 | + input_mix: T.Tensor[(num_tokens, mhc_mult), T.float32] | ||
| 107 | + mhc_scale: T.Tensor[(1,), T.float32] | ||
| 108 | + mhc_base: T.Tensor[(mhc_mult,), T.float32] | ||
| 109 | + input_mix_grad: T.Tensor[(num_tokens, mhc_mult), T.float32] | ||
| 110 | + mhc_scale_grad_partial: T.Tensor[(num_vec_cores, 1), T.float32] | ||
| 111 | + mhc_base_grad_partial: T.Tensor[(num_vec_cores, mhc_mult), T.float32] | ||
| 112 | + | ||
| 113 | + num_tiles = T.ceildiv(num_tokens, token_block_size) | ||
| 114 | + | ||
| 115 | + with T.Kernel(num_vec_cores) as block_id: | ||
| 116 | + output_grad_ub = T.alloc_shared((token_block_size, mhc_mult), T.float32) | ||
| 117 | + input_ub = T.alloc_shared((token_block_size, mhc_mult), T.float32) | ||
| 118 | + scale_ub = T.alloc_shared((8,), T.float32) | ||
| 119 | + base_ub = T.alloc_shared((T.ceildiv(mhc_mult, 8) * 8,), T.float32) | ||
| 120 | + input_grad_ub = T.alloc_shared((token_block_size, mhc_mult), T.float32) | ||
| 121 | + scale_grad_partial_ub = T.alloc_shared((8,), T.float32) | ||
| 122 | + base_grad_partial_ub = T.alloc_shared((T.ceildiv(mhc_mult, 8) * 8,), T.float32) | ||
| 123 | + scale_acc_ub = T.alloc_shared((_VECTOR_WIDTH,), T.float32) | ||
| 124 | + seed_acc_ub = T.alloc_shared((2, _VECTOR_WIDTH), T.float32) | ||
| 125 | + | ||
| 126 | + T.copy(mhc_base[0:mhc_mult], base_ub[0:mhc_mult]) | ||
| 127 | + T.copy(mhc_scale[0], scale_ub[0:1]) | ||
| 128 | + | ||
| 129 | + with T.SimtVF(threads=_VECTOR_WIDTH): | ||
| 130 | + T.clear(scale_acc_ub) | ||
| 131 | + T.clear(seed_acc_ub) | ||
| 132 | + | ||
| 133 | + for tile_id in T.Persistent( | ||
| 134 | + [num_tiles], | ||
| 135 | + num_vec_cores, | ||
| 136 | + block_id, | ||
| 137 | + group_size=1, | ||
| 138 | + num_stages=2, | ||
| 139 | + annotations={'multi_buffer_eligible': [output_grad_ub, input_ub, input_grad_ub]}, | ||
| 140 | + ): | ||
| 141 | + token_offset = tile_id * token_block_size | ||
| 142 | + valid_tokens = T.min(token_block_size, num_tokens - token_offset) | ||
| 143 | + | ||
| 144 | + T.copy( | ||
| 145 | + output_mix_grad[token_offset : token_offset + valid_tokens, 0:mhc_mult], | ||
| 146 | + output_grad_ub[0:valid_tokens, 0:mhc_mult], | ||
| 147 | + ) | ||
| 148 | + T.copy( | ||
| 149 | + input_mix[token_offset : token_offset + valid_tokens, 0:mhc_mult], | ||
| 150 | + input_ub[0:valid_tokens, 0:mhc_mult], | ||
| 151 | + ) | ||
| 152 | + | ||
| 153 | + with T.SimdVF(): | ||
| 154 | + zeros = S.vdup(T.float32(0.0), T.float32) | ||
| 155 | + ones = S.vdup(T.float32(1.0), T.float32) | ||
| 156 | + scale = S.vld(scale_ub[0], dist='BRC_B32') | ||
| 157 | + base_indices = S.vand( | ||
| 158 | + T.reinterpret(S.vci(T.int32(0), T.int32), 'uint32x64'), | ||
| 159 | + S.vdup(T.uint32(mhc_mult - 1), T.uint32), | ||
| 160 | + ) | ||
| 161 | + base_pattern = S.vgather2(base_ub[0], base_indices) | ||
| 162 | + | ||
| 163 | + S.mem_bar('VST_VLD') | ||
| 164 | + scale_acc = S.alloc_var(T.float32) | ||
| 165 | + scale_acc = S.vld(scale_acc_ub[0]) | ||
| 166 | + seed_acc = S.alloc_local((2,), T.float32) | ||
| 167 | + seed_acc[0] = S.vld(seed_acc_ub[0, 0]) | ||
| 168 | + seed_acc[1] = S.vld(seed_acc_ub[1, 0]) | ||
| 169 | + | ||
| 170 | + for vector_id in T.serial(token_block_size * mhc_mult // _VECTOR_WIDTH): | ||
| 171 | + row = vector_id * _VECTOR_WIDTH // mhc_mult | ||
| 172 | + x = S.alloc_var(T.float32) | ||
| 173 | + x = S.vld(input_ub[row, 0]) | ||
| 174 | + output_grad = S.alloc_var(T.float32) | ||
| 175 | + output_grad = S.vld(output_grad_ub[row, 0]) | ||
| 176 | + | ||
| 177 | + if valid_tokens < token_block_size: | ||
| 178 | + valid_mask = S.vcmp( | ||
| 179 | + S.vci(T.int32(row * mhc_mult), T.int32), | ||
| 180 | + S.vdup(T.int32((num_tokens % token_block_size) * mhc_mult), T.int32), | ||
| 181 | + op='lt', | ||
| 182 | + ) | ||
| 183 | + x = S.vsel(x, zeros, valid_mask) | ||
| 184 | + output_grad = S.vsel(output_grad, zeros, valid_mask) | ||
| 185 | + | ||
| 186 | + affine = S.alloc_var(T.float32) | ||
| 187 | + affine = x | ||
| 188 | + S.vmadd(affine, scale, base_pattern) | ||
| 189 | + sigmoid = S.vdiv(ones, S.vadds(S.vexpdif(zeros, affine), 1.0)) | ||
| 190 | + grad = S.vmul(S.vmul(sigmoid, S.vsub(ones, sigmoid)), output_grad) | ||
| 191 | + S.vsts(input_grad_ub[row, 0], S.vmul(grad, scale)) | ||
| 192 | + | ||
| 193 | + S.vmula(scale_acc, grad, x) | ||
| 194 | + even, odd = S.vdintlv(grad, grad) | ||
| 195 | + seed_acc[0] = S.vadd(seed_acc[0], even) | ||
| 196 | + seed_acc[1] = S.vadd(seed_acc[1], odd) | ||
| 197 | + | ||
| 198 | + S.vsts(scale_acc_ub[0], scale_acc) | ||
| 199 | + S.vsts(seed_acc_ub[0, 0], seed_acc[0]) | ||
| 200 | + S.vsts(seed_acc_ub[1, 0], seed_acc[1]) | ||
| 201 | + | ||
| 202 | + T.copy( | ||
| 203 | + input_grad_ub[0:valid_tokens, 0:mhc_mult], | ||
| 204 | + input_mix_grad[token_offset : token_offset + valid_tokens, 0:mhc_mult], | ||
| 205 | + ) | ||
| 206 | + | ||
| 207 | + with T.SimdVF(): | ||
| 208 | + S.mem_bar('VST_VLD') | ||
| 209 | + scale_acc = S.alloc_var(T.float32) | ||
| 210 | + scale_acc = S.vld(scale_acc_ub[0]) | ||
| 211 | + S.vsts(scale_grad_partial_ub[0], S.vcadd(scale_acc), dist='ONEPT_B32') | ||
| 212 | + | ||
| 213 | + seed_acc = S.alloc_local((2,), T.float32) | ||
| 214 | + seed_acc[0] = S.vld(seed_acc_ub[0, 0]) | ||
| 215 | + seed_acc[1] = S.vld(seed_acc_ub[1, 0]) | ||
| 216 | + | ||
| 217 | + base_acc = S.alloc_local((mhc_mult,), T.float32) | ||
| 218 | + base_acc[0] = seed_acc[0] | ||
| 219 | + base_acc[1] = seed_acc[1] | ||
| 220 | + _split_accumulator_tree(base_acc, mhc_mult) | ||
| 221 | + component_lanes_mask = S.pset(32, f'PAT_VL{_VECTOR_WIDTH // mhc_mult}') | ||
| 222 | + for component in T.unroll(mhc_mult, explicit=True): | ||
| 223 | + S.vsts( | ||
| 224 | + base_grad_partial_ub[component], | ||
| 225 | + S.vcadd(base_acc[component], component_lanes_mask), | ||
| 226 | + dist='ONEPT_B32', | ||
| 227 | + ) | ||
| 228 | + | ||
| 229 | + T.copy(scale_grad_partial_ub[0:1], mhc_scale_grad_partial[block_id, 0:1]) | ||
| 230 | + T.copy(base_grad_partial_ub[0:mhc_mult], mhc_base_grad_partial[block_id, 0:mhc_mult]) | ||
| @@ -0,0 +1,256 @@ | |||
| 1 | +"""Ascend MHC multilayer recompute operator.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +from collections.abc import Sequence | ||
| 6 | + | ||
| 7 | +import torch | ||
| 8 | + | ||
| 9 | +from cann_ops_tilelang.config import get_num_vector_cores, require_ascend | ||
| 10 | +from cann_ops_tilelang.utils import align_up | ||
| 11 | + | ||
| 12 | +from .multilayer_recompute_kernel import mhc_multilayer_recompute_asc | ||
| 13 | + | ||
| 14 | +_HIDDEN_ALIGNMENT = 64 | ||
| 15 | +_MAX_HIDDEN_BLOCK = 8192 | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +def _check_tensor( | ||
| 19 | + name: str, | ||
| 20 | + tensor: torch.Tensor, | ||
| 21 | + *, | ||
| 22 | + dtype: torch.dtype, | ||
| 23 | + shape: tuple[int, ...], | ||
| 24 | + device: torch.device, | ||
| 25 | +) -> None: | ||
| 26 | + if not isinstance(tensor, torch.Tensor): | ||
| 27 | + raise TypeError(f'{name} must be a torch.Tensor.') | ||
| 28 | + if tensor.device.type != 'npu': | ||
| 29 | + raise ValueError(f'{name} must be an NPU tensor, got {tensor.device}.') | ||
| 30 | + if tensor.device != device: | ||
| 31 | + raise ValueError(f'{name} must be on {device}, got {tensor.device}.') | ||
| 32 | + if tensor.dtype != dtype: | ||
| 33 | + raise TypeError(f'{name} must use {dtype}, got {tensor.dtype}.') | ||
| 34 | + if tensor.shape != shape: | ||
| 35 | + raise ValueError(f'{name} must have shape {shape}, got {tuple(tensor.shape)}.') | ||
| 36 | + if not tensor.is_contiguous(): | ||
| 37 | + raise ValueError(f'{name} must be contiguous.') | ||
| 38 | + | ||
| 39 | + | ||
| 40 | +def _check_tensor_list( | ||
| 41 | + name: str, | ||
| 42 | + tensors: Sequence[torch.Tensor], | ||
| 43 | + *, | ||
| 44 | + length: int, | ||
| 45 | + dtype: torch.dtype, | ||
| 46 | + shape: tuple[int, ...], | ||
| 47 | + device: torch.device, | ||
| 48 | +) -> None: | ||
| 49 | + if not isinstance(tensors, list): | ||
| 50 | + raise TypeError(f'{name} must be a list of torch.Tensor instances.') | ||
| 51 | + if len(tensors) != length: | ||
| 52 | + raise ValueError(f'{name} must contain {length} tensors, got {len(tensors)}.') | ||
| 53 | + for index, tensor in enumerate(tensors): | ||
| 54 | + _check_tensor( | ||
| 55 | + f'{name}[{index}]', | ||
| 56 | + tensor, | ||
| 57 | + dtype=dtype, | ||
| 58 | + shape=shape, | ||
| 59 | + device=device, | ||
| 60 | + ) | ||
| 61 | + | ||
| 62 | + | ||
| 63 | +def _shares_storage(left: torch.Tensor, right: torch.Tensor) -> bool: | ||
| 64 | + return ( | ||
| 65 | + left.numel() > 0 | ||
| 66 | + and right.numel() > 0 | ||
| 67 | + and left.untyped_storage().data_ptr() == right.untyped_storage().data_ptr() | ||
| 68 | + ) | ||
| 69 | + | ||
| 70 | + | ||
| 71 | +def _check_output_aliases( | ||
| 72 | + outputs: Sequence[tuple[str, torch.Tensor]], | ||
| 73 | + inputs: Sequence[tuple[str, torch.Tensor]], | ||
| 74 | +) -> None: | ||
| 75 | + for output_index, (output_name, output) in enumerate(outputs): | ||
| 76 | + for input_name, input_tensor in inputs: | ||
| 77 | + if _shares_storage(output, input_tensor): | ||
| 78 | + raise ValueError(f'{output_name} must not share storage with {input_name}.') | ||
| 79 | + for other_name, other_output in outputs[:output_index]: | ||
| 80 | + if _shares_storage(output, other_output): | ||
| 81 | + raise ValueError(f'{output_name} must not share storage with {other_name}.') | ||
| 82 | + | ||
| 83 | + | ||
| 84 | +def _make_pointer_tables( | ||
| 85 | + tensor_lists: Sequence[Sequence[torch.Tensor]], | ||
| 86 | + device: torch.device, | ||
| 87 | +) -> list[torch.Tensor]: | ||
| 88 | + sizes = [len(tensors) for tensors in tensor_lists] | ||
| 89 | + pointer_buffer = torch.empty(sum(sizes), dtype=torch.int64, device='cpu', pin_memory=True) | ||
| 90 | + offset = 0 | ||
| 91 | + for tensors in tensor_lists: | ||
| 92 | + for index, tensor in enumerate(tensors): | ||
| 93 | + pointer_buffer[offset + index] = tensor.data_ptr() | ||
| 94 | + offset += len(tensors) | ||
| 95 | + | ||
| 96 | + device_buffer = pointer_buffer.to(device=device, non_blocking=True) | ||
| 97 | + pointer_tables: list[torch.Tensor] = [] | ||
| 98 | + offset = 0 | ||
| 99 | + for size in sizes: | ||
| 100 | + pointer_tables.append(device_buffer[offset : offset + size]) | ||
| 101 | + offset += size | ||
| 102 | + return pointer_tables | ||
| 103 | + | ||
| 104 | + | ||
| 105 | +def mhc_multilayer_recompute( | ||
| 106 | + initial_residual: torch.Tensor, | ||
| 107 | + pre_mix_list: list[torch.Tensor], | ||
| 108 | + layer_output_list: list[torch.Tensor], | ||
| 109 | + post_mix_list: list[torch.Tensor], | ||
| 110 | + comb_mix_list: list[torch.Tensor], | ||
| 111 | + layer_input_list: list[torch.Tensor], | ||
| 112 | + residual_list: list[torch.Tensor], | ||
| 113 | +) -> None: | ||
| 114 | + """Recompute MHC layer inputs and residuals into caller-provided buffers.""" | ||
| 115 | + | ||
| 116 | + if not isinstance(initial_residual, torch.Tensor): | ||
| 117 | + raise TypeError('initial_residual must be a torch.Tensor.') | ||
| 118 | + if initial_residual.ndim < 3: | ||
| 119 | + raise ValueError( | ||
| 120 | + f'initial_residual must have shape (..., mhc_mult, hidden_size), got {tuple(initial_residual.shape)}.' | ||
| 121 | + ) | ||
| 122 | + | ||
| 123 | + require_ascend() | ||
| 124 | + if initial_residual.device.type != 'npu': | ||
| 125 | + raise ValueError(f'initial_residual must be an NPU tensor, got {initial_residual.device}.') | ||
| 126 | + if initial_residual.dtype != torch.bfloat16: | ||
| 127 | + raise TypeError(f'initial_residual must use {torch.bfloat16}, got {initial_residual.dtype}.') | ||
| 128 | + if not initial_residual.is_contiguous(): | ||
| 129 | + raise ValueError('initial_residual must be contiguous.') | ||
| 130 | + | ||
| 131 | + for name, tensors in ( | ||
| 132 | + ('pre_mix_list', pre_mix_list), | ||
| 133 | + ('layer_output_list', layer_output_list), | ||
| 134 | + ('post_mix_list', post_mix_list), | ||
| 135 | + ('comb_mix_list', comb_mix_list), | ||
| 136 | + ('layer_input_list', layer_input_list), | ||
| 137 | + ('residual_list', residual_list), | ||
| 138 | + ): | ||
| 139 | + if not isinstance(tensors, list): | ||
| 140 | + raise TypeError(f'{name} must be a list of torch.Tensor instances.') | ||
| 141 | + | ||
| 142 | + num_layers = len(pre_mix_list) | ||
| 143 | + num_post_layers = len(layer_output_list) | ||
| 144 | + if num_layers <= 0: | ||
| 145 | + raise ValueError(f'pre_mix_list must contain at least one tensor, got {num_layers}.') | ||
| 146 | + if num_post_layers not in (num_layers - 1, num_layers): | ||
| 147 | + raise ValueError( | ||
| 148 | + f'layer_output_list must contain {num_layers - 1} or {num_layers} tensors, got {num_post_layers}.' | ||
| 149 | + ) | ||
| 150 | + | ||
| 151 | + leading_shape = tuple(initial_residual.shape[:-2]) | ||
| 152 | + mhc_mult, hidden_size = initial_residual.shape[-2:] | ||
| 153 | + if mhc_mult <= 0: | ||
| 154 | + raise ValueError(f'mhc_mult must be positive, got {mhc_mult}.') | ||
| 155 | + if hidden_size <= 0 or hidden_size % _HIDDEN_ALIGNMENT: | ||
| 156 | + raise ValueError(f'hidden_size must be a positive multiple of {_HIDDEN_ALIGNMENT}, got {hidden_size}.') | ||
| 157 | + | ||
| 158 | + device = initial_residual.device | ||
| 159 | + _check_tensor_list( | ||
| 160 | + 'pre_mix_list', | ||
| 161 | + pre_mix_list, | ||
| 162 | + length=num_layers, | ||
| 163 | + dtype=torch.float32, | ||
| 164 | + shape=(*leading_shape, mhc_mult, 1), | ||
| 165 | + device=device, | ||
| 166 | + ) | ||
| 167 | + _check_tensor_list( | ||
| 168 | + 'layer_output_list', | ||
| 169 | + layer_output_list, | ||
| 170 | + length=num_post_layers, | ||
| 171 | + dtype=torch.bfloat16, | ||
| 172 | + shape=(*leading_shape, hidden_size), | ||
| 173 | + device=device, | ||
| 174 | + ) | ||
| 175 | + _check_tensor_list( | ||
| 176 | + 'post_mix_list', | ||
| 177 | + post_mix_list, | ||
| 178 | + length=num_post_layers, | ||
| 179 | + dtype=torch.float32, | ||
| 180 | + shape=(*leading_shape, mhc_mult, 1), | ||
| 181 | + device=device, | ||
| 182 | + ) | ||
| 183 | + _check_tensor_list( | ||
| 184 | + 'comb_mix_list', | ||
| 185 | + comb_mix_list, | ||
| 186 | + length=num_post_layers, | ||
| 187 | + dtype=torch.float32, | ||
| 188 | + shape=(*leading_shape, mhc_mult, mhc_mult), | ||
| 189 | + device=device, | ||
| 190 | + ) | ||
| 191 | + _check_tensor_list( | ||
| 192 | + 'layer_input_list', | ||
| 193 | + layer_input_list, | ||
| 194 | + length=num_layers, | ||
| 195 | + dtype=torch.bfloat16, | ||
| 196 | + shape=(*leading_shape, hidden_size), | ||
| 197 | + device=device, | ||
| 198 | + ) | ||
| 199 | + _check_tensor_list( | ||
| 200 | + 'residual_list', | ||
| 201 | + residual_list, | ||
| 202 | + length=num_post_layers, | ||
| 203 | + dtype=torch.bfloat16, | ||
| 204 | + shape=(*leading_shape, mhc_mult, hidden_size), | ||
| 205 | + device=device, | ||
| 206 | + ) | ||
| 207 | + | ||
| 208 | + input_tensors = [('initial_residual', initial_residual)] | ||
| 209 | + for list_name, tensors in ( | ||
| 210 | + ('pre_mix_list', pre_mix_list), | ||
| 211 | + ('layer_output_list', layer_output_list), | ||
| 212 | + ('post_mix_list', post_mix_list), | ||
| 213 | + ('comb_mix_list', comb_mix_list), | ||
| 214 | + ): | ||
| 215 | + input_tensors.extend((f'{list_name}[{index}]', tensor) for index, tensor in enumerate(tensors)) | ||
| 216 | + output_tensors = [ | ||
| 217 | + *((f'layer_input_list[{index}]', tensor) for index, tensor in enumerate(layer_input_list)), | ||
| 218 | + *((f'residual_list[{index}]', tensor) for index, tensor in enumerate(residual_list)), | ||
| 219 | + ] | ||
| 220 | + _check_output_aliases(output_tensors, input_tensors) | ||
| 221 | + | ||
| 222 | + num_tokens = initial_residual.numel() // (mhc_mult * hidden_size) | ||
| 223 | + if num_tokens == 0: | ||
| 224 | + return | ||
| 225 | + | ||
| 226 | + ( | ||
| 227 | + pre_mix_ptrs, | ||
| 228 | + layer_input_ptrs, | ||
| 229 | + residual_ptrs, | ||
| 230 | + layer_output_ptrs, | ||
| 231 | + post_mix_ptrs, | ||
| 232 | + comb_mix_ptrs, | ||
| 233 | + ) = _make_pointer_tables( | ||
| 234 | + [ | ||
| 235 | + pre_mix_list, | ||
| 236 | + layer_input_list, | ||
| 237 | + residual_list, | ||
| 238 | + layer_output_list, | ||
| 239 | + post_mix_list, | ||
| 240 | + comb_mix_list, | ||
| 241 | + ], | ||
| 242 | + device, | ||
| 243 | + ) | ||
| 244 | + | ||
| 245 | + hidden_block = min(align_up(hidden_size, _HIDDEN_ALIGNMENT), _MAX_HIDDEN_BLOCK) | ||
| 246 | + mhc_multilayer_recompute_asc( | ||
| 247 | + initial_residual.view(num_tokens, mhc_mult, hidden_size), | ||
| 248 | + pre_mix_ptrs, | ||
| 249 | + layer_output_ptrs, | ||
| 250 | + post_mix_ptrs, | ||
| 251 | + comb_mix_ptrs, | ||
| 252 | + layer_input_ptrs, | ||
| 253 | + residual_ptrs, | ||
| 254 | + h_blk=hidden_block, | ||
| 255 | + num_vec_cores=get_num_vector_cores(), | ||
| 256 | + ) | ||
| @@ -0,0 +1,219 @@ | |||
| 1 | +import tilelang | ||
| 2 | +from tilelang.ascend import language as T | ||
| 3 | +from tilelang.ascend.language import simd as S | ||
| 4 | + | ||
| 5 | +_VEC = 64 | ||
| 6 | + | ||
| 7 | +_PASS_CONFIGS = { | ||
| 8 | + tilelang.PassConfigKey.TL_ENABLE_AUTO_SCHEDULE: False, | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | + | ||
| 12 | +@tilelang.jit(pass_configs=_PASS_CONFIGS) | ||
| 13 | +def mhc_multilayer_recompute_asc( | ||
| 14 | + initial_residual, | ||
| 15 | + pre_mix_ptrs, | ||
| 16 | + layer_output_ptrs, | ||
| 17 | + post_mix_ptrs, | ||
| 18 | + comb_mix_ptrs, | ||
| 19 | + layer_input_ptrs, | ||
| 20 | + residual_ptrs, | ||
| 21 | + h_blk: int, | ||
| 22 | + num_vec_cores: int, | ||
| 23 | +): | ||
| 24 | + num_tokens = T.dynamic('n') | ||
| 25 | + mhc = T.const('mhc') | ||
| 26 | + hidden = T.const('h') | ||
| 27 | + num_layers = T.const('L') | ||
| 28 | + num_post_layers = T.const('L_post') | ||
| 29 | + | ||
| 30 | + initial_residual: T.Tensor[(num_tokens, mhc, hidden), T.bfloat16] | ||
| 31 | + pre_mix_ptrs: T.Tensor[(num_layers,), T.ptr] | ||
| 32 | + layer_output_ptrs: T.Tensor[(num_post_layers,), T.ptr] | ||
| 33 | + post_mix_ptrs: T.Tensor[(num_post_layers,), T.ptr] | ||
| 34 | + comb_mix_ptrs: T.Tensor[(num_post_layers,), T.ptr] | ||
| 35 | + layer_input_ptrs: T.Tensor[(num_layers,), T.ptr] | ||
| 36 | + residual_ptrs: T.Tensor[(num_post_layers,), T.ptr] | ||
| 37 | + | ||
| 38 | + num_hidden_tiles = T.ceildiv(hidden, h_blk) | ||
| 39 | + num_chunks = h_blk // _VEC | ||
| 40 | + mhc_pad = T.ceildiv(mhc, 8) * 8 | ||
| 41 | + | ||
| 42 | + with T.Kernel(num_vec_cores) as core_id: | ||
| 43 | + residual_ub = T.alloc_shared((2, mhc, h_blk), T.bfloat16) | ||
| 44 | + layer_output_ub = T.alloc_shared((2, h_blk), T.bfloat16) | ||
| 45 | + layer_input_ub = T.alloc_shared((2, h_blk), T.bfloat16) | ||
| 46 | + pre_mix_ub = T.alloc_shared((2, mhc_pad), T.float32) | ||
| 47 | + post_mix_ub = T.alloc_shared((2, mhc_pad), T.float32) | ||
| 48 | + comb_mix_ub = T.alloc_shared((2, mhc, mhc), T.float32) | ||
| 49 | + | ||
| 50 | + T.ascend_set_flag('MTE3_MTE2', 0) | ||
| 51 | + | ||
| 52 | + for pid_n, pid_h in T.Persistent( | ||
| 53 | + [num_tokens, num_hidden_tiles], | ||
| 54 | + num_vec_cores, | ||
| 55 | + core_id, | ||
| 56 | + group_size=1, | ||
| 57 | + num_stages=0, | ||
| 58 | + ): | ||
| 59 | + T.ascend_set_flag('V_MTE2', 0) | ||
| 60 | + T.ascend_set_flag('V_MTE2', 1) | ||
| 61 | + T.ascend_set_flag('MTE3_V', 0) | ||
| 62 | + T.ascend_set_flag('MTE3_V', 1) | ||
| 63 | + | ||
| 64 | + T.ascend_wait_flag('MTE3_MTE2', 0) | ||
| 65 | + T.copy(initial_residual[pid_n, 0, pid_h * h_blk], residual_ub[0, :, :]) | ||
| 66 | + T.ascend_set_flag('MTE2_V', 2) | ||
| 67 | + T.ascend_wait_flag('MTE2_V', 2) | ||
| 68 | + | ||
| 69 | + for layer_index in T.serial(num_post_layers): | ||
| 70 | + input_slot = layer_index % 2 | ||
| 71 | + output_slot = 1 - input_slot | ||
| 72 | + pre_mix_tensor = T.make_tensor(pre_mix_ptrs[layer_index], (num_tokens, mhc), T.float32) | ||
| 73 | + post_mix_tensor = T.make_tensor(post_mix_ptrs[layer_index], (num_tokens, mhc), T.float32) | ||
| 74 | + comb_mix_tensor = T.make_tensor( | ||
| 75 | + comb_mix_ptrs[layer_index], | ||
| 76 | + (num_tokens, mhc, mhc), | ||
| 77 | + T.float32, | ||
| 78 | + ) | ||
| 79 | + layer_output_tensor = T.make_tensor( | ||
| 80 | + layer_output_ptrs[layer_index], | ||
| 81 | + (num_tokens, hidden), | ||
| 82 | + T.bfloat16, | ||
| 83 | + ) | ||
| 84 | + layer_input_tensor = T.make_tensor( | ||
| 85 | + layer_input_ptrs[layer_index], | ||
| 86 | + (num_tokens, hidden), | ||
| 87 | + T.bfloat16, | ||
| 88 | + ) | ||
| 89 | + residual_tensor = T.make_tensor( | ||
| 90 | + residual_ptrs[layer_index], | ||
| 91 | + (num_tokens, mhc, hidden), | ||
| 92 | + T.bfloat16, | ||
| 93 | + ) | ||
| 94 | + | ||
| 95 | + T.ascend_wait_flag('V_MTE2', input_slot) | ||
| 96 | + T.copy(pre_mix_tensor[pid_n, 0], pre_mix_ub[input_slot, 0:mhc]) | ||
| 97 | + T.copy(post_mix_tensor[pid_n, 0], post_mix_ub[input_slot, 0:mhc]) | ||
| 98 | + T.copy(comb_mix_tensor[pid_n, 0, 0], comb_mix_ub[input_slot, :, :]) | ||
| 99 | + T.copy(layer_output_tensor[pid_n, pid_h * h_blk], layer_output_ub[input_slot, :]) | ||
| 100 | + T.ascend_set_flag('MTE2_V', input_slot) | ||
| 101 | + | ||
| 102 | + T.ascend_wait_flag('MTE2_V', input_slot) | ||
| 103 | + T.ascend_wait_flag('MTE3_V', output_slot) | ||
| 104 | + with T.SimdVF(): | ||
| 105 | + pre_mix = S.alloc_local((mhc,), T.float32) | ||
| 106 | + post_mix = S.alloc_local((mhc,), T.float32) | ||
| 107 | + comb_mix = S.alloc_local((mhc * mhc,), T.float32) | ||
| 108 | + for mix_index in T.unroll(mhc, explicit=True): | ||
| 109 | + pre_mix[mix_index] = S.vld(pre_mix_ub[input_slot, mix_index], dist='BRC_B32') | ||
| 110 | + post_mix[mix_index] = S.vld(post_mix_ub[input_slot, mix_index], dist='BRC_B32') | ||
| 111 | + for input_index in T.unroll(mhc, explicit=True): | ||
| 112 | + for output_index in T.unroll(mhc, explicit=True): | ||
| 113 | + comb_mix[input_index * mhc + output_index] = S.vld( | ||
| 114 | + comb_mix_ub[input_slot, input_index, output_index], | ||
| 115 | + dist='BRC_B32', | ||
| 116 | + ) | ||
| 117 | + residual = S.alloc_local((mhc,), T.float32) | ||
| 118 | + output = S.alloc_local((mhc,), T.float32) | ||
| 119 | + layer_input = S.alloc_var(T.float32) | ||
| 120 | + for chunk_index in T.serial(num_chunks): | ||
| 121 | + column_offset = chunk_index * _VEC | ||
| 122 | + for input_index in T.unroll(mhc, explicit=True): | ||
| 123 | + residual[input_index] = S.vcvt( | ||
| 124 | + S.vld( | ||
| 125 | + residual_ub[input_slot, input_index, column_offset], | ||
| 126 | + dist='UNPK_B16', | ||
| 127 | + ), | ||
| 128 | + T.float32, | ||
| 129 | + part=0, | ||
| 130 | + ) | ||
| 131 | + layer_input = S.vmul(residual[0], pre_mix[0]) | ||
| 132 | + for input_index in T.unroll(mhc - 1, explicit=True): | ||
| 133 | + S.vmula(layer_input, residual[input_index + 1], pre_mix[input_index + 1]) | ||
| 134 | + S.vsts( | ||
| 135 | + layer_input_ub[input_slot, column_offset], | ||
| 136 | + S.vcvt(layer_input, T.bfloat16), | ||
| 137 | + dist='PK_B32', | ||
| 138 | + ) | ||
| 139 | + layer_output = S.vcvt( | ||
| 140 | + S.vld(layer_output_ub[input_slot, column_offset], dist='UNPK_B16'), | ||
| 141 | + T.float32, | ||
| 142 | + part=0, | ||
| 143 | + ) | ||
| 144 | + for output_index in T.unroll(mhc, explicit=True): | ||
| 145 | + output[output_index] = S.vmul(layer_output, post_mix[output_index]) | ||
| 146 | + for input_index in T.unroll(mhc, explicit=True): | ||
| 147 | + for output_index in T.unroll(mhc, explicit=True): | ||
| 148 | + S.vmula( | ||
| 149 | + output[output_index], | ||
| 150 | + residual[input_index], | ||
| 151 | + comb_mix[input_index * mhc + output_index], | ||
| 152 | + ) | ||
| 153 | + for output_index in T.unroll(mhc, explicit=True): | ||
| 154 | + S.vsts( | ||
| 155 | + residual_ub[output_slot, output_index, column_offset], | ||
| 156 | + S.vcvt(output[output_index], T.bfloat16), | ||
| 157 | + dist='PK_B32', | ||
| 158 | + ) | ||
| 159 | + T.ascend_set_flag('V_MTE2', input_slot) | ||
| 160 | + T.ascend_set_flag('V_MTE3', input_slot) | ||
| 161 | + | ||
| 162 | + T.ascend_wait_flag('V_MTE3', input_slot) | ||
| 163 | + T.copy(residual_ub[output_slot, :, :], residual_tensor[pid_n, 0, pid_h * h_blk]) | ||
| 164 | + T.copy(layer_input_ub[input_slot, :], layer_input_tensor[pid_n, pid_h * h_blk]) | ||
| 165 | + T.ascend_set_flag('MTE3_V', output_slot) | ||
| 166 | + | ||
| 167 | + T.ascend_set_flag('MTE3_MTE2', 0) | ||
| 168 | + | ||
| 169 | + if num_layers > num_post_layers: | ||
| 170 | + final_slot = num_post_layers % 2 | ||
| 171 | + final_output_slot = 1 - final_slot | ||
| 172 | + pre_mix_tensor = T.make_tensor(pre_mix_ptrs[num_post_layers], (num_tokens, mhc), T.float32) | ||
| 173 | + layer_input_tensor = T.make_tensor( | ||
| 174 | + layer_input_ptrs[num_post_layers], | ||
| 175 | + (num_tokens, hidden), | ||
| 176 | + T.bfloat16, | ||
| 177 | + ) | ||
| 178 | + T.ascend_wait_flag('V_MTE2', final_slot) | ||
| 179 | + T.copy(pre_mix_tensor[pid_n, 0], pre_mix_ub[final_slot, 0:mhc]) | ||
| 180 | + T.ascend_set_flag('MTE2_V', final_slot) | ||
| 181 | + T.ascend_wait_flag('MTE2_V', final_slot) | ||
| 182 | + T.ascend_wait_flag('MTE3_V', final_output_slot) | ||
| 183 | + with T.SimdVF(): | ||
| 184 | + pre_mix = S.alloc_local((mhc,), T.float32) | ||
| 185 | + for mix_index in T.unroll(mhc, explicit=True): | ||
| 186 | + pre_mix[mix_index] = S.vld(pre_mix_ub[final_slot, mix_index], dist='BRC_B32') | ||
| 187 | + residual = S.alloc_local((mhc,), T.float32) | ||
| 188 | + layer_input = S.alloc_var(T.float32) | ||
| 189 | + for chunk_index in T.serial(num_chunks): | ||
| 190 | + column_offset = chunk_index * _VEC | ||
| 191 | + for mix_index in T.unroll(mhc, explicit=True): | ||
| 192 | + residual[mix_index] = S.vcvt( | ||
| 193 | + S.vld( | ||
| 194 | + residual_ub[final_slot, mix_index, column_offset], | ||
| 195 | + dist='UNPK_B16', | ||
| 196 | + ), | ||
| 197 | + T.float32, | ||
| 198 | + part=0, | ||
| 199 | + ) | ||
| 200 | + layer_input = S.vmul(residual[0], pre_mix[0]) | ||
| 201 | + for mix_index in T.unroll(mhc - 1, explicit=True): | ||
| 202 | + S.vmula(layer_input, residual[mix_index + 1], pre_mix[mix_index + 1]) | ||
| 203 | + S.vsts( | ||
| 204 | + layer_input_ub[final_slot, column_offset], | ||
| 205 | + S.vcvt(layer_input, T.bfloat16), | ||
| 206 | + dist='PK_B32', | ||
| 207 | + ) | ||
| 208 | + T.ascend_set_flag('V_MTE2', final_slot) | ||
| 209 | + T.ascend_set_flag('V_MTE3', final_slot) | ||
| 210 | + T.ascend_wait_flag('V_MTE3', final_slot) | ||
| 211 | + T.copy(layer_input_ub[final_slot, :], layer_input_tensor[pid_n, pid_h * h_blk]) | ||
| 212 | + T.ascend_set_flag('MTE3_V', final_output_slot) | ||
| 213 | + | ||
| 214 | + T.ascend_wait_flag('V_MTE2', 0) | ||
| 215 | + T.ascend_wait_flag('V_MTE2', 1) | ||
| 216 | + T.ascend_wait_flag('MTE3_V', 0) | ||
| 217 | + T.ascend_wait_flag('MTE3_V', 1) | ||
| 218 | + | ||
| 219 | + T.ascend_wait_flag('MTE3_MTE2', 0) | ||
| @@ -0,0 +1,416 @@ | |||
| 1 | +"""Ascend MHC norm, GEMM, and partial-reduction operators.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +import math | ||
| 6 | +from numbers import Integral, Real | ||
| 7 | + | ||
| 8 | +import torch | ||
| 9 | + | ||
| 10 | +from cann_ops_tilelang.config import get_num_ai_cores, require_ascend | ||
| 11 | + | ||
| 12 | +from .norm_fn_kernel import ( | ||
| 13 | + mhc_fn_normw_merge_bwd_asc, | ||
| 14 | + mhc_fn_normw_merge_fwd_asc, | ||
| 15 | + mhc_gemm_with_sqrsum_bwd_asc, | ||
| 16 | + mhc_gemm_with_sqrsum_fwd_asc, | ||
| 17 | + mhc_reduce_partials_and_rmsnorm_bwd_asc, | ||
| 18 | + mhc_reduce_partials_and_rmsnorm_fwd_asc, | ||
| 19 | +) | ||
| 20 | + | ||
| 21 | +_MHC_MULT3 = 24 | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +def _check_tensor( | ||
| 25 | + name: str, | ||
| 26 | + tensor: torch.Tensor, | ||
| 27 | + dtype: torch.dtype, | ||
| 28 | + ndim: int, | ||
| 29 | + *, | ||
| 30 | + device: torch.device | None = None, | ||
| 31 | +) -> None: | ||
| 32 | + if not isinstance(tensor, torch.Tensor): | ||
| 33 | + raise TypeError(f'{name} must be a torch.Tensor.') | ||
| 34 | + if tensor.device.type != 'npu': | ||
| 35 | + raise ValueError(f'{name} must be an NPU tensor, got {tensor.device}.') | ||
| 36 | + if device is not None and tensor.device != device: | ||
| 37 | + raise ValueError(f'{name} must be on {device}, got {tensor.device}.') | ||
| 38 | + if tensor.dtype != dtype: | ||
| 39 | + raise TypeError(f'{name} must use {dtype}, got {tensor.dtype}.') | ||
| 40 | + if tensor.ndim != ndim: | ||
| 41 | + raise ValueError(f'{name} must be {ndim}-dimensional, got shape {tuple(tensor.shape)}.') | ||
| 42 | + if not tensor.is_contiguous(): | ||
| 43 | + raise ValueError(f'{name} must be contiguous.') | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +def _shares_storage(left: torch.Tensor, right: torch.Tensor) -> bool: | ||
| 47 | + return ( | ||
| 48 | + left.numel() > 0 | ||
| 49 | + and right.numel() > 0 | ||
| 50 | + and left.untyped_storage().data_ptr() == right.untyped_storage().data_ptr() | ||
| 51 | + ) | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +def _check_output_aliases(inputs: dict[str, torch.Tensor], outputs: dict[str, torch.Tensor]) -> None: | ||
| 55 | + output_items = list(outputs.items()) | ||
| 56 | + for output_name, output in output_items: | ||
| 57 | + for input_name, input_tensor in inputs.items(): | ||
| 58 | + if _shares_storage(output, input_tensor): | ||
| 59 | + raise ValueError(f'{output_name} must not share storage with {input_name}.') | ||
| 60 | + for index, (left_name, left) in enumerate(output_items): | ||
| 61 | + for right_name, right in output_items[index + 1 :]: | ||
| 62 | + if _shares_storage(left, right): | ||
| 63 | + raise ValueError(f'{left_name} and {right_name} must not share storage.') | ||
| 64 | + | ||
| 65 | + | ||
| 66 | +def _check_positive_int(name: str, value: int) -> int: | ||
| 67 | + if isinstance(value, bool) or not isinstance(value, Integral): | ||
| 68 | + raise TypeError(f'{name} must be an integer.') | ||
| 69 | + converted = int(value) | ||
| 70 | + if converted <= 0: | ||
| 71 | + raise ValueError(f'{name} must be positive, got {converted}.') | ||
| 72 | + return converted | ||
| 73 | + | ||
| 74 | + | ||
| 75 | +def _check_non_negative_real(name: str, value: float) -> float: | ||
| 76 | + if isinstance(value, bool) or not isinstance(value, Real): | ||
| 77 | + raise TypeError(f'{name} must be a real number.') | ||
| 78 | + converted = float(value) | ||
| 79 | + if not math.isfinite(converted) or converted < 0: | ||
| 80 | + raise ValueError(f'{name} must be finite and non-negative, got {value}.') | ||
| 81 | + return converted | ||
| 82 | + | ||
| 83 | + | ||
| 84 | +def _check_shape(name: str, tensor: torch.Tensor, expected: tuple[int, ...]) -> None: | ||
| 85 | + if tensor.shape != expected: | ||
| 86 | + raise ValueError(f'{name} must have shape {expected}, got {tuple(tensor.shape)}.') | ||
| 87 | + | ||
| 88 | + | ||
| 89 | +def _ascend_forward_config(mhc_hidden_size: int, mhc_mult3: int, num_tokens: int) -> dict[str, int]: | ||
| 90 | + block_num_tokens = 128 | ||
| 91 | + block_hidden = 256 | ||
| 92 | + l0b_size_bytes = 64 * 1024 | ||
| 93 | + l0c_size_bytes = 256 * 1024 | ||
| 94 | + | ||
| 95 | + num_ai_cores = get_num_ai_cores() | ||
| 96 | + block_mhc_mult3 = (mhc_mult3 + 15) // 16 * 16 | ||
| 97 | + num_hidden_blocks = mhc_hidden_size // block_hidden | ||
| 98 | + num_token_blocks = math.ceil(num_tokens / block_num_tokens) | ||
| 99 | + num_l0b_stages = l0b_size_bytes // (block_mhc_mult3 * block_hidden * 4) | ||
| 100 | + num_l0c_stages = l0c_size_bytes // (block_num_tokens * block_mhc_mult3 * 4) | ||
| 101 | + | ||
| 102 | + num_k_splits_by_tokens = max(1, num_ai_cores * num_l0c_stages // num_token_blocks) | ||
| 103 | + num_k_splits_by_hidden = num_hidden_blocks & -num_hidden_blocks | ||
| 104 | + num_k_splits_by_cores = num_ai_cores & -num_ai_cores | ||
| 105 | + split_limit = min( | ||
| 106 | + num_k_splits_by_tokens, | ||
| 107 | + num_k_splits_by_hidden, | ||
| 108 | + num_k_splits_by_cores, | ||
| 109 | + ) | ||
| 110 | + num_k_splits = 1 << (split_limit.bit_length() - 1) | ||
| 111 | + active_ai_cores = num_k_splits * min(num_ai_cores // num_k_splits, num_token_blocks) | ||
| 112 | + cores_per_split = active_ai_cores // num_k_splits | ||
| 113 | + num_hidden_blocks_per_split = num_hidden_blocks // num_k_splits | ||
| 114 | + num_token_blocks_per_chunk = cores_per_split * num_l0c_stages | ||
| 115 | + | ||
| 116 | + return { | ||
| 117 | + 'block_mhc_mult3': block_mhc_mult3, | ||
| 118 | + 'num_aic_cores': active_ai_cores, | ||
| 119 | + 'num_k_splits': num_k_splits, | ||
| 120 | + 'cores_per_split': cores_per_split, | ||
| 121 | + 'num_hidden_blocks_per_split': num_hidden_blocks_per_split, | ||
| 122 | + 'num_l0b_stages': num_l0b_stages, | ||
| 123 | + 'num_l0c_stages': num_l0c_stages, | ||
| 124 | + 'num_token_blocks_per_chunk': num_token_blocks_per_chunk, | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + | ||
| 128 | +def mhc_fn_normw_merge_forward( | ||
| 129 | + fn: torch.Tensor, | ||
| 130 | + normw: torch.Tensor, | ||
| 131 | + out_fn: torch.Tensor, | ||
| 132 | +) -> None: | ||
| 133 | + """Multiply an FP32 MHC function matrix by its FP32 normalization weights.""" | ||
| 134 | + | ||
| 135 | + if not all(isinstance(tensor, torch.Tensor) for tensor in (fn, normw, out_fn)): | ||
| 136 | + raise TypeError('fn, normw and out_fn must be torch.Tensor instances.') | ||
| 137 | + require_ascend() | ||
| 138 | + _check_tensor('fn', fn, torch.float32, 2) | ||
| 139 | + _check_tensor('normw', normw, torch.float32, 1, device=fn.device) | ||
| 140 | + _check_tensor('out_fn', out_fn, torch.float32, 2, device=fn.device) | ||
| 141 | + if fn.shape[0] != _MHC_MULT3 or fn.shape[1] <= 0 or fn.shape[1] % 256: | ||
| 142 | + raise ValueError(f'fn must have shape (24, hidden) with hidden divisible by 256, got {tuple(fn.shape)}.') | ||
| 143 | + _check_shape('normw', normw, (fn.shape[1],)) | ||
| 144 | + _check_shape('out_fn', out_fn, tuple(fn.shape)) | ||
| 145 | + _check_output_aliases({'fn': fn, 'normw': normw}, {'out_fn': out_fn}) | ||
| 146 | + | ||
| 147 | + mhc_fn_normw_merge_fwd_asc( | ||
| 148 | + fn, | ||
| 149 | + normw, | ||
| 150 | + out_fn, | ||
| 151 | + num_stages=2, | ||
| 152 | + ) | ||
| 153 | + | ||
| 154 | + | ||
| 155 | +def mhc_fn_normw_merge_backward( | ||
| 156 | + fn: torch.Tensor, | ||
| 157 | + normw: torch.Tensor, | ||
| 158 | + out_fn_grad: torch.Tensor, | ||
| 159 | + fn_grad: torch.Tensor, | ||
| 160 | + normw_grad: torch.Tensor, | ||
| 161 | +) -> None: | ||
| 162 | + """Accumulate gradients into the FP32 function and normalization buffers.""" | ||
| 163 | + | ||
| 164 | + tensors = (fn, normw, out_fn_grad, fn_grad, normw_grad) | ||
| 165 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 166 | + raise TypeError('all norm-weight merge inputs and outputs must be torch.Tensor instances.') | ||
| 167 | + require_ascend() | ||
| 168 | + _check_tensor('fn', fn, torch.float32, 2) | ||
| 169 | + device = fn.device | ||
| 170 | + _check_tensor('normw', normw, torch.float32, 1, device=device) | ||
| 171 | + _check_tensor('out_fn_grad', out_fn_grad, torch.float32, 2, device=device) | ||
| 172 | + _check_tensor('fn_grad', fn_grad, torch.float32, 2, device=device) | ||
| 173 | + _check_tensor('normw_grad', normw_grad, torch.float32, 1, device=device) | ||
| 174 | + if fn.shape[0] != _MHC_MULT3 or fn.shape[1] <= 0 or fn.shape[1] % 128: | ||
| 175 | + raise ValueError(f'fn must have shape (24, hidden) with hidden divisible by 128, got {tuple(fn.shape)}.') | ||
| 176 | + _check_shape('normw', normw, (fn.shape[1],)) | ||
| 177 | + _check_shape('out_fn_grad', out_fn_grad, tuple(fn.shape)) | ||
| 178 | + _check_shape('fn_grad', fn_grad, tuple(fn.shape)) | ||
| 179 | + _check_shape('normw_grad', normw_grad, (fn.shape[1],)) | ||
| 180 | + _check_output_aliases( | ||
| 181 | + {'fn': fn, 'normw': normw, 'out_fn_grad': out_fn_grad}, | ||
| 182 | + {'fn_grad': fn_grad, 'normw_grad': normw_grad}, | ||
| 183 | + ) | ||
| 184 | + | ||
| 185 | + mhc_fn_normw_merge_bwd_asc( | ||
| 186 | + fn, | ||
| 187 | + normw, | ||
| 188 | + out_fn_grad, | ||
| 189 | + fn_grad, | ||
| 190 | + normw_grad, | ||
| 191 | + num_stages=2, | ||
| 192 | + ) | ||
| 193 | + | ||
| 194 | + | ||
| 195 | +def mhc_gemm_with_sqrsum_forward( | ||
| 196 | + x: torch.Tensor, | ||
| 197 | + fn: torch.Tensor, | ||
| 198 | + out_mul_splitted: torch.Tensor, | ||
| 199 | + sqrsum_splitted: torch.Tensor, | ||
| 200 | + n_splits: int = 16, | ||
| 201 | +) -> None: | ||
| 202 | + """Compute the MHC GEMM and squared sums into one internally reduced partial.""" | ||
| 203 | + | ||
| 204 | + tensors = (x, fn, out_mul_splitted, sqrsum_splitted) | ||
| 205 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 206 | + raise TypeError('all GEMM forward inputs and outputs must be torch.Tensor instances.') | ||
| 207 | + require_ascend() | ||
| 208 | + _check_tensor('x', x, torch.bfloat16, 2) | ||
| 209 | + device = x.device | ||
| 210 | + _check_tensor('fn', fn, torch.float32, 2, device=device) | ||
| 211 | + _check_tensor('out_mul_splitted', out_mul_splitted, torch.float32, 4, device=device) | ||
| 212 | + _check_tensor('sqrsum_splitted', sqrsum_splitted, torch.float32, 3, device=device) | ||
| 213 | + _check_positive_int('n_splits', n_splits) | ||
| 214 | + | ||
| 215 | + num_tokens, mhc_hidden_size = x.shape | ||
| 216 | + if fn.shape != (_MHC_MULT3, mhc_hidden_size): | ||
| 217 | + raise ValueError(f'fn must have shape {(24, mhc_hidden_size)}, got {tuple(fn.shape)}.') | ||
| 218 | + if mhc_hidden_size <= 0 or mhc_hidden_size % 256: | ||
| 219 | + raise ValueError(f'x hidden size must be a positive multiple of 256, got {mhc_hidden_size}.') | ||
| 220 | + _check_shape('out_mul_splitted', out_mul_splitted, (1, num_tokens, 1, _MHC_MULT3)) | ||
| 221 | + _check_shape('sqrsum_splitted', sqrsum_splitted, (1, num_tokens, 1)) | ||
| 222 | + _check_output_aliases( | ||
| 223 | + {'x': x, 'fn': fn}, | ||
| 224 | + {'out_mul_splitted': out_mul_splitted, 'sqrsum_splitted': sqrsum_splitted}, | ||
| 225 | + ) | ||
| 226 | + if num_tokens == 0: | ||
| 227 | + return | ||
| 228 | + | ||
| 229 | + config = _ascend_forward_config(mhc_hidden_size, fn.shape[0], num_tokens) | ||
| 230 | + mhc_gemm_with_sqrsum_fwd_asc( | ||
| 231 | + x, | ||
| 232 | + fn, | ||
| 233 | + out_mul_splitted[0, :, 0, :], | ||
| 234 | + sqrsum_splitted[0, :, 0], | ||
| 235 | + block_mhc_mult3=config['block_mhc_mult3'], | ||
| 236 | + num_aic_cores=config['num_aic_cores'], | ||
| 237 | + num_k_splits=config['num_k_splits'], | ||
| 238 | + cores_per_split=config['cores_per_split'], | ||
| 239 | + num_hidden_blocks_per_split=config['num_hidden_blocks_per_split'], | ||
| 240 | + num_l0b_stages=config['num_l0b_stages'], | ||
| 241 | + num_l0c_stages=config['num_l0c_stages'], | ||
| 242 | + num_token_blocks_per_chunk=config['num_token_blocks_per_chunk'], | ||
| 243 | + deterministic=torch.are_deterministic_algorithms_enabled(), | ||
| 244 | + ) | ||
| 245 | + | ||
| 246 | + | ||
| 247 | +def mhc_gemm_with_sqrsum_backward( | ||
| 248 | + out_mul_grad: torch.Tensor, | ||
| 249 | + sqrsum_grad: torch.Tensor, | ||
| 250 | + x: torch.Tensor, | ||
| 251 | + fn: torch.Tensor, | ||
| 252 | + x_grad: torch.Tensor, | ||
| 253 | + fn_grad: torch.Tensor, | ||
| 254 | +) -> None: | ||
| 255 | + """Accumulate the BF16 input gradient and write the FP32 function gradient.""" | ||
| 256 | + | ||
| 257 | + tensors = (out_mul_grad, sqrsum_grad, x, fn, x_grad, fn_grad) | ||
| 258 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 259 | + raise TypeError('all GEMM backward inputs and outputs must be torch.Tensor instances.') | ||
| 260 | + require_ascend() | ||
| 261 | + _check_tensor('x', x, torch.bfloat16, 2) | ||
| 262 | + device = x.device | ||
| 263 | + _check_tensor('out_mul_grad', out_mul_grad, torch.float32, 3, device=device) | ||
| 264 | + _check_tensor('sqrsum_grad', sqrsum_grad, torch.float32, 2, device=device) | ||
| 265 | + _check_tensor('fn', fn, torch.float32, 2, device=device) | ||
| 266 | + _check_tensor('x_grad', x_grad, torch.bfloat16, 2, device=device) | ||
| 267 | + _check_tensor('fn_grad', fn_grad, torch.float32, 2, device=device) | ||
| 268 | + | ||
| 269 | + num_tokens, mhc_hidden_size = x.shape | ||
| 270 | + if fn.shape != (_MHC_MULT3, mhc_hidden_size): | ||
| 271 | + raise ValueError(f'fn must have shape {(24, mhc_hidden_size)}, got {tuple(fn.shape)}.') | ||
| 272 | + if mhc_hidden_size <= 0 or mhc_hidden_size % 128: | ||
| 273 | + raise ValueError(f'x hidden size must be a positive multiple of 128, got {mhc_hidden_size}.') | ||
| 274 | + _check_shape('out_mul_grad', out_mul_grad, (num_tokens, 1, _MHC_MULT3)) | ||
| 275 | + _check_shape('sqrsum_grad', sqrsum_grad, (num_tokens, 1)) | ||
| 276 | + _check_shape('x_grad', x_grad, tuple(x.shape)) | ||
| 277 | + _check_shape('fn_grad', fn_grad, tuple(fn.shape)) | ||
| 278 | + _check_output_aliases( | ||
| 279 | + {'out_mul_grad': out_mul_grad, 'sqrsum_grad': sqrsum_grad, 'x': x, 'fn': fn}, | ||
| 280 | + {'x_grad': x_grad, 'fn_grad': fn_grad}, | ||
| 281 | + ) | ||
| 282 | + if num_tokens == 0: | ||
| 283 | + fn_grad.zero_() | ||
| 284 | + return | ||
| 285 | + | ||
| 286 | + kernel_out_mul_grad = out_mul_grad | ||
| 287 | + kernel_sqrsum_grad = sqrsum_grad | ||
| 288 | + kernel_x = x | ||
| 289 | + kernel_x_grad = x_grad | ||
| 290 | + if num_tokens % 128: | ||
| 291 | + padded_tokens = (num_tokens + 127) // 128 * 128 | ||
| 292 | + kernel_out_mul_grad = out_mul_grad.new_zeros((padded_tokens, 1, _MHC_MULT3)) | ||
| 293 | + kernel_sqrsum_grad = sqrsum_grad.new_zeros((padded_tokens, 1)) | ||
| 294 | + kernel_x = x.new_zeros((padded_tokens, mhc_hidden_size)) | ||
| 295 | + kernel_x_grad = x_grad.new_zeros((padded_tokens, mhc_hidden_size)) | ||
| 296 | + kernel_out_mul_grad[:num_tokens].copy_(out_mul_grad) | ||
| 297 | + kernel_sqrsum_grad[:num_tokens].copy_(sqrsum_grad) | ||
| 298 | + kernel_x[:num_tokens].copy_(x) | ||
| 299 | + kernel_x_grad[:num_tokens].copy_(x_grad) | ||
| 300 | + | ||
| 301 | + mhc_gemm_with_sqrsum_bwd_asc( | ||
| 302 | + kernel_out_mul_grad, | ||
| 303 | + kernel_sqrsum_grad, | ||
| 304 | + kernel_x, | ||
| 305 | + fn, | ||
| 306 | + kernel_x_grad, | ||
| 307 | + fn_grad, | ||
| 308 | + token_block=128, | ||
| 309 | + hidden_block=128, | ||
| 310 | + ) | ||
| 311 | + if kernel_x_grad is not x_grad: | ||
| 312 | + x_grad.copy_(kernel_x_grad[:num_tokens]) | ||
| 313 | + | ||
| 314 | + | ||
| 315 | +def mhc_reduce_partials_and_rmsnorm_forward( | ||
| 316 | + out_mul_splitted: torch.Tensor, | ||
| 317 | + sqrsum_splitted: torch.Tensor, | ||
| 318 | + out_mul: torch.Tensor, | ||
| 319 | + sqrsum: torch.Tensor, | ||
| 320 | + out: torch.Tensor, | ||
| 321 | + rms_group_size: int, | ||
| 322 | + rms_eps: float, | ||
| 323 | + n_splits: int, | ||
| 324 | +) -> None: | ||
| 325 | + """Reduce split partials and apply RMS normalization into caller buffers.""" | ||
| 326 | + | ||
| 327 | + tensors = (out_mul_splitted, sqrsum_splitted, out_mul, sqrsum, out) | ||
| 328 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 329 | + raise TypeError('all reduce/RMSNorm forward inputs and outputs must be torch.Tensor instances.') | ||
| 330 | + require_ascend() | ||
| 331 | + _check_tensor('out_mul_splitted', out_mul_splitted, torch.float32, 4) | ||
| 332 | + device = out_mul_splitted.device | ||
| 333 | + _check_tensor('sqrsum_splitted', sqrsum_splitted, torch.float32, 3, device=device) | ||
| 334 | + _check_tensor('out_mul', out_mul, torch.float32, 3, device=device) | ||
| 335 | + _check_tensor('sqrsum', sqrsum, torch.float32, 2, device=device) | ||
| 336 | + _check_tensor('out', out, torch.float32, 2, device=device) | ||
| 337 | + n_splits = _check_positive_int('n_splits', n_splits) | ||
| 338 | + rms_group_size = _check_positive_int('rms_group_size', rms_group_size) | ||
| 339 | + rms_eps = _check_non_negative_real('rms_eps', rms_eps) | ||
| 340 | + | ||
| 341 | + if out_mul_splitted.shape[0] != n_splits: | ||
| 342 | + raise ValueError('out_mul_splitted first dimension must equal n_splits.') | ||
| 343 | + _, num_tokens, num_rms_groups, mhc_mult3 = out_mul_splitted.shape | ||
| 344 | + if num_rms_groups != 1 or mhc_mult3 != _MHC_MULT3: | ||
| 345 | + raise ValueError( | ||
| 346 | + f'out_mul_splitted must have shape (n_splits, num_tokens, 1, 24), got {tuple(out_mul_splitted.shape)}.' | ||
| 347 | + ) | ||
| 348 | + _check_shape('sqrsum_splitted', sqrsum_splitted, (n_splits, num_tokens, 1)) | ||
| 349 | + _check_shape('out_mul', out_mul, (num_tokens, 1, _MHC_MULT3)) | ||
| 350 | + _check_shape('sqrsum', sqrsum, (num_tokens, 1)) | ||
| 351 | + _check_shape('out', out, (num_tokens, _MHC_MULT3)) | ||
| 352 | + _check_output_aliases( | ||
| 353 | + {'out_mul_splitted': out_mul_splitted, 'sqrsum_splitted': sqrsum_splitted}, | ||
| 354 | + {'out_mul': out_mul, 'sqrsum': sqrsum, 'out': out}, | ||
| 355 | + ) | ||
| 356 | + if num_tokens == 0: | ||
| 357 | + return | ||
| 358 | + | ||
| 359 | + mhc_reduce_partials_and_rmsnorm_fwd_asc( | ||
| 360 | + out_mul_splitted, | ||
| 361 | + sqrsum_splitted, | ||
| 362 | + out_mul, | ||
| 363 | + sqrsum, | ||
| 364 | + out, | ||
| 365 | + rms_group_size, | ||
| 366 | + rms_eps, | ||
| 367 | + n_splits, | ||
| 368 | + ) | ||
| 369 | + | ||
| 370 | + | ||
| 371 | +def mhc_reduce_partials_and_rmsnorm_backward( | ||
| 372 | + out_grad: torch.Tensor, | ||
| 373 | + out_mul: torch.Tensor, | ||
| 374 | + sqrsum: torch.Tensor, | ||
| 375 | + out_mul_grad: torch.Tensor, | ||
| 376 | + sqrsum_grad: torch.Tensor, | ||
| 377 | + rms_group_size: int, | ||
| 378 | + rms_eps: float, | ||
| 379 | +) -> None: | ||
| 380 | + """Backpropagate through the MHC partial reduction and RMS normalization.""" | ||
| 381 | + | ||
| 382 | + tensors = (out_grad, out_mul, sqrsum, out_mul_grad, sqrsum_grad) | ||
| 383 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 384 | + raise TypeError('all reduce/RMSNorm backward inputs and outputs must be torch.Tensor instances.') | ||
| 385 | + require_ascend() | ||
| 386 | + _check_tensor('out_grad', out_grad, torch.float32, 2) | ||
| 387 | + device = out_grad.device | ||
| 388 | + _check_tensor('out_mul', out_mul, torch.float32, 3, device=device) | ||
| 389 | + _check_tensor('sqrsum', sqrsum, torch.float32, 2, device=device) | ||
| 390 | + _check_tensor('out_mul_grad', out_mul_grad, torch.float32, 3, device=device) | ||
| 391 | + _check_tensor('sqrsum_grad', sqrsum_grad, torch.float32, 2, device=device) | ||
| 392 | + rms_group_size = _check_positive_int('rms_group_size', rms_group_size) | ||
| 393 | + rms_eps = _check_non_negative_real('rms_eps', rms_eps) | ||
| 394 | + | ||
| 395 | + num_tokens = out_grad.shape[0] | ||
| 396 | + _check_shape('out_grad', out_grad, (num_tokens, _MHC_MULT3)) | ||
| 397 | + _check_shape('out_mul', out_mul, (num_tokens, 1, _MHC_MULT3)) | ||
| 398 | + _check_shape('sqrsum', sqrsum, (num_tokens, 1)) | ||
| 399 | + _check_shape('out_mul_grad', out_mul_grad, (num_tokens, 1, _MHC_MULT3)) | ||
| 400 | + _check_shape('sqrsum_grad', sqrsum_grad, (num_tokens, 1)) | ||
| 401 | + _check_output_aliases( | ||
| 402 | + {'out_grad': out_grad, 'out_mul': out_mul, 'sqrsum': sqrsum}, | ||
| 403 | + {'out_mul_grad': out_mul_grad, 'sqrsum_grad': sqrsum_grad}, | ||
| 404 | + ) | ||
| 405 | + if num_tokens == 0: | ||
| 406 | + return | ||
| 407 | + | ||
| 408 | + mhc_reduce_partials_and_rmsnorm_bwd_asc( | ||
| 409 | + out_grad, | ||
| 410 | + out_mul, | ||
| 411 | + sqrsum, | ||
| 412 | + out_mul_grad, | ||
| 413 | + sqrsum_grad, | ||
| 414 | + rms_group_size, | ||
| 415 | + rms_eps, | ||
| 416 | + ) | ||
| @@ -0,0 +1,187 @@ | |||
| 1 | +"""Public interfaces for MHC post forward and backward.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +import torch | ||
| 6 | + | ||
| 7 | +from cann_ops_tilelang.config import get_num_vector_cores, require_ascend, should_print_kernel_source | ||
| 8 | +from cann_ops_tilelang.utils import align_up, ceil_div | ||
| 9 | + | ||
| 10 | +from .post_kernel import get_mhc_post_backward_kernel, get_mhc_post_forward_kernel | ||
| 11 | + | ||
| 12 | +_HIDDEN_ALIGNMENT = 64 | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +def _check_npu_tensor( | ||
| 16 | + name: str, | ||
| 17 | + tensor: torch.Tensor, | ||
| 18 | + *, | ||
| 19 | + dtype: torch.dtype, | ||
| 20 | + device: torch.device | None = None, | ||
| 21 | +) -> None: | ||
| 22 | + if not isinstance(tensor, torch.Tensor): | ||
| 23 | + raise TypeError(f'{name} must be a torch.Tensor.') | ||
| 24 | + if tensor.device.type != 'npu': | ||
| 25 | + raise ValueError(f'{name} must be an NPU tensor, got {tensor.device}.') | ||
| 26 | + if device is not None and tensor.device != device: | ||
| 27 | + raise ValueError(f'{name} must be on {device}, got {tensor.device}.') | ||
| 28 | + if tensor.dtype != dtype: | ||
| 29 | + raise TypeError(f'{name} must use {dtype}, got {tensor.dtype}.') | ||
| 30 | + if not tensor.is_contiguous(): | ||
| 31 | + raise ValueError(f'{name} must be contiguous.') | ||
| 32 | + | ||
| 33 | + | ||
| 34 | +def _shares_storage(left: torch.Tensor, right: torch.Tensor) -> bool: | ||
| 35 | + return ( | ||
| 36 | + left.numel() > 0 | ||
| 37 | + and right.numel() > 0 | ||
| 38 | + and left.untyped_storage().data_ptr() == right.untyped_storage().data_ptr() | ||
| 39 | + ) | ||
| 40 | + | ||
| 41 | + | ||
| 42 | +def _validate_common_inputs( | ||
| 43 | + x: torch.Tensor, | ||
| 44 | + residual: torch.Tensor, | ||
| 45 | + post_layer_mix: torch.Tensor, | ||
| 46 | + comb_res_mix: torch.Tensor, | ||
| 47 | +) -> tuple[int, int, int, int]: | ||
| 48 | + tensors = (x, residual, post_layer_mix, comb_res_mix) | ||
| 49 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 50 | + raise TypeError('x, residual, post_layer_mix and comb_res_mix must be torch.Tensor instances.') | ||
| 51 | + | ||
| 52 | + require_ascend() | ||
| 53 | + _check_npu_tensor('x', x, dtype=torch.bfloat16) | ||
| 54 | + _check_npu_tensor('residual', residual, dtype=torch.bfloat16, device=x.device) | ||
| 55 | + _check_npu_tensor('post_layer_mix', post_layer_mix, dtype=torch.float32, device=x.device) | ||
| 56 | + _check_npu_tensor('comb_res_mix', comb_res_mix, dtype=torch.float32, device=x.device) | ||
| 57 | + | ||
| 58 | + if residual.ndim != 4: | ||
| 59 | + raise ValueError('residual must have shape (num_seqs, num_tokens, mhc_mult, hidden_size).') | ||
| 60 | + num_seqs, num_tokens, mhc_mult, hidden_size = residual.shape | ||
| 61 | + if mhc_mult <= 0: | ||
| 62 | + raise ValueError(f'mhc_mult must be positive, got {mhc_mult}.') | ||
| 63 | + if hidden_size <= 0 or hidden_size % _HIDDEN_ALIGNMENT: | ||
| 64 | + raise ValueError(f'hidden_size must be a positive multiple of {_HIDDEN_ALIGNMENT}, got {hidden_size}.') | ||
| 65 | + | ||
| 66 | + expected_x_shape = (num_seqs, num_tokens, hidden_size) | ||
| 67 | + if x.shape != expected_x_shape: | ||
| 68 | + raise ValueError(f'x must have shape {expected_x_shape}, got {tuple(x.shape)}.') | ||
| 69 | + expected_post_shape = (num_seqs, num_tokens, mhc_mult, 1) | ||
| 70 | + if post_layer_mix.shape != expected_post_shape: | ||
| 71 | + raise ValueError(f'post_layer_mix must have shape {expected_post_shape}, got {tuple(post_layer_mix.shape)}.') | ||
| 72 | + expected_comb_shape = (num_seqs, num_tokens, mhc_mult, mhc_mult) | ||
| 73 | + if comb_res_mix.shape != expected_comb_shape: | ||
| 74 | + raise ValueError(f'comb_res_mix must have shape {expected_comb_shape}, got {tuple(comb_res_mix.shape)}.') | ||
| 75 | + return num_seqs, num_tokens, mhc_mult, hidden_size | ||
| 76 | + | ||
| 77 | + | ||
| 78 | +def _choose_hidden_block(hidden_size: int) -> int: | ||
| 79 | + hidden_block = hidden_size if hidden_size <= 4096 else min(ceil_div(hidden_size, 2), 4096) | ||
| 80 | + return align_up(hidden_block, _HIDDEN_ALIGNMENT) | ||
| 81 | + | ||
| 82 | + | ||
| 83 | +def mhc_post_forward( | ||
| 84 | + x: torch.Tensor, | ||
| 85 | + residual: torch.Tensor, | ||
| 86 | + post_layer_mix: torch.Tensor, | ||
| 87 | + comb_res_mix: torch.Tensor, | ||
| 88 | + out: torch.Tensor | None = None, | ||
| 89 | +) -> torch.Tensor: | ||
| 90 | + """Apply the MHC post residual mix on Ascend. | ||
| 91 | + | ||
| 92 | + ``x`` has shape ``(num_seqs, num_tokens, hidden_size)`` and ``residual`` | ||
| 93 | + has shape ``(num_seqs, num_tokens, mhc_mult, hidden_size)``. Activations | ||
| 94 | + use BF16, while both mix tensors use FP32. | ||
| 95 | + """ | ||
| 96 | + num_seqs, num_tokens, mhc_mult, hidden_size = _validate_common_inputs( | ||
| 97 | + x, | ||
| 98 | + residual, | ||
| 99 | + post_layer_mix, | ||
| 100 | + comb_res_mix, | ||
| 101 | + ) | ||
| 102 | + if out is not None: | ||
| 103 | + _check_npu_tensor('out', out, dtype=torch.bfloat16, device=x.device) | ||
| 104 | + if out.shape != residual.shape: | ||
| 105 | + raise ValueError(f'out must have shape {tuple(residual.shape)}, got {tuple(out.shape)}.') | ||
| 106 | + for name, tensor in ( | ||
| 107 | + ('x', x), | ||
| 108 | + ('residual', residual), | ||
| 109 | + ('post_layer_mix', post_layer_mix), | ||
| 110 | + ('comb_res_mix', comb_res_mix), | ||
| 111 | + ): | ||
| 112 | + if _shares_storage(out, tensor): | ||
| 113 | + raise ValueError(f'out and {name} must not share storage.') | ||
| 114 | + | ||
| 115 | + result = torch.empty_like(residual) if out is None else out | ||
| 116 | + if num_seqs == 0 or num_tokens == 0: | ||
| 117 | + return result | ||
| 118 | + | ||
| 119 | + hidden_block = _choose_hidden_block(hidden_size) | ||
| 120 | + kernel = get_mhc_post_forward_kernel( | ||
| 121 | + mhc_mult, | ||
| 122 | + hidden_size, | ||
| 123 | + hidden_block, | ||
| 124 | + get_num_vector_cores(), | ||
| 125 | + ) | ||
| 126 | + if should_print_kernel_source(): | ||
| 127 | + print(kernel.get_kernel_source()) | ||
| 128 | + flattened_tokens = num_seqs * num_tokens | ||
| 129 | + kernel( | ||
| 130 | + comb_res_mix.view(flattened_tokens, mhc_mult, mhc_mult), | ||
| 131 | + residual.view(flattened_tokens, mhc_mult, hidden_size), | ||
| 132 | + post_layer_mix.view(flattened_tokens, mhc_mult), | ||
| 133 | + x.view(flattened_tokens, hidden_size), | ||
| 134 | + result.view(flattened_tokens, mhc_mult, hidden_size), | ||
| 135 | + ) | ||
| 136 | + return result | ||
| 137 | + | ||
| 138 | + | ||
| 139 | +def mhc_post_backward( | ||
| 140 | + x: torch.Tensor, | ||
| 141 | + residual: torch.Tensor, | ||
| 142 | + post_layer_mix: torch.Tensor, | ||
| 143 | + comb_res_mix: torch.Tensor, | ||
| 144 | + d_o: torch.Tensor, | ||
| 145 | +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: | ||
| 146 | + """Return gradients for the four inputs of :func:`mhc_post_forward`.""" | ||
| 147 | + num_seqs, num_tokens, mhc_mult, hidden_size = _validate_common_inputs( | ||
| 148 | + x, | ||
| 149 | + residual, | ||
| 150 | + post_layer_mix, | ||
| 151 | + comb_res_mix, | ||
| 152 | + ) | ||
| 153 | + _check_npu_tensor('d_o', d_o, dtype=torch.bfloat16, device=x.device) | ||
| 154 | + if d_o.shape != residual.shape: | ||
| 155 | + raise ValueError(f'd_o must have shape {tuple(residual.shape)}, got {tuple(d_o.shape)}.') | ||
| 156 | + | ||
| 157 | + grad_x = torch.empty_like(x) | ||
| 158 | + grad_residual = torch.empty_like(residual) | ||
| 159 | + grad_post_layer_mix = torch.empty_like(post_layer_mix) | ||
| 160 | + grad_comb_res_mix = torch.empty_like(comb_res_mix) | ||
| 161 | + if num_seqs == 0 or num_tokens == 0: | ||
| 162 | + return grad_x, grad_residual, grad_post_layer_mix, grad_comb_res_mix | ||
| 163 | + | ||
| 164 | + hidden_block = _choose_hidden_block(hidden_size) | ||
| 165 | + num_stages = 3 if hidden_block <= 2560 else 2 | ||
| 166 | + kernel = get_mhc_post_backward_kernel( | ||
| 167 | + mhc_mult, | ||
| 168 | + hidden_size, | ||
| 169 | + hidden_block, | ||
| 170 | + num_stages, | ||
| 171 | + get_num_vector_cores(), | ||
| 172 | + ) | ||
| 173 | + if should_print_kernel_source(): | ||
| 174 | + print(kernel.get_kernel_source()) | ||
| 175 | + flattened_tokens = num_seqs * num_tokens | ||
| 176 | + kernel( | ||
| 177 | + d_o.view(flattened_tokens, mhc_mult, hidden_size), | ||
| 178 | + comb_res_mix.view(flattened_tokens, mhc_mult, mhc_mult), | ||
| 179 | + residual.view(flattened_tokens, mhc_mult, hidden_size), | ||
| 180 | + post_layer_mix.view(flattened_tokens, mhc_mult), | ||
| 181 | + x.view(flattened_tokens, hidden_size), | ||
| 182 | + grad_comb_res_mix.view(flattened_tokens, mhc_mult, mhc_mult), | ||
| 183 | + grad_residual.view(flattened_tokens, mhc_mult, hidden_size), | ||
| 184 | + grad_post_layer_mix.view(flattened_tokens, mhc_mult), | ||
| 185 | + grad_x.view(flattened_tokens, hidden_size), | ||
| 186 | + ) | ||
| 187 | + return grad_x, grad_residual, grad_post_layer_mix, grad_comb_res_mix | ||
| @@ -0,0 +1,290 @@ | |||
| 1 | +"""TileLang Ascend kernels for MHC post forward and backward.""" | ||
| 2 | + | ||
| 3 | +import tilelang | ||
| 4 | +from tilelang.ascend import language as T | ||
| 5 | +from tilelang.ascend.language import simd as S | ||
| 6 | + | ||
| 7 | +_VECTOR_LANES = 64 | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +@tilelang.jit | ||
| 11 | +def get_mhc_post_forward_kernel( | ||
| 12 | + mhc_mult: int, | ||
| 13 | + hidden_size: int, | ||
| 14 | + hidden_block: int, | ||
| 15 | + num_vector_cores: int, | ||
| 16 | +): | ||
| 17 | + """Build the Ascend MHC post forward kernel.""" | ||
| 18 | + num_tokens = T.dynamic('num_tokens') | ||
| 19 | + num_hidden_tiles = T.ceildiv(hidden_size, hidden_block) | ||
| 20 | + num_chunks = hidden_block // _VECTOR_LANES | ||
| 21 | + num_stages = 3 | ||
| 22 | + | ||
| 23 | + @T.prim_func | ||
| 24 | + def mhc_post_forward_kernel( | ||
| 25 | + comb_res_mix: T.Tensor[(num_tokens, mhc_mult, mhc_mult), T.float32], | ||
| 26 | + residual: T.Tensor[(num_tokens, mhc_mult, hidden_size), T.bfloat16], | ||
| 27 | + post_layer_mix: T.Tensor[(num_tokens, mhc_mult), T.float32], | ||
| 28 | + x: T.Tensor[(num_tokens, hidden_size), T.bfloat16], | ||
| 29 | + out: T.Tensor[(num_tokens, mhc_mult, hidden_size), T.bfloat16], | ||
| 30 | + ) -> None: | ||
| 31 | + with T.Kernel(num_vector_cores) as core_id: | ||
| 32 | + residual_ub = T.alloc_shared((mhc_mult, hidden_block), T.bfloat16) | ||
| 33 | + x_ub = T.alloc_shared((hidden_block,), T.bfloat16) | ||
| 34 | + comb_res_mix_ub = T.alloc_shared((mhc_mult, mhc_mult), T.float32) | ||
| 35 | + post_layer_mix_ub = T.alloc_shared((T.ceildiv(mhc_mult, 8) * 8,), T.float32) | ||
| 36 | + out_ub = T.alloc_shared((mhc_mult, hidden_block), T.bfloat16) | ||
| 37 | + T.annotate_buffer_versions( | ||
| 38 | + { | ||
| 39 | + residual_ub: num_stages, | ||
| 40 | + x_ub: num_stages, | ||
| 41 | + comb_res_mix_ub: num_stages, | ||
| 42 | + post_layer_mix_ub: num_stages, | ||
| 43 | + out_ub: num_stages, | ||
| 44 | + } | ||
| 45 | + ) | ||
| 46 | + | ||
| 47 | + for token_id, hidden_tile in T.Persistent( | ||
| 48 | + [num_tokens, num_hidden_tiles], | ||
| 49 | + num_vector_cores, | ||
| 50 | + core_id, | ||
| 51 | + group_size=1, | ||
| 52 | + num_stages=num_stages, | ||
| 53 | + ): | ||
| 54 | + T.copy(comb_res_mix[token_id, 0, 0], comb_res_mix_ub) | ||
| 55 | + T.copy(post_layer_mix[token_id, 0], post_layer_mix_ub[0:mhc_mult]) | ||
| 56 | + T.copy(residual[token_id, 0, hidden_tile * hidden_block], residual_ub) | ||
| 57 | + T.copy(x[token_id, hidden_tile * hidden_block], x_ub) | ||
| 58 | + | ||
| 59 | + with T.SimdVF(): | ||
| 60 | + comb_mix = S.alloc_local((mhc_mult * mhc_mult,), T.float32) | ||
| 61 | + post_mix = S.alloc_local((mhc_mult,), T.float32) | ||
| 62 | + for input_head in T.unroll(mhc_mult, explicit=True): | ||
| 63 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 64 | + comb_mix[input_head * mhc_mult + output_head] = S.vld( | ||
| 65 | + comb_res_mix_ub[input_head, output_head], | ||
| 66 | + dist='BRC_B32', | ||
| 67 | + ) | ||
| 68 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 69 | + post_mix[output_head] = S.vld( | ||
| 70 | + post_layer_mix_ub[output_head], | ||
| 71 | + dist='BRC_B32', | ||
| 72 | + ) | ||
| 73 | + out_reg = S.alloc_local((mhc_mult,), T.float32) | ||
| 74 | + residual_reg = S.alloc_local((mhc_mult,), T.float32) | ||
| 75 | + for chunk in T.serial(num_chunks): | ||
| 76 | + column = chunk * _VECTOR_LANES | ||
| 77 | + x_fp32 = S.vcvt( | ||
| 78 | + S.vld(x_ub[column], dist='UNPK_B16'), | ||
| 79 | + T.float32, | ||
| 80 | + part=0, | ||
| 81 | + ) | ||
| 82 | + for input_head in T.unroll(mhc_mult, explicit=True): | ||
| 83 | + residual_reg[input_head] = S.vcvt( | ||
| 84 | + S.vld(residual_ub[input_head, column], dist='UNPK_B16'), | ||
| 85 | + T.float32, | ||
| 86 | + part=0, | ||
| 87 | + ) | ||
| 88 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 89 | + out_reg[output_head] = S.vmul(x_fp32, post_mix[output_head]) | ||
| 90 | + for input_head in T.unroll(mhc_mult, explicit=True): | ||
| 91 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 92 | + S.vmula( | ||
| 93 | + out_reg[output_head], | ||
| 94 | + residual_reg[input_head], | ||
| 95 | + comb_mix[input_head * mhc_mult + output_head], | ||
| 96 | + ) | ||
| 97 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 98 | + S.vsts( | ||
| 99 | + out_ub[output_head, column], | ||
| 100 | + S.vcvt(out_reg[output_head], T.bfloat16), | ||
| 101 | + dist='PK_B32', | ||
| 102 | + ) | ||
| 103 | + | ||
| 104 | + T.copy(out_ub, out[token_id, 0, hidden_tile * hidden_block]) | ||
| 105 | + | ||
| 106 | + return mhc_post_forward_kernel | ||
| 107 | + | ||
| 108 | + | ||
| 109 | +@tilelang.jit | ||
| 110 | +def get_mhc_post_backward_kernel( | ||
| 111 | + mhc_mult: int, | ||
| 112 | + hidden_size: int, | ||
| 113 | + hidden_block: int, | ||
| 114 | + num_stages: int, | ||
| 115 | + num_vector_cores: int, | ||
| 116 | +): | ||
| 117 | + """Build the Ascend MHC post backward kernel.""" | ||
| 118 | + num_tokens = T.dynamic('num_tokens') | ||
| 119 | + num_hidden_tiles = T.ceildiv(hidden_size, hidden_block) | ||
| 120 | + num_chunks = hidden_block // _VECTOR_LANES | ||
| 121 | + | ||
| 122 | + @T.prim_func | ||
| 123 | + def mhc_post_backward_kernel( | ||
| 124 | + grad_out: T.Tensor[(num_tokens, mhc_mult, hidden_size), T.bfloat16], | ||
| 125 | + comb_res_mix: T.Tensor[(num_tokens, mhc_mult, mhc_mult), T.float32], | ||
| 126 | + residual: T.Tensor[(num_tokens, mhc_mult, hidden_size), T.bfloat16], | ||
| 127 | + post_layer_mix: T.Tensor[(num_tokens, mhc_mult), T.float32], | ||
| 128 | + x: T.Tensor[(num_tokens, hidden_size), T.bfloat16], | ||
| 129 | + grad_comb_res_mix: T.Tensor[(num_tokens, mhc_mult, mhc_mult), T.float32], | ||
| 130 | + grad_residual: T.Tensor[(num_tokens, mhc_mult, hidden_size), T.bfloat16], | ||
| 131 | + grad_post_layer_mix: T.Tensor[(num_tokens, mhc_mult), T.float32], | ||
| 132 | + grad_x: T.Tensor[(num_tokens, hidden_size), T.bfloat16], | ||
| 133 | + ) -> None: | ||
| 134 | + with T.Kernel(num_vector_cores) as core_id: | ||
| 135 | + grad_out_ub = T.alloc_shared((mhc_mult, hidden_block), T.bfloat16) | ||
| 136 | + x_ub = T.alloc_shared((hidden_block,), T.bfloat16) | ||
| 137 | + residual_ub = T.alloc_shared((mhc_mult, hidden_block), T.bfloat16) | ||
| 138 | + comb_res_mix_ub = T.alloc_shared((mhc_mult, mhc_mult), T.float32) | ||
| 139 | + post_layer_mix_ub = T.alloc_shared((T.ceildiv(mhc_mult, 8) * 8,), T.float32) | ||
| 140 | + grad_x_ub = T.alloc_shared((hidden_block,), T.bfloat16) | ||
| 141 | + grad_residual_ub = T.alloc_shared((mhc_mult, hidden_block), T.bfloat16) | ||
| 142 | + grad_post_mix_ub = T.alloc_shared((T.ceildiv(mhc_mult, 8) * 8,), T.float32) | ||
| 143 | + grad_comb_mix_ub = T.alloc_shared((mhc_mult, mhc_mult), T.float32) | ||
| 144 | + post_mix_acc = T.alloc_shared((mhc_mult, _VECTOR_LANES), T.float32) | ||
| 145 | + comb_mix_acc = T.alloc_shared((mhc_mult * mhc_mult, _VECTOR_LANES), T.float32) | ||
| 146 | + T.annotate_buffer_versions( | ||
| 147 | + { | ||
| 148 | + grad_out_ub: num_stages, | ||
| 149 | + x_ub: num_stages, | ||
| 150 | + residual_ub: num_stages, | ||
| 151 | + grad_x_ub: num_stages, | ||
| 152 | + grad_residual_ub: num_stages, | ||
| 153 | + comb_res_mix_ub: num_stages, | ||
| 154 | + post_layer_mix_ub: num_stages, | ||
| 155 | + grad_post_mix_ub: 1, | ||
| 156 | + grad_comb_mix_ub: 1, | ||
| 157 | + post_mix_acc: 1, | ||
| 158 | + comb_mix_acc: 1, | ||
| 159 | + } | ||
| 160 | + ) | ||
| 161 | + | ||
| 162 | + for token_id in T.Persistent( | ||
| 163 | + [num_tokens], | ||
| 164 | + num_vector_cores, | ||
| 165 | + core_id, | ||
| 166 | + group_size=1, | ||
| 167 | + num_stages=num_stages, | ||
| 168 | + ): | ||
| 169 | + T.copy(comb_res_mix[token_id, 0, 0], comb_res_mix_ub) | ||
| 170 | + T.copy(post_layer_mix[token_id, 0], post_layer_mix_ub[0:mhc_mult]) | ||
| 171 | + | ||
| 172 | + with T.SimdVF(): | ||
| 173 | + zero = S.vdup(0.0, T.float32) | ||
| 174 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 175 | + S.vsts(post_mix_acc[output_head, 0], zero) | ||
| 176 | + for index in T.unroll(mhc_mult * mhc_mult, explicit=True): | ||
| 177 | + S.vsts(comb_mix_acc[index, 0], zero) | ||
| 178 | + | ||
| 179 | + for hidden_tile in T.serial(num_hidden_tiles): | ||
| 180 | + T.copy(grad_out[token_id, 0, hidden_tile * hidden_block], grad_out_ub) | ||
| 181 | + T.copy(x[token_id, hidden_tile * hidden_block], x_ub) | ||
| 182 | + T.copy(residual[token_id, 0, hidden_tile * hidden_block], residual_ub) | ||
| 183 | + | ||
| 184 | + with T.SimdVF(): | ||
| 185 | + post_mix = S.alloc_local((mhc_mult,), T.float32) | ||
| 186 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 187 | + post_mix[output_head] = S.vld( | ||
| 188 | + post_layer_mix_ub[output_head], | ||
| 189 | + dist='BRC_B32', | ||
| 190 | + ) | ||
| 191 | + comb_mix = S.alloc_local((mhc_mult * mhc_mult,), T.float32) | ||
| 192 | + for input_head in T.unroll(mhc_mult, explicit=True): | ||
| 193 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 194 | + comb_mix[input_head * mhc_mult + output_head] = S.vld( | ||
| 195 | + comb_res_mix_ub[input_head, output_head], | ||
| 196 | + dist='BRC_B32', | ||
| 197 | + ) | ||
| 198 | + | ||
| 199 | + grad_out_reg = S.alloc_local((mhc_mult,), T.float32) | ||
| 200 | + grad_x_reg = S.alloc_var(T.float32) | ||
| 201 | + grad_residual_reg = S.alloc_var(T.float32) | ||
| 202 | + post_acc_reg = S.alloc_var(T.float32) | ||
| 203 | + grad_comb_mix_input = S.alloc_local((mhc_mult,), T.float32) | ||
| 204 | + | ||
| 205 | + for chunk in T.serial(num_chunks): | ||
| 206 | + column = chunk * _VECTOR_LANES | ||
| 207 | + x_fp32 = S.vcvt( | ||
| 208 | + S.vld(x_ub[column], dist='UNPK_B16'), | ||
| 209 | + T.float32, | ||
| 210 | + part=0, | ||
| 211 | + ) | ||
| 212 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 213 | + grad_out_reg[output_head] = S.vcvt( | ||
| 214 | + S.vld(grad_out_ub[output_head, column], dist='UNPK_B16'), | ||
| 215 | + T.float32, | ||
| 216 | + part=0, | ||
| 217 | + ) | ||
| 218 | + | ||
| 219 | + grad_x_reg = S.vdup(0.0, T.float32) | ||
| 220 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 221 | + S.vmula(grad_x_reg, grad_out_reg[output_head], post_mix[output_head]) | ||
| 222 | + S.vsts( | ||
| 223 | + grad_x_ub[column], | ||
| 224 | + S.vcvt(grad_x_reg, T.bfloat16), | ||
| 225 | + dist='PK_B32', | ||
| 226 | + ) | ||
| 227 | + | ||
| 228 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 229 | + post_acc_reg = S.vld(post_mix_acc[output_head, 0]) | ||
| 230 | + S.vmula(post_acc_reg, grad_out_reg[output_head], x_fp32) | ||
| 231 | + S.vsts(post_mix_acc[output_head, 0], post_acc_reg) | ||
| 232 | + | ||
| 233 | + for input_head in T.unroll(mhc_mult, explicit=True): | ||
| 234 | + residual_input = S.vcvt( | ||
| 235 | + S.vld(residual_ub[input_head, column], dist='UNPK_B16'), | ||
| 236 | + T.float32, | ||
| 237 | + part=0, | ||
| 238 | + ) | ||
| 239 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 240 | + grad_comb_mix_input[output_head] = S.vld( | ||
| 241 | + comb_mix_acc[input_head * mhc_mult + output_head, 0] | ||
| 242 | + ) | ||
| 243 | + grad_residual_reg = S.vdup(0.0, T.float32) | ||
| 244 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 245 | + S.vmula( | ||
| 246 | + grad_residual_reg, | ||
| 247 | + grad_out_reg[output_head], | ||
| 248 | + comb_mix[input_head * mhc_mult + output_head], | ||
| 249 | + ) | ||
| 250 | + S.vmula( | ||
| 251 | + grad_comb_mix_input[output_head], | ||
| 252 | + grad_out_reg[output_head], | ||
| 253 | + residual_input, | ||
| 254 | + ) | ||
| 255 | + S.vsts( | ||
| 256 | + grad_residual_ub[input_head, column], | ||
| 257 | + S.vcvt(grad_residual_reg, T.bfloat16), | ||
| 258 | + dist='PK_B32', | ||
| 259 | + ) | ||
| 260 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 261 | + S.vsts( | ||
| 262 | + comb_mix_acc[input_head * mhc_mult + output_head, 0], | ||
| 263 | + grad_comb_mix_input[output_head], | ||
| 264 | + ) | ||
| 265 | + | ||
| 266 | + T.copy(grad_x_ub, grad_x[token_id, hidden_tile * hidden_block]) | ||
| 267 | + T.copy( | ||
| 268 | + grad_residual_ub, | ||
| 269 | + grad_residual[token_id, 0, hidden_tile * hidden_block], | ||
| 270 | + ) | ||
| 271 | + | ||
| 272 | + with T.SimdVF(): | ||
| 273 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 274 | + S.vsts( | ||
| 275 | + grad_post_mix_ub[output_head], | ||
| 276 | + S.vcadd(S.vld(post_mix_acc[output_head, 0])), | ||
| 277 | + dist='ONEPT_B32', | ||
| 278 | + ) | ||
| 279 | + for input_head in T.unroll(mhc_mult, explicit=True): | ||
| 280 | + for output_head in T.unroll(mhc_mult, explicit=True): | ||
| 281 | + S.vsts( | ||
| 282 | + grad_comb_mix_ub[input_head, output_head], | ||
| 283 | + S.vcadd(S.vld(comb_mix_acc[input_head * mhc_mult + output_head, 0])), | ||
| 284 | + dist='ONEPT_B32', | ||
| 285 | + ) | ||
| 286 | + | ||
| 287 | + T.copy(grad_post_mix_ub[0:mhc_mult], grad_post_layer_mix[token_id, 0]) | ||
| 288 | + T.copy(grad_comb_mix_ub, grad_comb_res_mix[token_id, 0, 0]) | ||
| 289 | + | ||
| 290 | + return mhc_post_backward_kernel | ||
| @@ -0,0 +1,138 @@ | |||
| 1 | +"""Ascend MHC pre-apply mix forward and backward operators.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +import torch | ||
| 6 | + | ||
| 7 | +from cann_ops_tilelang.config import get_num_vector_cores, require_ascend | ||
| 8 | +from cann_ops_tilelang.utils import align_up, ceil_div | ||
| 9 | + | ||
| 10 | +from .pre_apply_mix_kernel import mhc_pre_apply_mix_bwd_asc, mhc_pre_apply_mix_fwd_asc | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +def _check_tensor( | ||
| 14 | + name: str, | ||
| 15 | + tensor: torch.Tensor, | ||
| 16 | + dtype: torch.dtype, | ||
| 17 | + ndim: int, | ||
| 18 | + *, | ||
| 19 | + device: torch.device | None = None, | ||
| 20 | +) -> None: | ||
| 21 | + if not isinstance(tensor, torch.Tensor): | ||
| 22 | + raise TypeError(f'{name} must be a torch.Tensor.') | ||
| 23 | + if tensor.device.type != 'npu': | ||
| 24 | + raise ValueError(f'{name} must be an NPU tensor, got {tensor.device}.') | ||
| 25 | + if device is not None and tensor.device != device: | ||
| 26 | + raise ValueError(f'{name} must be on {device}, got {tensor.device}.') | ||
| 27 | + if tensor.dtype != dtype: | ||
| 28 | + raise TypeError(f'{name} must use {dtype}, got {tensor.dtype}.') | ||
| 29 | + if tensor.ndim != ndim: | ||
| 30 | + raise ValueError(f'{name} must be {ndim}-dimensional, got shape {tuple(tensor.shape)}.') | ||
| 31 | + if not tensor.is_contiguous(): | ||
| 32 | + raise ValueError(f'{name} must be contiguous.') | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +def _shares_storage(left: torch.Tensor, right: torch.Tensor) -> bool: | ||
| 36 | + return ( | ||
| 37 | + left.numel() > 0 | ||
| 38 | + and right.numel() > 0 | ||
| 39 | + and left.untyped_storage().data_ptr() == right.untyped_storage().data_ptr() | ||
| 40 | + ) | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +def _check_shape(num_tokens: int, mhc: int, hidden: int) -> None: | ||
| 44 | + if num_tokens < 0: | ||
| 45 | + raise ValueError(f'num_tokens must be non-negative, got {num_tokens}.') | ||
| 46 | + if mhc <= 0: | ||
| 47 | + raise ValueError(f'mhc must be positive, got {mhc}.') | ||
| 48 | + if hidden <= 0 or hidden % 64: | ||
| 49 | + raise ValueError(f'hidden must be a positive multiple of 64, got {hidden}.') | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def mhc_pre_apply_mix_forward( | ||
| 53 | + x: torch.Tensor, | ||
| 54 | + mix: torch.Tensor, | ||
| 55 | + o: torch.Tensor, | ||
| 56 | +) -> None: | ||
| 57 | + """Apply token-local MHC mixing into a preallocated BF16 output. | ||
| 58 | + | ||
| 59 | + ``x`` has shape ``(num_tokens, mhc, hidden)``, ``mix`` has shape | ||
| 60 | + ``(num_tokens, mhc)``, and ``o`` has shape | ||
| 61 | + ``(num_tokens, hidden)``. | ||
| 62 | + """ | ||
| 63 | + | ||
| 64 | + if not all(isinstance(tensor, torch.Tensor) for tensor in (x, mix, o)): | ||
| 65 | + raise TypeError('x, mix and o must be torch.Tensor instances.') | ||
| 66 | + require_ascend() | ||
| 67 | + _check_tensor('x', x, torch.bfloat16, 3) | ||
| 68 | + _check_tensor('mix', mix, torch.float32, 2, device=x.device) | ||
| 69 | + _check_tensor('o', o, torch.bfloat16, 2, device=x.device) | ||
| 70 | + | ||
| 71 | + num_tokens, mhc, hidden = x.shape | ||
| 72 | + _check_shape(num_tokens, mhc, hidden) | ||
| 73 | + if mix.shape != (num_tokens, mhc): | ||
| 74 | + raise ValueError(f'mix must have shape {(num_tokens, mhc)}, got {tuple(mix.shape)}.') | ||
| 75 | + if o.shape != (num_tokens, hidden): | ||
| 76 | + raise ValueError(f'o must have shape {(num_tokens, hidden)}, got {tuple(o.shape)}.') | ||
| 77 | + if _shares_storage(o, x) or _shares_storage(o, mix): | ||
| 78 | + raise ValueError('o must not share storage with x or mix.') | ||
| 79 | + if num_tokens == 0: | ||
| 80 | + return | ||
| 81 | + | ||
| 82 | + hidden_block = hidden if hidden <= 6144 else min(ceil_div(hidden, 2), 6144) | ||
| 83 | + hidden_block = align_up(hidden_block, 64) | ||
| 84 | + mhc_pre_apply_mix_fwd_asc( | ||
| 85 | + x, | ||
| 86 | + mix, | ||
| 87 | + o, | ||
| 88 | + h_blk=hidden_block, | ||
| 89 | + num_vec_cores=get_num_vector_cores(), | ||
| 90 | + ) | ||
| 91 | + | ||
| 92 | + | ||
| 93 | +def mhc_pre_apply_mix_backward( | ||
| 94 | + o_grad: torch.Tensor, | ||
| 95 | + x: torch.Tensor, | ||
| 96 | + mix: torch.Tensor, | ||
| 97 | + x_grad: torch.Tensor, | ||
| 98 | +) -> torch.Tensor: | ||
| 99 | + """Accumulate ``x`` gradients and return the FP32 mix gradient.""" | ||
| 100 | + | ||
| 101 | + tensors = (o_grad, x, mix, x_grad) | ||
| 102 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 103 | + raise TypeError('o_grad, x, mix and x_grad must be torch.Tensor instances.') | ||
| 104 | + require_ascend() | ||
| 105 | + _check_tensor('x', x, torch.bfloat16, 3) | ||
| 106 | + _check_tensor('o_grad', o_grad, torch.bfloat16, 2, device=x.device) | ||
| 107 | + _check_tensor('mix', mix, torch.float32, 2, device=x.device) | ||
| 108 | + _check_tensor('x_grad', x_grad, torch.bfloat16, 3, device=x.device) | ||
| 109 | + | ||
| 110 | + num_tokens, mhc, hidden = x.shape | ||
| 111 | + _check_shape(num_tokens, mhc, hidden) | ||
| 112 | + if o_grad.shape != (num_tokens, hidden): | ||
| 113 | + raise ValueError(f'o_grad must have shape {(num_tokens, hidden)}, got {tuple(o_grad.shape)}.') | ||
| 114 | + if mix.shape != (num_tokens, mhc): | ||
| 115 | + raise ValueError(f'mix must have shape {(num_tokens, mhc)}, got {tuple(mix.shape)}.') | ||
| 116 | + if x_grad.shape != x.shape: | ||
| 117 | + raise ValueError(f'x_grad must have shape {tuple(x.shape)}, got {tuple(x_grad.shape)}.') | ||
| 118 | + for name, tensor in (('o_grad', o_grad), ('x', x), ('mix', mix)): | ||
| 119 | + if _shares_storage(x_grad, tensor): | ||
| 120 | + raise ValueError(f'x_grad must not share storage with {name}.') | ||
| 121 | + if num_tokens == 0: | ||
| 122 | + return torch.empty((0, mhc), dtype=torch.float32, device=x.device) | ||
| 123 | + | ||
| 124 | + if hidden <= 4096: | ||
| 125 | + hidden_block = hidden | ||
| 126 | + num_stages = 3 | ||
| 127 | + else: | ||
| 128 | + hidden_block = align_up(min(ceil_div(hidden, 4), 3072), 64) | ||
| 129 | + num_stages = 4 | ||
| 130 | + return mhc_pre_apply_mix_bwd_asc( | ||
| 131 | + o_grad, | ||
| 132 | + x, | ||
| 133 | + mix, | ||
| 134 | + x_grad, | ||
| 135 | + h_blk=hidden_block, | ||
| 136 | + num_stages=num_stages, | ||
| 137 | + num_vec_cores=get_num_vector_cores(), | ||
| 138 | + ) | ||
| @@ -0,0 +1,128 @@ | |||
| 1 | +import tilelang | ||
| 2 | +from tilelang.ascend import language as T | ||
| 3 | +from tilelang.ascend.language import simd as S | ||
| 4 | + | ||
| 5 | +# Each bf16 chunk is unpacked into a 64-lane fp32 vector register. | ||
| 6 | +_VEC = 64 | ||
| 7 | + | ||
| 8 | + | ||
| 9 | +@tilelang.jit() | ||
| 10 | +def mhc_pre_apply_mix_fwd_asc(x, mix, o, h_blk, num_vec_cores: int): | ||
| 11 | + """Ascend forward: ``o[n, h] = sum_mhc mix[n, mhc] * x[n, mhc, h]``.""" | ||
| 12 | + num_tokens = T.dynamic('num_tokens') | ||
| 13 | + mhc = T.const('mhc') | ||
| 14 | + hidden = T.const('hidden') | ||
| 15 | + x: T.Tensor[(num_tokens, mhc, hidden), T.bfloat16] | ||
| 16 | + mix: T.Tensor[(num_tokens, mhc), T.float32] | ||
| 17 | + o: T.Tensor[(num_tokens, hidden), T.bfloat16] | ||
| 18 | + | ||
| 19 | + num_hidden_tiles = T.ceildiv(hidden, h_blk) | ||
| 20 | + num_chunks = h_blk // _VEC | ||
| 21 | + num_stages = 3 | ||
| 22 | + | ||
| 23 | + with T.Kernel(num_vec_cores) as core_id: | ||
| 24 | + x_ub = T.alloc_shared((mhc, h_blk), T.bfloat16) | ||
| 25 | + mix_ub = T.alloc_shared((T.ceildiv(mhc, 8) * 8,), T.float32) | ||
| 26 | + o_ub = T.alloc_shared((h_blk,), T.bfloat16) | ||
| 27 | + T.annotate_buffer_versions({x_ub: num_stages, mix_ub: num_stages, o_ub: num_stages}) | ||
| 28 | + | ||
| 29 | + for pid_n, pid_h in T.Persistent( | ||
| 30 | + [num_tokens, num_hidden_tiles], num_vec_cores, core_id, group_size=1, num_stages=num_stages | ||
| 31 | + ): | ||
| 32 | + T.copy(mix[pid_n, 0], mix_ub[0:mhc]) | ||
| 33 | + T.copy(x[pid_n, 0, pid_h * h_blk], x_ub) | ||
| 34 | + | ||
| 35 | + with T.SimdVF(): | ||
| 36 | + # Preload the mix scalars as broadcast registers once per token-tile. | ||
| 37 | + mix_reg = S.alloc_local((mhc,), T.float32) | ||
| 38 | + for m in T.unroll(mhc, explicit=True): | ||
| 39 | + mix_reg[m] = S.vld(mix_ub[m], dist='BRC_B32') | ||
| 40 | + xs = S.alloc_local((mhc,), T.float32) | ||
| 41 | + acc = S.alloc_var(T.float32) | ||
| 42 | + for c in T.serial(num_chunks): | ||
| 43 | + col = c * _VEC | ||
| 44 | + for m in T.unroll(mhc, explicit=True): | ||
| 45 | + xs[m] = S.vcvt(S.vld(x_ub[m, col], dist='UNPK_B16'), T.float32, part=0) | ||
| 46 | + acc = S.vmul(xs[0], mix_reg[0]) | ||
| 47 | + for m in T.unroll(mhc - 1, explicit=True): | ||
| 48 | + S.vmula(acc, xs[m + 1], mix_reg[m + 1]) | ||
| 49 | + S.vsts(o_ub[col], S.vcvt(acc, T.bfloat16), dist='PK_B32') | ||
| 50 | + | ||
| 51 | + T.copy(o_ub, o[pid_n, pid_h * h_blk]) | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +@tilelang.jit() | ||
| 55 | +def mhc_pre_apply_mix_bwd_asc(o_grad, x, mix, x_grad, h_blk, num_stages, num_vec_cores: int): | ||
| 56 | + """Ascend backward. | ||
| 57 | + | ||
| 58 | + ``x_grad[n, m, h] += mix[n, m] * o_grad[n, h]`` (accumulated onto existing values) | ||
| 59 | + ``mix_grad[n, m] = sum_h o_grad[n, h] * x[n, m, h]`` (reduction over h) | ||
| 60 | + """ | ||
| 61 | + num_tokens = T.dynamic('num_tokens') | ||
| 62 | + mhc = T.const('mhc') | ||
| 63 | + hidden = T.const('hidden') | ||
| 64 | + o_grad: T.Tensor[(num_tokens, hidden), T.bfloat16] | ||
| 65 | + x: T.Tensor[(num_tokens, mhc, hidden), T.bfloat16] | ||
| 66 | + mix: T.Tensor[(num_tokens, mhc), T.float32] | ||
| 67 | + x_grad: T.Tensor[(num_tokens, mhc, hidden), T.bfloat16] | ||
| 68 | + mix_grad = T.empty((num_tokens, mhc), T.float32) | ||
| 69 | + | ||
| 70 | + num_hidden_tiles = T.ceildiv(hidden, h_blk) | ||
| 71 | + num_chunks = h_blk // _VEC | ||
| 72 | + | ||
| 73 | + with T.Kernel(num_vec_cores) as core_id: | ||
| 74 | + o_grad_ub = T.alloc_shared((h_blk,), T.bfloat16) | ||
| 75 | + x_ub = T.alloc_shared((mhc, h_blk), T.bfloat16) | ||
| 76 | + x_grad_ub = T.alloc_shared((mhc, h_blk), T.bfloat16) | ||
| 77 | + mix_ub = T.alloc_shared((T.ceildiv(mhc, 8) * 8,), T.float32) | ||
| 78 | + mix_grad_acc = T.alloc_shared((mhc, _VEC), T.float32) | ||
| 79 | + mix_grad_ub = T.alloc_shared((T.ceildiv(mhc, 8) * 8,), T.float32) | ||
| 80 | + T.annotate_buffer_versions( | ||
| 81 | + { | ||
| 82 | + o_grad_ub: num_stages, | ||
| 83 | + x_ub: num_stages, | ||
| 84 | + x_grad_ub: num_stages, | ||
| 85 | + mix_ub: num_stages, | ||
| 86 | + mix_grad_ub: num_stages, | ||
| 87 | + } | ||
| 88 | + ) | ||
| 89 | + | ||
| 90 | + for pid_n in T.Persistent([num_tokens], num_vec_cores, core_id, group_size=1, num_stages=num_stages): | ||
| 91 | + T.copy(mix[pid_n, 0], mix_ub[0:mhc]) | ||
| 92 | + | ||
| 93 | + with T.SimdVF(): | ||
| 94 | + zero = S.vdup(0.0, T.float32) | ||
| 95 | + for m in T.unroll(mhc, explicit=True): | ||
| 96 | + S.vsts(mix_grad_acc[m, 0], zero) | ||
| 97 | + | ||
| 98 | + for pid_h in T.serial(num_hidden_tiles): | ||
| 99 | + T.copy(o_grad[pid_n, pid_h * h_blk], o_grad_ub) | ||
| 100 | + T.copy(x[pid_n, 0, pid_h * h_blk], x_ub) | ||
| 101 | + T.copy(x_grad[pid_n, 0, pid_h * h_blk], x_grad_ub) | ||
| 102 | + | ||
| 103 | + with T.SimdVF(): | ||
| 104 | + mix_reg = S.alloc_local((mhc,), T.float32) | ||
| 105 | + for m in T.unroll(mhc, explicit=True): | ||
| 106 | + mix_reg[m] = S.vld(mix_ub[m], dist='BRC_B32') | ||
| 107 | + mix_grad_reg = S.alloc_var(T.float32) | ||
| 108 | + x_grad_f32 = S.alloc_var(T.float32) | ||
| 109 | + for m in T.unroll(mhc, explicit=True): | ||
| 110 | + mix_grad_reg = S.vld(mix_grad_acc[m, 0]) | ||
| 111 | + for c in T.serial(num_chunks): | ||
| 112 | + col = c * _VEC | ||
| 113 | + o_grad_f32 = S.vcvt(S.vld(o_grad_ub[col], dist='UNPK_B16'), T.float32, part=0) | ||
| 114 | + x_f32 = S.vcvt(S.vld(x_ub[m, col], dist='UNPK_B16'), T.float32, part=0) | ||
| 115 | + x_grad_f32 = S.vcvt(S.vld(x_grad_ub[m, col], dist='UNPK_B16'), T.float32, part=0) | ||
| 116 | + S.vmula(x_grad_f32, o_grad_f32, mix_reg[m]) | ||
| 117 | + S.vsts(x_grad_ub[m, col], S.vcvt(x_grad_f32, T.bfloat16), dist='PK_B32') | ||
| 118 | + S.vmula(mix_grad_reg, o_grad_f32, x_f32) | ||
| 119 | + S.vsts(mix_grad_acc[m, 0], mix_grad_reg) | ||
| 120 | + | ||
| 121 | + T.copy(x_grad_ub, x_grad[pid_n, 0, pid_h * h_blk]) | ||
| 122 | + | ||
| 123 | + with T.SimdVF(): | ||
| 124 | + for m in T.unroll(mhc, explicit=True): | ||
| 125 | + S.vsts(mix_grad_ub[m], S.vcadd(S.vld(mix_grad_acc[m, 0])), dist='ONEPT_B32') | ||
| 126 | + T.copy(mix_grad_ub[0:mhc], mix_grad[pid_n, 0]) | ||
| 127 | + | ||
| 128 | + return mix_grad | ||
| @@ -0,0 +1,291 @@ | |||
| 1 | +"""Ascend fused MHC preprocessing operator.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +import math | ||
| 6 | +from numbers import Integral, Real | ||
| 7 | + | ||
| 8 | +import torch | ||
| 9 | + | ||
| 10 | +from cann_ops_tilelang.config import get_num_vector_cores, require_ascend | ||
| 11 | + | ||
| 12 | +from .pre_big_fuse_kernel import ( | ||
| 13 | + _mhc_reduce_partials_and_rmsnorm_forward_kernel, | ||
| 14 | + mhc_pre_big_fuse_fwd_asc, | ||
| 15 | +) | ||
| 16 | + | ||
| 17 | +_ASCEND_MAX_H_BLK = 6144 | ||
| 18 | +_ASCEND_STATIC_TOKEN_COUNTS = (512, 1024, 2048, 8192) | ||
| 19 | + | ||
| 20 | + | ||
| 21 | +def _ascend_pre_big_fuse_config(num_tokens: int, hidden_size: int) -> dict[str, int | bool]: | ||
| 22 | + """Mirror the current TileKernels main launch policy for Ascend.""" | ||
| 23 | + | ||
| 24 | + use_batched_residual = num_tokens == 8192 and hidden_size == 7168 | ||
| 25 | + if use_batched_residual: | ||
| 26 | + hidden_block = 256 | ||
| 27 | + residual_token_block = 16 | ||
| 28 | + residual_num_stages = 4 | ||
| 29 | + else: | ||
| 30 | + hidden_block = min(hidden_size, _ASCEND_MAX_H_BLK) | ||
| 31 | + hidden_block -= hidden_block % 64 | ||
| 32 | + while hidden_block > 0 and hidden_size % hidden_block: | ||
| 33 | + hidden_block -= 64 | ||
| 34 | + if hidden_block == 0: | ||
| 35 | + raise ValueError(f'No 64-aligned hidden block divides hidden={hidden_size}.') | ||
| 36 | + residual_token_block = 1 | ||
| 37 | + residual_num_stages = 3 | ||
| 38 | + | ||
| 39 | + return { | ||
| 40 | + 'h_blk': hidden_block, | ||
| 41 | + 'residual_token_block': residual_token_block, | ||
| 42 | + 'residual_num_stages': residual_num_stages, | ||
| 43 | + 'static_num_tokens': num_tokens if num_tokens in _ASCEND_STATIC_TOKEN_COUNTS else 0, | ||
| 44 | + 'use_fma_affine': num_tokens == 8192, | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +def _check_tensor( | ||
| 49 | + name: str, | ||
| 50 | + tensor: torch.Tensor, | ||
| 51 | + dtype: torch.dtype, | ||
| 52 | + ndim: int, | ||
| 53 | + *, | ||
| 54 | + device: torch.device | None = None, | ||
| 55 | +) -> None: | ||
| 56 | + if not isinstance(tensor, torch.Tensor): | ||
| 57 | + raise TypeError(f'{name} must be a torch.Tensor.') | ||
| 58 | + if tensor.device.type != 'npu': | ||
| 59 | + raise ValueError(f'{name} must be an NPU tensor, got {tensor.device}.') | ||
| 60 | + if device is not None and tensor.device != device: | ||
| 61 | + raise ValueError(f'{name} must be on {device}, got {tensor.device}.') | ||
| 62 | + if tensor.dtype != dtype: | ||
| 63 | + raise TypeError(f'{name} must use {dtype}, got {tensor.dtype}.') | ||
| 64 | + if tensor.ndim != ndim: | ||
| 65 | + raise ValueError(f'{name} must be {ndim}-dimensional, got shape {tuple(tensor.shape)}.') | ||
| 66 | + if not tensor.is_contiguous(): | ||
| 67 | + raise ValueError(f'{name} must be contiguous.') | ||
| 68 | + | ||
| 69 | + | ||
| 70 | +def _shares_storage(left: torch.Tensor, right: torch.Tensor) -> bool: | ||
| 71 | + return ( | ||
| 72 | + left.numel() > 0 | ||
| 73 | + and right.numel() > 0 | ||
| 74 | + and left.untyped_storage().data_ptr() == right.untyped_storage().data_ptr() | ||
| 75 | + ) | ||
| 76 | + | ||
| 77 | + | ||
| 78 | +def _check_real(name: str, value: float, *, non_negative: bool = False) -> float: | ||
| 79 | + if isinstance(value, bool) or not isinstance(value, Real): | ||
| 80 | + raise TypeError(f'{name} must be a real number.') | ||
| 81 | + converted = float(value) | ||
| 82 | + if not math.isfinite(converted): | ||
| 83 | + raise ValueError(f'{name} must be finite.') | ||
| 84 | + if non_negative and converted < 0: | ||
| 85 | + raise ValueError(f'{name} must be non-negative.') | ||
| 86 | + return converted | ||
| 87 | + | ||
| 88 | + | ||
| 89 | +def _check_positive_int(name: str, value: int) -> int: | ||
| 90 | + if isinstance(value, bool) or not isinstance(value, Integral): | ||
| 91 | + raise TypeError(f'{name} must be an integer.') | ||
| 92 | + converted = int(value) | ||
| 93 | + if converted <= 0: | ||
| 94 | + raise ValueError(f'{name} must be positive, got {converted}.') | ||
| 95 | + return converted | ||
| 96 | + | ||
| 97 | + | ||
| 98 | +def _check_output_aliases(inputs: dict[str, torch.Tensor], outputs: dict[str, torch.Tensor]) -> None: | ||
| 99 | + output_items = list(outputs.items()) | ||
| 100 | + for output_name, output in output_items: | ||
| 101 | + for input_name, input_tensor in inputs.items(): | ||
| 102 | + if _shares_storage(output, input_tensor): | ||
| 103 | + raise ValueError(f'{output_name} must not share storage with {input_name}.') | ||
| 104 | + for index, (left_name, left) in enumerate(output_items): | ||
| 105 | + for right_name, right in output_items[index + 1 :]: | ||
| 106 | + if _shares_storage(left, right): | ||
| 107 | + raise ValueError(f'{left_name} and {right_name} must not share storage.') | ||
| 108 | + | ||
| 109 | + | ||
| 110 | +def _mhc_reduce_partials_and_rmsnorm_forward( | ||
| 111 | + out_mul_splitted: torch.Tensor, | ||
| 112 | + sqrsum_splitted: torch.Tensor, | ||
| 113 | + out_mul: torch.Tensor, | ||
| 114 | + sqrsum: torch.Tensor, | ||
| 115 | + out: torch.Tensor, | ||
| 116 | + rms_group_size: int, | ||
| 117 | + rms_eps: float, | ||
| 118 | + n_splits: int, | ||
| 119 | +) -> None: | ||
| 120 | + """Run the private reduce/RMSNorm reference stage used by fused tests.""" | ||
| 121 | + | ||
| 122 | + tensors = (out_mul_splitted, sqrsum_splitted, out_mul, sqrsum, out) | ||
| 123 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 124 | + raise TypeError('all reduce/RMSNorm inputs and outputs must be torch.Tensor instances.') | ||
| 125 | + require_ascend() | ||
| 126 | + _check_tensor('out_mul_splitted', out_mul_splitted, torch.float32, 4) | ||
| 127 | + device = out_mul_splitted.device | ||
| 128 | + _check_tensor('sqrsum_splitted', sqrsum_splitted, torch.float32, 3, device=device) | ||
| 129 | + _check_tensor('out_mul', out_mul, torch.float32, 3, device=device) | ||
| 130 | + _check_tensor('sqrsum', sqrsum, torch.float32, 2, device=device) | ||
| 131 | + _check_tensor('out', out, torch.float32, 2, device=device) | ||
| 132 | + | ||
| 133 | + n_splits = _check_positive_int('n_splits', n_splits) | ||
| 134 | + rms_group_size = _check_positive_int('rms_group_size', rms_group_size) | ||
| 135 | + rms_eps = _check_real('rms_eps', rms_eps, non_negative=True) | ||
| 136 | + if out_mul_splitted.shape[0] != n_splits: | ||
| 137 | + raise ValueError( | ||
| 138 | + f'out_mul_splitted first dimension must equal n_splits={n_splits}, got {out_mul_splitted.shape[0]}.' | ||
| 139 | + ) | ||
| 140 | + _, num_tokens, num_rms_groups, mhc_mult3 = out_mul_splitted.shape | ||
| 141 | + if num_rms_groups != 1 or mhc_mult3 != 24: | ||
| 142 | + raise ValueError( | ||
| 143 | + f'out_mul_splitted must have shape (n_splits, num_tokens, 1, 24), got {tuple(out_mul_splitted.shape)}.' | ||
| 144 | + ) | ||
| 145 | + expected_shapes = { | ||
| 146 | + 'sqrsum_splitted': (n_splits, num_tokens, 1), | ||
| 147 | + 'out_mul': (num_tokens, 1, 24), | ||
| 148 | + 'sqrsum': (num_tokens, 1), | ||
| 149 | + 'out': (num_tokens, 24), | ||
| 150 | + } | ||
| 151 | + actual_tensors = { | ||
| 152 | + 'sqrsum_splitted': sqrsum_splitted, | ||
| 153 | + 'out_mul': out_mul, | ||
| 154 | + 'sqrsum': sqrsum, | ||
| 155 | + 'out': out, | ||
| 156 | + } | ||
| 157 | + for name, expected_shape in expected_shapes.items(): | ||
| 158 | + if actual_tensors[name].shape != expected_shape: | ||
| 159 | + raise ValueError(f'{name} must have shape {expected_shape}, got {tuple(actual_tensors[name].shape)}.') | ||
| 160 | + _check_output_aliases( | ||
| 161 | + { | ||
| 162 | + 'out_mul_splitted': out_mul_splitted, | ||
| 163 | + 'sqrsum_splitted': sqrsum_splitted, | ||
| 164 | + }, | ||
| 165 | + {'out_mul': out_mul, 'sqrsum': sqrsum, 'out': out}, | ||
| 166 | + ) | ||
| 167 | + if num_tokens == 0: | ||
| 168 | + return | ||
| 169 | + | ||
| 170 | + _mhc_reduce_partials_and_rmsnorm_forward_kernel( | ||
| 171 | + out_mul_splitted, | ||
| 172 | + sqrsum_splitted, | ||
| 173 | + out_mul, | ||
| 174 | + sqrsum, | ||
| 175 | + out, | ||
| 176 | + rms_group_size, | ||
| 177 | + rms_eps, | ||
| 178 | + n_splits, | ||
| 179 | + get_num_vector_cores(), | ||
| 180 | + ) | ||
| 181 | + | ||
| 182 | + | ||
| 183 | +def mhc_pre_big_fuse( | ||
| 184 | + gemm_out_mul: torch.Tensor, | ||
| 185 | + gemm_out_sqrsum: torch.Tensor, | ||
| 186 | + mhc_scale: torch.Tensor, | ||
| 187 | + mhc_base: torch.Tensor, | ||
| 188 | + residual: torch.Tensor, | ||
| 189 | + post_mix: torch.Tensor, | ||
| 190 | + comb_mix: torch.Tensor, | ||
| 191 | + layer_input: torch.Tensor, | ||
| 192 | + rms_eps: float, | ||
| 193 | + mhc_pre_eps: float, | ||
| 194 | + mhc_sinkhorn_eps: float, | ||
| 195 | + mhc_post_mult_value: float, | ||
| 196 | + sinkhorn_repeat: int, | ||
| 197 | + n_splits: int = 16, | ||
| 198 | +) -> None: | ||
| 199 | + """Run fused MHC preprocessing into caller-provided output buffers.""" | ||
| 200 | + | ||
| 201 | + tensors = ( | ||
| 202 | + gemm_out_mul, | ||
| 203 | + gemm_out_sqrsum, | ||
| 204 | + mhc_scale, | ||
| 205 | + mhc_base, | ||
| 206 | + residual, | ||
| 207 | + post_mix, | ||
| 208 | + comb_mix, | ||
| 209 | + layer_input, | ||
| 210 | + ) | ||
| 211 | + if not all(isinstance(tensor, torch.Tensor) for tensor in tensors): | ||
| 212 | + raise TypeError('all MHC pre-big-fuse inputs and outputs must be torch.Tensor instances.') | ||
| 213 | + require_ascend() | ||
| 214 | + _check_tensor('gemm_out_mul', gemm_out_mul, torch.float32, 3) | ||
| 215 | + device = gemm_out_mul.device | ||
| 216 | + _check_tensor('gemm_out_sqrsum', gemm_out_sqrsum, torch.float32, 2, device=device) | ||
| 217 | + _check_tensor('mhc_scale', mhc_scale, torch.float32, 1, device=device) | ||
| 218 | + _check_tensor('mhc_base', mhc_base, torch.float32, 1, device=device) | ||
| 219 | + _check_tensor('residual', residual, torch.bfloat16, 3, device=device) | ||
| 220 | + _check_tensor('post_mix', post_mix, torch.float32, 2, device=device) | ||
| 221 | + _check_tensor('comb_mix', comb_mix, torch.float32, 2, device=device) | ||
| 222 | + _check_tensor('layer_input', layer_input, torch.bfloat16, 2, device=device) | ||
| 223 | + | ||
| 224 | + rms_eps = _check_real('rms_eps', rms_eps, non_negative=True) | ||
| 225 | + mhc_pre_eps = _check_real('mhc_pre_eps', mhc_pre_eps, non_negative=True) | ||
| 226 | + mhc_sinkhorn_eps = _check_real('mhc_sinkhorn_eps', mhc_sinkhorn_eps, non_negative=True) | ||
| 227 | + mhc_post_mult_value = _check_real('mhc_post_mult_value', mhc_post_mult_value) | ||
| 228 | + sinkhorn_repeat = _check_positive_int('sinkhorn_repeat', sinkhorn_repeat) | ||
| 229 | + _check_positive_int('n_splits', n_splits) | ||
| 230 | + | ||
| 231 | + num_tokens, mhc, hidden = residual.shape | ||
| 232 | + if mhc != 4: | ||
| 233 | + raise ValueError(f'residual must use mHC4, got mhc={mhc}.') | ||
| 234 | + if hidden <= 0 or hidden % 64: | ||
| 235 | + raise ValueError(f'hidden must be a positive multiple of 64, got {hidden}.') | ||
| 236 | + expected_shapes = { | ||
| 237 | + 'gemm_out_mul': (1, num_tokens, 24), | ||
| 238 | + 'gemm_out_sqrsum': (1, num_tokens), | ||
| 239 | + 'mhc_scale': (3,), | ||
| 240 | + 'mhc_base': (24,), | ||
| 241 | + 'post_mix': (num_tokens, 4), | ||
| 242 | + 'comb_mix': (num_tokens, 16), | ||
| 243 | + 'layer_input': (num_tokens, hidden), | ||
| 244 | + } | ||
| 245 | + actual_tensors = { | ||
| 246 | + 'gemm_out_mul': gemm_out_mul, | ||
| 247 | + 'gemm_out_sqrsum': gemm_out_sqrsum, | ||
| 248 | + 'mhc_scale': mhc_scale, | ||
| 249 | + 'mhc_base': mhc_base, | ||
| 250 | + 'post_mix': post_mix, | ||
| 251 | + 'comb_mix': comb_mix, | ||
| 252 | + 'layer_input': layer_input, | ||
| 253 | + } | ||
| 254 | + for name, expected_shape in expected_shapes.items(): | ||
| 255 | + if actual_tensors[name].shape != expected_shape: | ||
| 256 | + raise ValueError(f'{name} must have shape {expected_shape}, got {tuple(actual_tensors[name].shape)}.') | ||
| 257 | + _check_output_aliases( | ||
| 258 | + { | ||
| 259 | + 'gemm_out_mul': gemm_out_mul, | ||
| 260 | + 'gemm_out_sqrsum': gemm_out_sqrsum, | ||
| 261 | + 'mhc_scale': mhc_scale, | ||
| 262 | + 'mhc_base': mhc_base, | ||
| 263 | + 'residual': residual, | ||
| 264 | + }, | ||
| 265 | + {'post_mix': post_mix, 'comb_mix': comb_mix, 'layer_input': layer_input}, | ||
| 266 | + ) | ||
| 267 | + if num_tokens == 0: | ||
| 268 | + return | ||
| 269 | + | ||
| 270 | + config = _ascend_pre_big_fuse_config(num_tokens, hidden) | ||
| 271 | + mhc_pre_big_fuse_fwd_asc( | ||
| 272 | + gemm_out_mul, | ||
| 273 | + gemm_out_sqrsum, | ||
| 274 | + mhc_scale, | ||
| 275 | + mhc_base, | ||
| 276 | + residual, | ||
| 277 | + post_mix, | ||
| 278 | + comb_mix, | ||
| 279 | + layer_input, | ||
| 280 | + rms_eps, | ||
| 281 | + mhc_pre_eps, | ||
| 282 | + mhc_sinkhorn_eps, | ||
| 283 | + mhc_post_mult_value, | ||
| 284 | + sinkhorn_repeat, | ||
| 285 | + h_blk=config['h_blk'], | ||
| 286 | + residual_token_block=config['residual_token_block'], | ||
| 287 | + residual_num_stages=config['residual_num_stages'], | ||
| 288 | + use_fma_affine=config['use_fma_affine'], | ||
| 289 | + static_num_tokens=config['static_num_tokens'], | ||
| 290 | + n_splits=1, | ||
| 291 | + ) | ||