[Inkling] silu_and_mul: replace helion kernels with plain Triton (#33903)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam Shleifer
2026-08-08 15:00:23 +08:00
committed by GitHub
co-authored by Claude Fable 5
parent d238e36b24
commit afb4f37ca5
13 changed files with 367 additions and 699 deletions
-1
View File
@@ -33,7 +33,6 @@ dependencies = [
"flash-attn-4>=4.0.0b18",
"flashinfer_python[cu13]==0.6.15.post1", # keep it aligned with jit-cache version in Dockerfile
"gguf",
"helion==1.4",
"humming-kernels[cu13]==0.1.10",
"interegular",
"IPython",
-1
View File
@@ -32,7 +32,6 @@ runtime_base = [
"einops",
"fastapi",
"gguf",
"helion==1.4",
"interegular",
"IPython",
"llguidance>=1.7.6,<2.0.0",
+206 -122
View File
@@ -1,142 +1,195 @@
from functools import partial
import helion
import helion.language as hl
import torch
import triton
import triton.language as tl
from sglang.kernels.jit.utils import is_arch_support_pdl
from sglang.srt.layers.moe.moe_runner.triton_utils.helion_utils import (
get_model_depths,
helion_aot_autotune,
)
from sglang.srt.utils.common import is_sm121
DEFAULT_BLOCK_SIZE = 4096
BLOCK_SIZE_M = 128
def silu_and_mul_key(
gateup_output: torch.Tensor,
topk_weights: torch.Tensor | None,
out_dtype: object | None = None,
@triton.jit(do_not_specialize=["M"])
def silu_and_mul_interleaved_kernel(
gateup_out_ptr, # type: ignore # [M, 2N], rows are [g0, u0, g1, u1, ...]
topk_weights_ptr, # type: ignore # [M] (unused when HAS_TOPK_WEIGHTS=False)
down_inp_ptr, # type: ignore # [M, N]
M, # type: ignore
stride_gm, # type: ignore
N: tl.constexpr,
HAS_TOPK_WEIGHTS: tl.constexpr,
INT64_INDEX: tl.constexpr,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
# Keep this stable across inputs for Helion AOT autotune.
del out_dtype
return gateup_output.shape[1], (gateup_output.dtype,), (topk_weights is not None,)
# Flat grid, N-tile fastest: consecutive CTAs read consecutive spans of
# the same rows, which measures faster than row-fastest rasterization.
pid = tl.program_id(0)
if INT64_INDEX:
pid = pid.to(tl.int64)
NUM_BLOCKS_N: tl.constexpr = tl.cdiv(N, BLOCK_N)
pid_m = pid // NUM_BLOCKS_N
pid_n = pid % NUM_BLOCKS_N
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
mask_n = offs_n < N
offs_2n = pid_n * (2 * BLOCK_N) + tl.arange(0, 2 * BLOCK_N)
mask_2n = offs_2n < 2 * N
if BLOCK_M == 1:
# The grid covers M exactly; eliding the row mask measurably helps
# at bandwidth-bound shapes.
mask_mn = mask_n[None, :]
mask_m2n = mask_2n[None, :]
else:
mask_m = offs_m < M
mask_mn = mask_m[:, None] & mask_n[None, :]
mask_m2n = mask_m[:, None] & mask_2n[None, :]
def silu_and_mul_inputs(sizes: list[int]):
# Used only for Helion autotune input generation.
inputs = []
numel = 2**30
with torch.device("cuda"):
for size in sizes:
x = torch.randn(numel // size, 2 * size, dtype=torch.bfloat16)
inputs.append((x, None, None))
inputs.append((x, torch.randn(numel // size, dtype=torch.bfloat16), None))
return inputs
@helion_aot_autotune(
"silu_and_mul_interleaved",
kernel_key=silu_and_mul_key,
primary_inputs=partial(silu_and_mul_inputs, sizes=[512, 2048, 48 * 96, 6144, 8192]),
secondary_inputs=partial(
silu_and_mul_inputs,
sizes=[512]
+ [i * 96 for i in get_model_depths()]
+ list(range(1024, 8192 + 1, 1024)),
),
)
@helion.kernel(static_shapes=False)
def _silu_and_mul_helion_interleaved_kernel(
gateup_output,
topk_weights: torch.Tensor | None = None,
out_dtype: hl.constexpr | None = None,
):
"""
Interleaved version of silu_and_mul using Helion kernel.
Input format: [gate[0], up[0], gate[1], up[1], ...]
This matches the interleaved w13 weight format.
"""
batch_size, hidden_size = gateup_output.shape
hidden_size = hl.specialize(hidden_size)
assert hidden_size % 2 == 0, f"{hidden_size=}"
half_hidden_size = hidden_size // 2
down_input = gateup_output.new_empty(
batch_size, half_hidden_size, dtype=out_dtype or gateup_output.dtype
# Contiguous load of the [g, u] pairs, deinterleaved in registers via
# tl.split; element-strided gather loads halve the effective bandwidth.
gateup = tl.load(
gateup_out_ptr + offs_m[:, None] * stride_gm + offs_2n[None, :],
mask=mask_m2n,
other=0.0,
)
for batch_tile, hidden_tile in hl.tile([batch_size, half_hidden_size]):
gate_output = gateup_output[batch_tile, 2 * hidden_tile.index].to(torch.float32)
up_output = gateup_output[batch_tile, 2 * hidden_tile.index + 1].to(
torch.float32
)
silu_mul_output = gate_output * torch.sigmoid(gate_output) * up_output
if topk_weights is not None:
weight_scale = topk_weights[batch_tile, None].to(torch.float32)
silu_mul_output = silu_mul_output * weight_scale
down_input[batch_tile, hidden_tile] = silu_mul_output
return down_input
gate, up = tl.split(tl.reshape(gateup, (BLOCK_M, BLOCK_N, 2)))
gate = gate.to(tl.float32)
up = up.to(tl.float32)
down_inp = gate * tl.sigmoid(gate) * up
if HAS_TOPK_WEIGHTS:
if BLOCK_M == 1:
weight_scale = tl.load(
topk_weights_ptr + offs_m, eviction_policy="evict_last"
).to(tl.float32)
else:
weight_scale = tl.load(
topk_weights_ptr + offs_m,
mask=offs_m < M,
eviction_policy="evict_last",
).to(tl.float32)
down_inp = down_inp * weight_scale[:, None]
@helion_aot_autotune(
"silu_and_mul",
kernel_key=silu_and_mul_key,
primary_inputs=partial(silu_and_mul_inputs, sizes=[512, 2048, 48 * 96, 6144, 8192]),
secondary_inputs=partial(
silu_and_mul_inputs,
sizes=[512]
+ [i * 96 for i in get_model_depths()]
+ list(range(1024, 8192 + 1, 1024)),
),
)
@helion.kernel(static_shapes=False)
def _silu_and_mul_helion_non_interleaved_kernel(
gateup_output,
topk_weights: torch.Tensor | None = None,
out_dtype: hl.constexpr | None = None,
):
"""
Non-interleaved version of silu_and_mul using Helion kernel.
Input format: [gate[0], gate[1], ..., gate[N-1], up[0], up[1], ..., up[N-1]]
"""
batch_size, hidden_size = gateup_output.shape
hidden_size = hl.specialize(hidden_size)
assert hidden_size % 2 == 0, f"{hidden_size=}"
half_hidden_size = hidden_size // 2
down_input = gateup_output.new_empty(
batch_size, half_hidden_size, dtype=out_dtype or gateup_output.dtype
offs_mn = offs_m[:, None] * N + offs_n[None, :]
tl.store(
down_inp_ptr + offs_mn,
down_inp.to(down_inp_ptr.dtype.element_ty),
mask=mask_mn,
)
for batch_tile, hidden_tile in hl.tile([batch_size, half_hidden_size]):
gate_output = gateup_output[batch_tile, hidden_tile.index].to(torch.float32)
up_output = gateup_output[batch_tile, hidden_tile.index + half_hidden_size].to(
torch.float32
)
silu_mul_output = gate_output * torch.sigmoid(gate_output) * up_output
if topk_weights is not None:
weight_scale = topk_weights[batch_tile, None].to(torch.float32)
silu_mul_output = silu_mul_output * weight_scale
down_input[batch_tile, hidden_tile] = silu_mul_output
return down_input
def silu_and_mul_helion(
@triton.jit(do_not_specialize=["M"])
def silu_and_mul_non_interleaved_kernel(
gateup_out_ptr, # type: ignore # [M, 2N], rows are [g0, ..., gN-1, u0, ..., uN-1]
topk_weights_ptr, # type: ignore # [M] (unused when HAS_TOPK_WEIGHTS=False)
down_inp_ptr, # type: ignore # [M, N]
M, # type: ignore
stride_gm, # type: ignore
N: tl.constexpr,
HAS_TOPK_WEIGHTS: tl.constexpr,
INT64_INDEX: tl.constexpr,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
# Flat grid, N-tile fastest: consecutive CTAs read consecutive spans of
# the same rows, which measures faster than row-fastest rasterization.
pid = tl.program_id(0)
if INT64_INDEX:
pid = pid.to(tl.int64)
NUM_BLOCKS_N: tl.constexpr = tl.cdiv(N, BLOCK_N)
pid_m = pid // NUM_BLOCKS_N
pid_n = pid % NUM_BLOCKS_N
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
mask_n = offs_n < N
if BLOCK_M == 1:
# The grid covers M exactly; eliding the row mask measurably helps
# at bandwidth-bound shapes.
mask_mn = mask_n[None, :]
else:
mask_m = offs_m < M
mask_mn = mask_m[:, None] & mask_n[None, :]
# Cache hints match the tuned helion config (numerics-neutral): the gate
# stream stays in L2 while the up stream of the same rows loads.
offs_gate = offs_m[:, None] * stride_gm + offs_n[None, :]
gate = tl.load(
gateup_out_ptr + offs_gate,
mask=mask_mn,
other=0.0,
eviction_policy="evict_last",
).to(tl.float32)
up = tl.load(
gateup_out_ptr + offs_gate + N,
mask=mask_mn,
other=0.0,
eviction_policy="evict_first",
).to(tl.float32)
down_inp = gate * tl.sigmoid(gate) * up
if HAS_TOPK_WEIGHTS:
if BLOCK_M == 1:
weight_scale = tl.load(
topk_weights_ptr + offs_m, eviction_policy="evict_last"
).to(tl.float32)
else:
weight_scale = tl.load(
topk_weights_ptr + offs_m,
mask=offs_m < M,
eviction_policy="evict_last",
).to(tl.float32)
down_inp = down_inp * weight_scale[:, None]
offs_mn = offs_m[:, None] * N + offs_n[None, :]
tl.store(
down_inp_ptr + offs_mn,
down_inp.to(down_inp_ptr.dtype.element_ty),
mask=mask_mn,
)
def _select_silu_and_mul_config(
M: int, N: int, interleaved: bool
) -> tuple[int, int, int]:
"""(BLOCK_M, BLOCK_N, num_warps) heuristic from a GB300 (sm_100) sweep.
One row per CTA keeps the grid large enough to fill the GPU at decode row
counts. BLOCK_N=512 avoids a quarter-masked tail block for N=1536-style
widths; other widths prefer the widest contiguous load even when the tail
block is masked. The non-interleaved kernel reads two separate streams per
row and prefers (2, 512) tiles at prefill row counts.
"""
if not interleaved and M > 4096 and N % 512 == 0:
return 2, 512, 1
BLOCK_M = 1
if N % 512 == 0 and N % 1024 != 0:
BLOCK_N = 512
else:
BLOCK_N = min(1024, triton.next_power_of_2(N))
if M <= 4096:
num_warps = 4
else:
num_warps = 2 if interleaved else 1
return BLOCK_M, BLOCK_N, num_warps
def silu_and_mul(
gateup_output: torch.Tensor,
topk_weights: torch.Tensor | None = None,
out_dtype: torch.dtype | None = None,
use_interleaved: bool = True,
) -> torch.Tensor:
"""
Unified silu_and_mul function using Helion kernel.
Supports both interleaved and non-interleaved input formats.
"""silu(gate) * up over routed rows, optionally scaled by per-row weights.
Bitwise-identical port of the former Helion kernels (fp32 math,
gate * sigmoid(gate) * up, weight scale last, one rounding cast at the
store).
Args:
gateup_output: Input tensor of shape (batch_size, hidden_size)
topk_weights: Optional topk weights tensor
topk_weights: Optional per-row weights tensor of shape (batch_size,)
out_dtype: Optional output dtype
use_interleaved: If True, expects interleaved format [gate[0], up[0], gate[1], up[1], ...]
If False, expects non-interleaved format [gate[0], ..., gate[N-1], up[0], ..., up[N-1]]
@@ -144,20 +197,51 @@ def silu_and_mul_helion(
Returns:
Output tensor of shape (batch_size, hidden_size // 2)
"""
if use_interleaved:
return _silu_and_mul_helion_interleaved_kernel(
gateup_output, topk_weights, out_dtype
)
else:
return _silu_and_mul_helion_non_interleaved_kernel(
gateup_output, topk_weights, out_dtype
)
assert gateup_output.ndim == 2, f"{gateup_output.shape=}"
assert gateup_output.stride(1) == 1, f"{gateup_output.stride()=}"
M, hidden_size = gateup_output.shape
assert hidden_size % 2 == 0, f"{hidden_size=}"
N = hidden_size // 2
if topk_weights is not None:
assert topk_weights.is_contiguous(), f"{topk_weights.stride()=}"
assert topk_weights.shape == (M,), f"{topk_weights.shape=} {M=}"
down_input = torch.empty(
(M, N), device=gateup_output.device, dtype=out_dtype or gateup_output.dtype
)
if M == 0:
return down_input
BLOCK_M, BLOCK_N, num_warps = _select_silu_and_mul_config(M, N, use_interleaved)
grid = (triton.cdiv(M, BLOCK_M) * triton.cdiv(N, BLOCK_N),)
kernel = (
silu_and_mul_interleaved_kernel
if use_interleaved
else silu_and_mul_non_interleaved_kernel
)
kernel[grid](
gateup_out_ptr=gateup_output,
topk_weights_ptr=topk_weights,
down_inp_ptr=down_input,
M=M,
stride_gm=gateup_output.stride(0),
N=N,
HAS_TOPK_WEIGHTS=topk_weights is not None,
INT64_INDEX=gateup_output.nbytes >= 2**31 or down_input.nbytes >= 2**31,
BLOCK_M=BLOCK_M,
BLOCK_N=BLOCK_N,
num_warps=num_warps,
)
return down_input
# ---------------------------------------------------------------------------
# Triton silu_and_mul
# Used by InklingBatchDenseMLP._swiglu because the helion kernel above produces
# NaN for small shared-expert batches in EP+DP configs.
# Persistent-grid Triton silu_and_mul
# Used by InklingBatchDenseMLP._swiglu. It predates the plain-Triton port of
# silu_and_mul above: the helion kernels that silu_and_mul replaced were
# reported to produce NaN for small shared-expert batches in EP+DP configs.
# Unlike silu_and_mul it can read the row count from a device tensor (M_ptr)
# and supports PDL.
# ---------------------------------------------------------------------------
@@ -1,39 +0,0 @@
{
"useful_configs": {
"0": "helion.Config(block_sizes=[2, 512], indexing=['tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor'], l2_groupings=[1], load_eviction_policies=['last', '', ''], loop_orders=[[1, 0]], num_stages=3, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])",
"1": "helion.Config(block_sizes=[1, 1024], indexing=['tensor_descriptor', 'tensor_descriptor', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['last', 'first', 'last'], loop_orders=[[1, 0]], num_stages=5, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])",
"4": "helion.Config(block_sizes=[2, 512], indexing=['pointer', 'tensor_descriptor', 'pointer'], l2_groupings=[2], load_eviction_policies=['last', ''], loop_orders=[[0, 1]], num_stages=2, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])"
},
"hash_configs": {
"(512, (2,), (False,))": 0,
"(512, (2,), (True,))": 1,
"(1024, (2,), (False,))": 0,
"(1024, (2,), (True,))": 0,
"(1536, (2,), (False,))": 1,
"(1536, (2,), (True,))": 1,
"(2048, (2,), (False,))": 4,
"(2048, (2,), (True,))": 1,
"(3072, (2,), (False,))": 0,
"(3072, (2,), (True,))": 0,
"(4096, (2,), (False,))": 4,
"(4096, (2,), (True,))": 1,
"(4608, (2,), (False,))": 0,
"(4608, (2,), (True,))": 0,
"(6144, (2,), (False,))": 0,
"(6144, (2,), (True,))": 1,
"(7680, (2,), (False,))": 1,
"(7680, (2,), (True,))": 1,
"(8192, (2,), (False,))": 4,
"(8192, (2,), (True,))": 1,
"(9216, (2,), (False,))": 0,
"(9216, (2,), (True,))": 0,
"(10240, (2,), (False,))": 0,
"(10240, (2,), (True,))": 1,
"(12288, (2,), (False,))": 0,
"(12288, (2,), (True,))": 1,
"(14336, (2,), (False,))": 0,
"(14336, (2,), (True,))": 1,
"(16384, (2,), (False,))": 4,
"(16384, (2,), (True,))": 1
}
}
@@ -1,39 +0,0 @@
{
"useful_configs": {
"0": "helion.Config(block_sizes=[2, 512], indexing=['tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor'], l2_groupings=[1], load_eviction_policies=['last', '', ''], loop_orders=[[1, 0]], num_stages=3, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])",
"1": "helion.Config(block_sizes=[1, 1024], indexing=['tensor_descriptor', 'tensor_descriptor', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['last', 'first', 'last'], loop_orders=[[1, 0]], num_stages=5, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])",
"4": "helion.Config(block_sizes=[2, 512], indexing=['pointer', 'tensor_descriptor', 'pointer'], l2_groupings=[2], load_eviction_policies=['last', ''], loop_orders=[[0, 1]], num_stages=2, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])"
},
"hash_configs": {
"(512, (2,), (False,))": 0,
"(512, (2,), (True,))": 1,
"(1024, (2,), (False,))": 0,
"(1024, (2,), (True,))": 0,
"(1536, (2,), (False,))": 1,
"(1536, (2,), (True,))": 1,
"(2048, (2,), (False,))": 4,
"(2048, (2,), (True,))": 1,
"(3072, (2,), (False,))": 0,
"(3072, (2,), (True,))": 0,
"(4096, (2,), (False,))": 4,
"(4096, (2,), (True,))": 1,
"(4608, (2,), (False,))": 0,
"(4608, (2,), (True,))": 0,
"(6144, (2,), (False,))": 0,
"(6144, (2,), (True,))": 1,
"(7680, (2,), (False,))": 1,
"(7680, (2,), (True,))": 1,
"(8192, (2,), (False,))": 4,
"(8192, (2,), (True,))": 1,
"(9216, (2,), (False,))": 0,
"(9216, (2,), (True,))": 0,
"(10240, (2,), (False,))": 0,
"(10240, (2,), (True,))": 1,
"(12288, (2,), (False,))": 0,
"(12288, (2,), (True,))": 1,
"(14336, (2,), (False,))": 0,
"(14336, (2,), (True,))": 1,
"(16384, (2,), (False,))": 4,
"(16384, (2,), (True,))": 1
}
}
@@ -1,40 +0,0 @@
{
"useful_configs": {
"0": "helion.Config(block_sizes=[2, 512], indexing=['pointer', 'pointer', 'tensor_descriptor'], l2_groupings=[2], load_eviction_policies=['', ''], loop_orders=[[0, 1]], num_stages=2, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"1": "helion.Config(block_sizes=[1, 512], indexing=['pointer', 'tensor_descriptor', 'tensor_descriptor', 'pointer'], l2_groupings=[1], load_eviction_policies=['', 'first', 'first'], loop_orders=[[1, 0]], num_stages=7, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"2": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['last', '', 'last'], loop_orders=[[1, 0]], num_stages=3, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"3": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor'], l2_groupings=[1], load_eviction_policies=['', 'first', ''], loop_orders=[[1, 0]], num_stages=7, num_warps=2, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])"
},
"hash_configs": {
"(512, (2,), (False,))": 0,
"(512, (2,), (True,))": 1,
"(1024, (2,), (False,))": 0,
"(1024, (2,), (True,))": 1,
"(1536, (2,), (False,))": 2,
"(1536, (2,), (True,))": 2,
"(2048, (2,), (False,))": 3,
"(2048, (2,), (True,))": 3,
"(3072, (2,), (False,))": 1,
"(3072, (2,), (True,))": 1,
"(4096, (2,), (False,))": 3,
"(4096, (2,), (True,))": 3,
"(4608, (2,), (False,))": 1,
"(4608, (2,), (True,))": 1,
"(6144, (2,), (False,))": 3,
"(6144, (2,), (True,))": 3,
"(7680, (2,), (False,))": 2,
"(7680, (2,), (True,))": 2,
"(8192, (2,), (False,))": 3,
"(8192, (2,), (True,))": 3,
"(9216, (2,), (False,))": 1,
"(9216, (2,), (True,))": 1,
"(10240, (2,), (False,))": 3,
"(10240, (2,), (True,))": 3,
"(12288, (2,), (False,))": 3,
"(12288, (2,), (True,))": 3,
"(14336, (2,), (False,))": 3,
"(14336, (2,), (True,))": 3,
"(16384, (2,), (False,))": 3,
"(16384, (2,), (True,))": 3
}
}
@@ -1,40 +0,0 @@
{
"useful_configs": {
"0": "helion.Config(block_sizes=[2, 512], indexing=['pointer', 'pointer', 'pointer'], l2_groupings=[2], load_eviction_policies=['', ''], loop_orders=[[0, 1]], num_stages=2, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"1": "helion.Config(block_sizes=[1, 512], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['', 'first', 'first'], loop_orders=[[1, 0]], num_stages=7, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"2": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['last', '', 'last'], loop_orders=[[1, 0]], num_stages=3, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"3": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['', 'first', ''], loop_orders=[[1, 0]], num_stages=7, num_warps=2, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])"
},
"hash_configs": {
"(512, (2,), (False,))": 0,
"(512, (2,), (True,))": 1,
"(1024, (2,), (False,))": 0,
"(1024, (2,), (True,))": 1,
"(1536, (2,), (False,))": 2,
"(1536, (2,), (True,))": 2,
"(2048, (2,), (False,))": 3,
"(2048, (2,), (True,))": 3,
"(3072, (2,), (False,))": 1,
"(3072, (2,), (True,))": 1,
"(4096, (2,), (False,))": 3,
"(4096, (2,), (True,))": 3,
"(4608, (2,), (False,))": 1,
"(4608, (2,), (True,))": 1,
"(6144, (2,), (False,))": 3,
"(6144, (2,), (True,))": 3,
"(7680, (2,), (False,))": 2,
"(7680, (2,), (True,))": 2,
"(8192, (2,), (False,))": 3,
"(8192, (2,), (True,))": 3,
"(9216, (2,), (False,))": 1,
"(9216, (2,), (True,))": 1,
"(10240, (2,), (False,))": 3,
"(10240, (2,), (True,))": 3,
"(12288, (2,), (False,))": 3,
"(12288, (2,), (True,))": 3,
"(14336, (2,), (False,))": 3,
"(14336, (2,), (True,))": 3,
"(16384, (2,), (False,))": 3,
"(16384, (2,), (True,))": 3
}
}
@@ -1,39 +0,0 @@
{
"useful_configs": {
"0": "helion.Config(block_sizes=[2, 512], indexing=['tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor'], l2_groupings=[1], load_eviction_policies=['last', '', ''], loop_orders=[[1, 0]], num_stages=3, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])",
"1": "helion.Config(block_sizes=[1, 1024], indexing=['tensor_descriptor', 'tensor_descriptor', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['last', 'first', 'last'], loop_orders=[[1, 0]], num_stages=5, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])",
"4": "helion.Config(block_sizes=[2, 512], indexing=['pointer', 'tensor_descriptor', 'pointer'], l2_groupings=[2], load_eviction_policies=['last', ''], loop_orders=[[0, 1]], num_stages=2, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[None])"
},
"hash_configs": {
"(512, (2,), (False,))": 0,
"(512, (2,), (True,))": 1,
"(1024, (2,), (False,))": 0,
"(1024, (2,), (True,))": 0,
"(1536, (2,), (False,))": 1,
"(1536, (2,), (True,))": 1,
"(2048, (2,), (False,))": 4,
"(2048, (2,), (True,))": 1,
"(3072, (2,), (False,))": 0,
"(3072, (2,), (True,))": 0,
"(4096, (2,), (False,))": 4,
"(4096, (2,), (True,))": 1,
"(4608, (2,), (False,))": 0,
"(4608, (2,), (True,))": 0,
"(6144, (2,), (False,))": 0,
"(6144, (2,), (True,))": 1,
"(7680, (2,), (False,))": 1,
"(7680, (2,), (True,))": 1,
"(8192, (2,), (False,))": 4,
"(8192, (2,), (True,))": 1,
"(9216, (2,), (False,))": 0,
"(9216, (2,), (True,))": 0,
"(10240, (2,), (False,))": 0,
"(10240, (2,), (True,))": 1,
"(12288, (2,), (False,))": 0,
"(12288, (2,), (True,))": 1,
"(14336, (2,), (False,))": 0,
"(14336, (2,), (True,))": 1,
"(16384, (2,), (False,))": 4,
"(16384, (2,), (True,))": 1
}
}
@@ -1,40 +0,0 @@
{
"useful_configs": {
"0": "helion.Config(block_sizes=[2, 512], indexing=['pointer', 'pointer', 'tensor_descriptor'], l2_groupings=[2], load_eviction_policies=['', ''], loop_orders=[[0, 1]], num_stages=2, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"1": "helion.Config(block_sizes=[1, 512], indexing=['pointer', 'tensor_descriptor', 'tensor_descriptor', 'pointer'], l2_groupings=[1], load_eviction_policies=['', 'first', 'first'], loop_orders=[[1, 0]], num_stages=7, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"2": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['last', '', 'last'], loop_orders=[[1, 0]], num_stages=3, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"3": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'tensor_descriptor', 'tensor_descriptor', 'tensor_descriptor'], l2_groupings=[1], load_eviction_policies=['', 'first', ''], loop_orders=[[1, 0]], num_stages=7, num_warps=2, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])"
},
"hash_configs": {
"(512, (2,), (False,))": 0,
"(512, (2,), (True,))": 1,
"(1024, (2,), (False,))": 0,
"(1024, (2,), (True,))": 1,
"(1536, (2,), (False,))": 2,
"(1536, (2,), (True,))": 2,
"(2048, (2,), (False,))": 3,
"(2048, (2,), (True,))": 3,
"(3072, (2,), (False,))": 1,
"(3072, (2,), (True,))": 1,
"(4096, (2,), (False,))": 3,
"(4096, (2,), (True,))": 3,
"(4608, (2,), (False,))": 1,
"(4608, (2,), (True,))": 1,
"(6144, (2,), (False,))": 3,
"(6144, (2,), (True,))": 3,
"(7680, (2,), (False,))": 2,
"(7680, (2,), (True,))": 2,
"(8192, (2,), (False,))": 3,
"(8192, (2,), (True,))": 3,
"(9216, (2,), (False,))": 1,
"(9216, (2,), (True,))": 1,
"(10240, (2,), (False,))": 3,
"(10240, (2,), (True,))": 3,
"(12288, (2,), (False,))": 3,
"(12288, (2,), (True,))": 3,
"(14336, (2,), (False,))": 3,
"(14336, (2,), (True,))": 3,
"(16384, (2,), (False,))": 3,
"(16384, (2,), (True,))": 3
}
}
@@ -1,40 +0,0 @@
{
"useful_configs": {
"0": "helion.Config(block_sizes=[2, 512], indexing=['pointer', 'pointer', 'pointer'], l2_groupings=[2], load_eviction_policies=['', ''], loop_orders=[[0, 1]], num_stages=2, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"1": "helion.Config(block_sizes=[1, 512], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['', 'first', 'first'], loop_orders=[[1, 0]], num_stages=7, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"2": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['last', '', 'last'], loop_orders=[[1, 0]], num_stages=3, num_warps=1, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])",
"3": "helion.Config(block_sizes=[1, 1024], indexing=['pointer', 'pointer', 'pointer', 'pointer'], l2_groupings=[1], load_eviction_policies=['', 'first', ''], loop_orders=[[1, 0]], num_stages=7, num_warps=2, pid_type='flat', range_flattens=[None], range_multi_buffers=[None], range_num_stages=[0], range_unroll_factors=[0], range_warp_specializes=[])"
},
"hash_configs": {
"(512, (2,), (False,))": 0,
"(512, (2,), (True,))": 1,
"(1024, (2,), (False,))": 0,
"(1024, (2,), (True,))": 1,
"(1536, (2,), (False,))": 2,
"(1536, (2,), (True,))": 2,
"(2048, (2,), (False,))": 3,
"(2048, (2,), (True,))": 3,
"(3072, (2,), (False,))": 1,
"(3072, (2,), (True,))": 1,
"(4096, (2,), (False,))": 3,
"(4096, (2,), (True,))": 3,
"(4608, (2,), (False,))": 1,
"(4608, (2,), (True,))": 1,
"(6144, (2,), (False,))": 3,
"(6144, (2,), (True,))": 3,
"(7680, (2,), (False,))": 2,
"(7680, (2,), (True,))": 2,
"(8192, (2,), (False,))": 3,
"(8192, (2,), (True,))": 3,
"(9216, (2,), (False,))": 1,
"(9216, (2,), (True,))": 1,
"(10240, (2,), (False,))": 3,
"(10240, (2,), (True,))": 3,
"(12288, (2,), (False,))": 3,
"(12288, (2,), (True,))": 3,
"(14336, (2,), (False,))": 3,
"(14336, (2,), (True,))": 3,
"(16384, (2,), (False,))": 3,
"(16384, (2,), (True,))": 3
}
}
@@ -1,296 +0,0 @@
import copy
import dataclasses
import functools
import json
import logging
import os
from collections import defaultdict
from collections.abc import Iterable
from enum import Enum
from pathlib import Path
from typing import Any, Callable
import helion # noqa: F401
import torch
from triton.testing import do_bench
logger = logging.getLogger(__name__)
AutotuneInputFn = Callable[[], Iterable[tuple[Any, ...]]]
def get_model_depths() -> list[int]:
return [8, 16, 24, 32, 40, 48, 64]
def get_cuda_device_capability() -> tuple[int, int]:
if torch.cuda.is_available():
dev = torch.cuda.current_device()
cc_major, cc_minor = torch.cuda.get_device_capability(dev)
return cc_major, cc_minor
return (0, 0)
class AOTAutotuneMode(Enum):
NONE = "none"
CREATE = "create"
RETUNE = "retune"
@classmethod
def from_str(cls, mode: str) -> "AOTAutotuneMode":
return cls[mode.upper()]
def load_autotune_data_from_json(
path: Path,
) -> tuple[dict[int, Any], dict[tuple[Any, Any, Any], int]]:
with open(path, "r") as f:
json_obj = json.load(f)
useful_configs = {
int(k): eval(v) for k, v in json_obj["useful_configs"].items()
}
hash_configs = {tuple(eval(k)): v for k, v in json_obj["hash_configs"].items()}
return useful_configs, hash_configs
def bind_and_compile_kernel(
kernel: helion.Kernel, args: Any, config: helion.Config
) -> Callable:
has_int64 = any([i.numel() >= 2**31 for i in args if isinstance(i, torch.Tensor)])
if has_int64:
new_settings = dataclasses.replace(kernel.settings, index_dtype=torch.int64)
kernel = helion.Kernel(
kernel.fn,
configs=kernel.configs,
settings=new_settings,
key=kernel._key_fn,
)
config = copy.deepcopy(config)
return kernel.bind(args).compile_config(config, allow_print=False)
def helion_aot_autotune(
config_path: str,
kernel_key: Callable[..., tuple[Any, Any, Any]],
primary_inputs: AutotuneInputFn,
secondary_inputs: AutotuneInputFn | None = None,
int64_threshold: Callable[..., bool] | None = None,
warn_on_hash_miss: bool = False,
):
"""
A decorator that automatically tunes and dispatches a Helion kernel based off of the kernel_key and provided inputs.
The general flow is this:
1. We first run helion_kernel.autotune on all primary_inputs. This will give us a list of configs, one per primary_input.
2. We benchmark every config on every primary_input and secondary_input.
3. For every primary input/secondary input, we keep the config that is the fastest (NB: We aim to do some deduplication by reusing configs if they're within some threshold of the fastest config).
4. We'll save the configs and the dispatch choices to a json file.
5. We create a dispatch function that will lookup to see whether each kernel is one we've tuned for in step 2. If so, we'll use that config. Otherwise, we'll use some heuristic to (deterministically) find a reasonable config for the kernel.
There are 3 modes (set by env variable HELION_AOT_AUTOTUNE):
- none: No autotuning will be done. We will skip to step 5. If the config json file doesn't exist, we'll raise an error.
- retune: We only benchmark the existing useful configs on the primary/secondary inputs. We'll skip to step 2. This finishes much faster than create (albeit won't fully retune for each shape), but requires create to have been run first. For example, for rmsnorm, retune takes maybe one minute for 10 shapes, but create might take 30 minutes.
- create: The kernel will be fully autotuned, starting from step 1.
You can also minimize the kernels to be autotuned by setting the env variable HELION_AOT_AUTOTUNE_KERNEL to the name of the kernel. For example, if you want to only autotune rmsnorm, you can set HELION_AOT_AUTOTUNE_KERNEL="rms_norm_fwd".
kernel_key: Callable[..., tuple[Any, Any, Any]]
returns:
(numeric_key, hash_key, exact_key)
The semantics are that if all 3 match a saved key, we'll use that config.
Otherwise, we'll use some heuristic to find a reasonable config for the
kernel. The heuristic is:
- We require exact_key to match the saved config.
- We prioritize hash_key that match the saved config.
- We then prioritize the highest numeric_key that's <= the current numeric_key.
"""
# Threshold for how much faster a config has to be for some shape to be considered "useful"
threshold = 1.01
# How many ms to run the kernel when retuning
retune_rep_ms = 1000
def inner_autotune(kernel: helion.Kernel):
helion_dir = Path(__file__).parent / "configs"
cc_major, cc_minor = get_cuda_device_capability()
if cc_minor == 3:
cc_minor = 0
gpu_arch = f"sm_{cc_major}{cc_minor}"
path = helion_dir / Path(f"{config_path}_{gpu_arch}.json")
@functools.wraps(kernel_key)
def wrapped_kernel_key(*inps: Any) -> tuple[Any, Any, Any]:
"""
A wrapper that handles dtype specially (since dtype is not
serializable to json).
"""
numeric_key, hash_key, exact_key = kernel_key(*inps)
assert isinstance(hash_key, tuple)
assert isinstance(exact_key, tuple)
hash_key = tuple(
i.itemsize if isinstance(i, torch.dtype) else i for i in hash_key
)
if exact_key is not None:
exact_key = tuple(
i.itemsize if isinstance(i, torch.dtype) else i for i in exact_key
)
return numeric_key, hash_key, exact_key
autotune_mode_str = os.environ.get("HELION_AOT_AUTOTUNE", "none")
autotune_mode = AOTAutotuneMode.from_str(autotune_mode_str)
autotune_kernel = os.environ.get("HELION_AOT_AUTOTUNE_KERNEL", "all")
if autotune_kernel != "all" and autotune_kernel != config_path:
autotune_mode = AOTAutotuneMode.NONE
@functools.cache
def get_configs_from_autotuning(autotune_mode: AOTAutotuneMode):
if autotune_mode == AOTAutotuneMode.NONE:
if not path.exists():
raise RuntimeError(
f"Helion kernel not tuned yet. Run with HELION_AOT_AUTOTUNE=create HELION_AOT_AUTOTUNE_KERNEL={config_path} to generate the config at {path}"
)
return load_autotune_data_from_json(path)
inputs = sorted(
list(primary_inputs()), key=lambda x: wrapped_kernel_key(*x)[0]
)
if autotune_mode == AOTAutotuneMode.CREATE:
useful_configs = []
for idx, input in enumerate(inputs):
print(
f"Autotuning for {config_path} with key: ",
wrapped_kernel_key(*input),
)
config = kernel.autotune(input)
useful_configs.append(repr(config))
elif autotune_mode == AOTAutotuneMode.RETUNE:
with open(path, "r") as f:
json_obj = json.load(f)
useful_configs = list(json_obj["useful_configs"].values())
else:
raise RuntimeError(f"Unexpected autotune mode: {autotune_mode}")
logger.info("Candidate useful configs: ")
for idx, config in enumerate(useful_configs):
logger.info(f"{idx}:, Config: {config}")
input_timings = []
if secondary_inputs is not None:
inputs += list(secondary_inputs())
inputs = sorted(inputs, key=lambda x: kernel_key(*x)[0])
for input in inputs:
cur_input_key = wrapped_kernel_key(*input)
timings = []
for idx, config in enumerate(useful_configs):
try:
cur_kernel = bind_and_compile_kernel(
kernel, input, eval(config)
)
timings.append(
do_bench(lambda: cur_kernel(*input), rep=retune_rep_ms)
) # noqa: B023
except Exception as e:
logger.info(f"Error compiling config {config}: {e}")
timings.append(float("inf"))
input_timings.append((cur_input_key, timings))
for idx, (key, timing) in enumerate(input_timings):
logger.info(
f"Key {key} timings: {' '.join([f'{i:.5f}' for i in timing])}"
)
hash_configs_timings: dict[tuple[Any, Any], tuple[float, int | None]] = (
defaultdict(lambda: (float("inf"), None))
)
for cur_kernel_key, input_timings in input_timings:
for config_idx, (config, timing) in enumerate(
zip(useful_configs, input_timings)
):
if timing < hash_configs_timings[cur_kernel_key][0] * threshold:
hash_configs_timings[cur_kernel_key] = (timing, config_idx)
kept_configs = {}
hash_configs = {k: v[1] for k, v in hash_configs_timings.items()}
for key, config_idx in hash_configs.items():
assert config_idx is not None
kept_configs[config_idx] = useful_configs[config_idx]
json_obj = {
"useful_configs": kept_configs,
"hash_configs": {repr(k): v for k, v in hash_configs.items()},
}
with open(path, "w") as f:
json.dump(json_obj, f, indent=2)
f.write("\n")
return load_autotune_data_from_json(path)
cached_kernels = {}
def default_int64_threshold(*args: Any) -> bool:
return any(
[i.numel() >= 2**31 for i in args if isinstance(i, torch.Tensor)]
)
used_int64_threshold = (
int64_threshold if int64_threshold is not None else default_int64_threshold
)
def wrapped_func(*args: Any):
nonlocal cached_kernels
cur_kernel_key = wrapped_kernel_key(*args)
has_int64 = used_int64_threshold(*args)
size1 = tuple(
tuple(shape == 1 for shape in arg.shape)
for arg in args
if isinstance(arg, torch.Tensor)
)
dtypes = tuple(arg.dtype for arg in args if isinstance(arg, torch.Tensor))
scalar_args = tuple(a for a in args if not isinstance(a, torch.Tensor))
key = (cur_kernel_key, has_int64, size1, dtypes, scalar_args)
if key in cached_kernels:
out = cached_kernels[key](*args)
return out
if has_int64:
kernel.settings = dataclasses.replace(
kernel.settings, index_dtype=torch.int64
)
useful_configs, hash_configs = get_configs_from_autotuning(autotune_mode)
key_to_config = {k: useful_configs[v] for k, v in hash_configs.items()}
if key not in cached_kernels and cur_kernel_key in hash_configs:
cached_kernels[key] = bind_and_compile_kernel(
kernel, args, key_to_config[cur_kernel_key]
)
else:
if warn_on_hash_miss:
logger.warning(
f"No config found for key {cur_kernel_key} for kernel={config_path}. Finding best match. This is *not* a correctness issue, but means that the performance of this kernel could potentially be improved."
)
used_config = None
def config_key_sort(
config_key: tuple[Any, Any, Any],
) -> tuple[Any, ...]:
return (
config_key[2] == cur_kernel_key[2],
config_key[1] == cur_kernel_key[1],
config_key[0] <= cur_kernel_key[0],
config_key[0],
)
sorted_keys = sorted(
hash_configs.keys(), key=config_key_sort, reverse=True
)
used_config = key_to_config[sorted_keys[0]]
if len(used_config) == 3:
assert (
used_config[2] == cur_kernel_key[2]
), "Exact key not found in configs"
cached_kernels[key] = bind_and_compile_kernel(kernel, args, used_config)
out = cached_kernels[key](*args)
return out
return wrapped_func
return inner_autotune
@@ -25,7 +25,7 @@ from sglang.kernels.ops.moe.inkling_moe import (
post_reorder,
pre_reorder,
select_grouped_gemm_block_m,
silu_and_mul_helion,
silu_and_mul,
)
from sglang.kernels.ops.moe.sigmoid_gate_topk_renorm import (
sigmoid_gate_topk_renorm,
@@ -572,7 +572,7 @@ def activation(
*gateup_output.shape[:-1], gateup_output.shape[-1] // 2, dtype=out_dtype
)
return silu_and_mul_helion(
return silu_and_mul(
gateup_output, topk_weights, out_dtype, use_interleaved=use_interleaved
)
raise ValueError(f"Unsupported activation: {activation_type}")