59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
497 lines
15 KiB
Python
497 lines
15 KiB
Python
# Copyright (c) 2026 LightSeek Foundation
|
|
#
|
|
# Portions copyright the vLLM project contributors under Apache-2.0.
|
|
|
|
from __future__ import annotations
|
|
|
|
from functools import cache
|
|
|
|
import torch
|
|
import triton
|
|
import triton.language as tl
|
|
|
|
from tokenspeed.runtime.utils import ceil_div
|
|
|
|
try:
|
|
from tokenspeed_kernel.thirdparty import deep_gemm
|
|
except Exception:
|
|
deep_gemm = None # type: ignore[assignment]
|
|
|
|
|
|
@cache
|
|
def _compute_num_split(block_k: int, k: int | None, grid_size: int) -> int:
|
|
device_props = torch.cuda.get_device_properties(0)
|
|
split_k = device_props.multi_processor_count // grid_size
|
|
if k is not None:
|
|
num_block_k = ceil_div(k, block_k)
|
|
split_k = min(split_k, num_block_k // 4)
|
|
return max(split_k, 1)
|
|
|
|
|
|
@triton.jit
|
|
def _load_reduced_mix(
|
|
gemm_out_mul,
|
|
token_id,
|
|
mix_id: tl.constexpr,
|
|
num_tokens,
|
|
hc_mult3: tl.constexpr,
|
|
n_splits: tl.constexpr,
|
|
):
|
|
value = tl.full((), 0.0, tl.float32)
|
|
for split_id in tl.static_range(0, n_splits):
|
|
offset = split_id * num_tokens * hc_mult3 + token_id * hc_mult3 + mix_id
|
|
value += tl.load(gemm_out_mul + offset)
|
|
return value
|
|
|
|
|
|
@triton.jit
|
|
def _mhc_pre_mix_triton_kernel(
|
|
gemm_out_mul,
|
|
gemm_out_sqrsum,
|
|
hc_scale,
|
|
hc_base,
|
|
pre_mix,
|
|
post_mix,
|
|
comb_mix,
|
|
hidden_size: tl.constexpr,
|
|
rms_eps: tl.constexpr,
|
|
hc_eps: tl.constexpr,
|
|
sinkhorn_iters: tl.constexpr,
|
|
n_splits: tl.constexpr,
|
|
hc_mult: tl.constexpr,
|
|
hc_mult2: tl.constexpr,
|
|
hc_mult3: tl.constexpr,
|
|
block_comb: tl.constexpr,
|
|
num_tokens,
|
|
):
|
|
token_id = tl.program_id(0)
|
|
|
|
rms_sum = tl.full((), 0.0, tl.float32)
|
|
for split_id in tl.static_range(0, n_splits):
|
|
rms_sum += tl.load(gemm_out_sqrsum + split_id * num_tokens + token_id)
|
|
rms = tl.rsqrt(rms_sum / (hc_mult * hidden_size) + rms_eps)
|
|
|
|
pre_scale = tl.load(hc_scale)
|
|
for hc_id in tl.static_range(0, hc_mult):
|
|
mix = _load_reduced_mix(
|
|
gemm_out_mul,
|
|
token_id,
|
|
hc_id,
|
|
num_tokens,
|
|
hc_mult3,
|
|
n_splits,
|
|
)
|
|
pre = tl.sigmoid(mix * rms * pre_scale + tl.load(hc_base + hc_id)) + hc_eps
|
|
tl.store(pre_mix + token_id * hc_mult + hc_id, pre)
|
|
|
|
post_scale = tl.load(hc_scale + 1)
|
|
for hc_id in tl.static_range(0, hc_mult):
|
|
mix = _load_reduced_mix(
|
|
gemm_out_mul,
|
|
token_id,
|
|
hc_mult + hc_id,
|
|
num_tokens,
|
|
hc_mult3,
|
|
n_splits,
|
|
)
|
|
post = (
|
|
tl.sigmoid(mix * rms * post_scale + tl.load(hc_base + hc_mult + hc_id))
|
|
* 2.0
|
|
)
|
|
tl.store(post_mix + token_id * hc_mult + hc_id, post)
|
|
|
|
comb_offsets = tl.arange(0, block_comb)
|
|
comb_mask = comb_offsets < hc_mult2
|
|
comb_scale = tl.load(hc_scale + 2)
|
|
comb_mix_values = tl.zeros((block_comb,), tl.float32)
|
|
for split_id in tl.static_range(0, n_splits):
|
|
split_base = split_id * num_tokens * hc_mult3 + token_id * hc_mult3
|
|
comb_mix_values += tl.load(
|
|
gemm_out_mul + split_base + hc_mult * 2 + comb_offsets,
|
|
mask=comb_mask,
|
|
other=0.0,
|
|
)
|
|
comb_values = comb_mix_values * rms * comb_scale + tl.load(
|
|
hc_base + hc_mult * 2 + comb_offsets, mask=comb_mask, other=0.0
|
|
)
|
|
rows = comb_offsets // hc_mult
|
|
cols = comb_offsets - rows * hc_mult
|
|
active = comb_mask
|
|
|
|
for row_id in tl.static_range(0, hc_mult):
|
|
row_values = tl.where((rows == row_id) & active, comb_values, -float("inf"))
|
|
row_max = tl.max(row_values, axis=0)
|
|
comb_values = tl.where(
|
|
(rows == row_id) & active, tl.exp(comb_values - row_max), comb_values
|
|
)
|
|
for row_id in tl.static_range(0, hc_mult):
|
|
row_sum = tl.sum(tl.where((rows == row_id) & active, comb_values, 0.0), axis=0)
|
|
comb_values = tl.where(
|
|
(rows == row_id) & active, comb_values / row_sum + hc_eps, comb_values
|
|
)
|
|
for col_id in tl.static_range(0, hc_mult):
|
|
col_sum = tl.sum(tl.where((cols == col_id) & active, comb_values, 0.0), axis=0)
|
|
comb_values = tl.where(
|
|
(cols == col_id) & active,
|
|
comb_values / (col_sum + hc_eps),
|
|
comb_values,
|
|
)
|
|
|
|
for _ in tl.static_range(1, sinkhorn_iters):
|
|
for row_id in tl.static_range(0, hc_mult):
|
|
row_sum = tl.sum(
|
|
tl.where((rows == row_id) & active, comb_values, 0.0), axis=0
|
|
)
|
|
comb_values = tl.where(
|
|
(rows == row_id) & active,
|
|
comb_values / (row_sum + hc_eps),
|
|
comb_values,
|
|
)
|
|
for col_id in tl.static_range(0, hc_mult):
|
|
col_sum = tl.sum(
|
|
tl.where((cols == col_id) & active, comb_values, 0.0), axis=0
|
|
)
|
|
comb_values = tl.where(
|
|
(cols == col_id) & active,
|
|
comb_values / (col_sum + hc_eps),
|
|
comb_values,
|
|
)
|
|
|
|
tl.store(
|
|
comb_mix + token_id * hc_mult2 + comb_offsets,
|
|
comb_values,
|
|
mask=comb_mask,
|
|
)
|
|
|
|
|
|
@triton.jit
|
|
def _mhc_pre_layer_triton_kernel(
|
|
pre_mix,
|
|
residual,
|
|
layer_input,
|
|
hidden_size: tl.constexpr,
|
|
hc_mult: tl.constexpr,
|
|
block_h: tl.constexpr,
|
|
):
|
|
token_id = tl.program_id(0)
|
|
hidden_block_id = tl.program_id(1)
|
|
|
|
hidden_offsets = hidden_block_id * block_h + tl.arange(0, block_h)
|
|
hidden_mask = hidden_offsets < hidden_size
|
|
layer_acc = tl.zeros((block_h,), tl.float32)
|
|
for hc_id in tl.static_range(0, hc_mult):
|
|
pre = tl.load(pre_mix + token_id * hc_mult + hc_id).to(tl.float32)
|
|
residual_offsets = (
|
|
token_id * hc_mult * hidden_size + hc_id * hidden_size + hidden_offsets
|
|
)
|
|
residual_values = tl.load(
|
|
residual + residual_offsets, mask=hidden_mask, other=0.0
|
|
).to(tl.float32)
|
|
layer_acc += pre * residual_values
|
|
tl.store(
|
|
layer_input + token_id * hidden_size + hidden_offsets,
|
|
layer_acc,
|
|
mask=hidden_mask,
|
|
)
|
|
|
|
|
|
@triton.jit
|
|
def _mhc_post_triton_kernel(
|
|
comb,
|
|
residual,
|
|
post,
|
|
hidden_states,
|
|
out,
|
|
hidden_size: tl.constexpr,
|
|
hc_mult: tl.constexpr,
|
|
block_h: tl.constexpr,
|
|
):
|
|
token_id = tl.program_id(0)
|
|
hidden_block_id = tl.program_id(1)
|
|
hidden_offsets = hidden_block_id * block_h + tl.arange(0, block_h)
|
|
hidden_mask = hidden_offsets < hidden_size
|
|
hidden_values = tl.load(
|
|
hidden_states + token_id * hidden_size + hidden_offsets,
|
|
mask=hidden_mask,
|
|
other=0.0,
|
|
).to(tl.float32)
|
|
|
|
for out_hc in tl.static_range(0, hc_mult):
|
|
acc = tl.load(post + token_id * hc_mult + out_hc).to(tl.float32) * hidden_values
|
|
for in_hc in tl.static_range(0, hc_mult):
|
|
comb_value = tl.load(
|
|
comb + token_id * hc_mult * hc_mult + in_hc * hc_mult + out_hc
|
|
).to(tl.float32)
|
|
residual_values = tl.load(
|
|
residual
|
|
+ token_id * hc_mult * hidden_size
|
|
+ in_hc * hidden_size
|
|
+ hidden_offsets,
|
|
mask=hidden_mask,
|
|
other=0.0,
|
|
).to(tl.float32)
|
|
acc += comb_value * residual_values
|
|
tl.store(
|
|
out
|
|
+ token_id * hc_mult * hidden_size
|
|
+ out_hc * hidden_size
|
|
+ hidden_offsets,
|
|
acc,
|
|
mask=hidden_mask,
|
|
)
|
|
|
|
|
|
@triton.jit
|
|
def _mhc_post_hc4_triton_kernel(
|
|
comb,
|
|
residual,
|
|
post,
|
|
hidden_states,
|
|
out,
|
|
hidden_size: tl.constexpr,
|
|
block_h: tl.constexpr,
|
|
):
|
|
token_id = tl.program_id(0)
|
|
hidden_block_id = tl.program_id(1)
|
|
hidden_offsets = hidden_block_id * block_h + tl.arange(0, block_h)
|
|
hidden_mask = hidden_offsets < hidden_size
|
|
token_hidden_offset = token_id * hidden_size
|
|
token_residual_offset = token_id * 4 * hidden_size
|
|
|
|
hidden_values = tl.load(
|
|
hidden_states + token_hidden_offset + hidden_offsets,
|
|
mask=hidden_mask,
|
|
other=0.0,
|
|
).to(tl.float32)
|
|
|
|
post_base = token_id * 4
|
|
acc0 = tl.load(post + post_base + 0).to(tl.float32) * hidden_values
|
|
acc1 = tl.load(post + post_base + 1).to(tl.float32) * hidden_values
|
|
acc2 = tl.load(post + post_base + 2).to(tl.float32) * hidden_values
|
|
acc3 = tl.load(post + post_base + 3).to(tl.float32) * hidden_values
|
|
|
|
comb_base = token_id * 16
|
|
for in_hc in tl.static_range(0, 4):
|
|
residual_values = tl.load(
|
|
residual + token_residual_offset + in_hc * hidden_size + hidden_offsets,
|
|
mask=hidden_mask,
|
|
other=0.0,
|
|
).to(tl.float32)
|
|
comb_row = comb_base + in_hc * 4
|
|
acc0 += tl.load(comb + comb_row + 0).to(tl.float32) * residual_values
|
|
acc1 += tl.load(comb + comb_row + 1).to(tl.float32) * residual_values
|
|
acc2 += tl.load(comb + comb_row + 2).to(tl.float32) * residual_values
|
|
acc3 += tl.load(comb + comb_row + 3).to(tl.float32) * residual_values
|
|
|
|
tl.store(
|
|
out + token_residual_offset + hidden_offsets,
|
|
acc0,
|
|
mask=hidden_mask,
|
|
)
|
|
tl.store(
|
|
out + token_residual_offset + hidden_size + hidden_offsets,
|
|
acc1,
|
|
mask=hidden_mask,
|
|
)
|
|
tl.store(
|
|
out + token_residual_offset + hidden_size * 2 + hidden_offsets,
|
|
acc2,
|
|
mask=hidden_mask,
|
|
)
|
|
tl.store(
|
|
out + token_residual_offset + hidden_size * 3 + hidden_offsets,
|
|
acc3,
|
|
mask=hidden_mask,
|
|
)
|
|
|
|
|
|
def mhc_fused_hc(
|
|
x_prev: torch.Tensor,
|
|
residual_prev: torch.Tensor,
|
|
post_prev: torch.Tensor,
|
|
comb_prev: torch.Tensor,
|
|
fn: torch.Tensor,
|
|
hc_scale: torch.Tensor,
|
|
hc_base: torch.Tensor,
|
|
rms_eps: float,
|
|
hc_eps: float,
|
|
sinkhorn_iters: int,
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
"""Fused post_mapping(prev) + pre_mapping(curr).
|
|
|
|
Returns (residual_cur, layer_input, post_cur, comb_cur).
|
|
"""
|
|
residual_cur = mhc_post(x_prev, residual_prev, post_prev, comb_prev)
|
|
layer_input, post_cur, comb_cur = mhc_pre(
|
|
residual_cur,
|
|
fn,
|
|
hc_scale,
|
|
hc_base,
|
|
rms_eps,
|
|
hc_eps,
|
|
sinkhorn_iters,
|
|
)
|
|
return residual_cur, layer_input, post_cur, comb_cur
|
|
|
|
|
|
def mhc_pre(
|
|
residual: torch.Tensor,
|
|
fn: torch.Tensor,
|
|
hc_scale: torch.Tensor,
|
|
hc_base: torch.Tensor,
|
|
rms_eps: float,
|
|
hc_eps: float,
|
|
sinkhorn_iters: int,
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
if residual.dtype != torch.bfloat16 or fn.dtype != torch.float32:
|
|
raise RuntimeError("fast mHC requires bf16 residual and fp32 weights")
|
|
if not residual.is_cuda:
|
|
raise RuntimeError("fast mHC requires CUDA tensors")
|
|
|
|
if deep_gemm is None:
|
|
raise RuntimeError("deep_gemm.tf32_hc_prenorm_gemm is unavailable")
|
|
|
|
hc_mult = residual.shape[-2]
|
|
hidden_size = residual.shape[-1]
|
|
hc_mult2 = hc_mult * hc_mult
|
|
hc_mult3 = hc_mult * 2 + hc_mult2
|
|
hc_hidden_size = hc_mult * hidden_size
|
|
outer_shape = residual.shape[:-2]
|
|
residual_flat = residual.view(-1, hc_mult, hidden_size)
|
|
num_tokens = residual_flat.shape[0]
|
|
if num_tokens == 0:
|
|
return (
|
|
residual.new_empty(*outer_shape, hidden_size),
|
|
torch.empty(
|
|
*outer_shape,
|
|
hc_mult,
|
|
1,
|
|
dtype=torch.float32,
|
|
device=residual.device,
|
|
),
|
|
torch.empty(
|
|
*outer_shape,
|
|
hc_mult,
|
|
hc_mult,
|
|
dtype=torch.float32,
|
|
device=residual.device,
|
|
),
|
|
)
|
|
|
|
block_k = 64
|
|
block_m = 64
|
|
n_splits = _compute_num_split(
|
|
block_k, hc_hidden_size, ceil_div(num_tokens, block_m)
|
|
)
|
|
|
|
post_mix = torch.empty(
|
|
num_tokens, hc_mult, dtype=torch.float32, device=residual.device
|
|
)
|
|
pre_mix = torch.empty(
|
|
num_tokens, hc_mult, dtype=torch.float32, device=residual.device
|
|
)
|
|
comb_mix = torch.empty(
|
|
num_tokens, hc_mult2, dtype=torch.float32, device=residual.device
|
|
)
|
|
layer_input = torch.empty(
|
|
num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
|
|
)
|
|
gemm_out_mul = torch.empty(
|
|
n_splits, num_tokens, hc_mult3, dtype=torch.float32, device=residual.device
|
|
)
|
|
gemm_out_sqrsum = torch.empty(
|
|
n_splits, num_tokens, dtype=torch.float32, device=residual.device
|
|
)
|
|
|
|
deep_gemm.tf32_hc_prenorm_gemm(
|
|
residual_flat.view(num_tokens, hc_hidden_size),
|
|
fn,
|
|
gemm_out_mul,
|
|
gemm_out_sqrsum,
|
|
n_splits,
|
|
)
|
|
block_h = 1024
|
|
block_comb = triton.next_power_of_2(hc_mult2)
|
|
_mhc_pre_mix_triton_kernel[(num_tokens,)](
|
|
gemm_out_mul,
|
|
gemm_out_sqrsum,
|
|
hc_scale,
|
|
hc_base,
|
|
pre_mix,
|
|
post_mix,
|
|
comb_mix,
|
|
hidden_size=hidden_size,
|
|
rms_eps=rms_eps,
|
|
hc_eps=hc_eps,
|
|
sinkhorn_iters=sinkhorn_iters,
|
|
n_splits=n_splits,
|
|
hc_mult=hc_mult,
|
|
hc_mult2=hc_mult2,
|
|
hc_mult3=hc_mult3,
|
|
block_comb=block_comb,
|
|
num_tokens=num_tokens,
|
|
num_warps=1,
|
|
)
|
|
_mhc_pre_layer_triton_kernel[(num_tokens, triton.cdiv(hidden_size, block_h))](
|
|
pre_mix,
|
|
residual_flat,
|
|
layer_input,
|
|
hidden_size=hidden_size,
|
|
hc_mult=hc_mult,
|
|
block_h=block_h,
|
|
num_warps=4,
|
|
)
|
|
|
|
return (
|
|
layer_input.view(*outer_shape, hidden_size),
|
|
post_mix.view(*outer_shape, hc_mult, 1),
|
|
comb_mix.view(*outer_shape, hc_mult, hc_mult),
|
|
)
|
|
|
|
|
|
def mhc_post(
|
|
hidden_states: torch.Tensor,
|
|
residual: torch.Tensor,
|
|
post: torch.Tensor,
|
|
comb: torch.Tensor,
|
|
) -> torch.Tensor:
|
|
if not hidden_states.is_cuda:
|
|
raise RuntimeError("fast mHC requires CUDA tensors")
|
|
if residual.numel() == 0:
|
|
return torch.empty_like(residual)
|
|
out = torch.empty_like(residual)
|
|
hc_mult = residual.shape[-2]
|
|
hidden_size = residual.shape[-1]
|
|
residual_flat = residual.view(-1, hc_mult, hidden_size)
|
|
hidden_states_flat = hidden_states.view(-1, hidden_size)
|
|
post_flat = post.view(-1, hc_mult)
|
|
comb_flat = comb.view(-1, hc_mult, hc_mult)
|
|
num_tokens = residual_flat.shape[0]
|
|
if hc_mult == 4:
|
|
block_h = 256
|
|
_mhc_post_hc4_triton_kernel[(num_tokens, triton.cdiv(hidden_size, block_h))](
|
|
comb_flat,
|
|
residual_flat,
|
|
post_flat,
|
|
hidden_states_flat,
|
|
out,
|
|
hidden_size=hidden_size,
|
|
block_h=block_h,
|
|
num_warps=4,
|
|
)
|
|
return out
|
|
|
|
block_h = 1024
|
|
_mhc_post_triton_kernel[(num_tokens, triton.cdiv(hidden_size, block_h))](
|
|
comb_flat,
|
|
residual_flat,
|
|
post_flat,
|
|
hidden_states_flat,
|
|
out,
|
|
hidden_size=hidden_size,
|
|
hc_mult=hc_mult,
|
|
block_h=block_h,
|
|
num_warps=4,
|
|
)
|
|
return out
|