[AMD] Perf Kimi-K3 fuse ROCm KDA decode boundary (#34198)
Co-authored-by: wunhuang <wunhuang@amd.com>
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
"""Fail-closed adapter for AITER's gfx950 Kimi-K3 fused KDA decode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.utils import is_hip
|
||||
|
||||
_HEADS = 12
|
||||
_DIM = 128
|
||||
_CHANNELS = 3 * _HEADS * _DIM
|
||||
_WARMED: set[tuple[int, float, float]] = set()
|
||||
|
||||
|
||||
def enabled() -> bool:
|
||||
return os.environ.get("SGLANG_K3_KDA_FUSED_BACKEND", "").lower() == "aiter"
|
||||
|
||||
|
||||
def _ops():
|
||||
try:
|
||||
from sglang.kernels.ops.kimi_k3.flydsl.source import load_module
|
||||
|
||||
module = load_module(
|
||||
"sglang.kernels.ops.kimi_k3.flydsl.kimi_k3_kda_decode",
|
||||
"aiter.ops.flydsl.kimi_k3_kda_decode",
|
||||
)
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
return None, None
|
||||
return (
|
||||
module.flydsl_kimi_k3_kda_decode_with_f_b,
|
||||
module.is_flydsl_kimi_k3_kda_decode_supported,
|
||||
)
|
||||
|
||||
|
||||
def available(device: torch.device | None = None) -> bool:
|
||||
if not is_hip() or not enabled() or not torch.cuda.is_available():
|
||||
return False
|
||||
_, supported = _ops()
|
||||
if supported is None:
|
||||
return False
|
||||
return bool(supported(device))
|
||||
|
||||
|
||||
def covered(
|
||||
f_a: torch.Tensor,
|
||||
f_b_weight: torch.Tensor,
|
||||
mixed_qkv: torch.Tensor,
|
||||
raw_beta: torch.Tensor,
|
||||
conv_state: torch.Tensor,
|
||||
state: torch.Tensor,
|
||||
state_indices: torch.Tensor,
|
||||
output_gate: torch.Tensor,
|
||||
norm_weight: torch.Tensor,
|
||||
) -> bool:
|
||||
if not available(f_a.device) or f_a.ndim != 2:
|
||||
return False
|
||||
batch = f_a.shape[0]
|
||||
return (
|
||||
batch > 0
|
||||
and f_a.shape == (batch, _DIM)
|
||||
and f_a.dtype == torch.bfloat16
|
||||
and f_a.stride(-1) == 1
|
||||
and f_b_weight.shape == (_HEADS, _DIM, _DIM)
|
||||
and f_b_weight.dtype == torch.bfloat16
|
||||
and f_b_weight.stride()[-2:] == (_DIM, 1)
|
||||
and mixed_qkv.shape == (batch, _CHANNELS)
|
||||
and mixed_qkv.dtype == torch.bfloat16
|
||||
and mixed_qkv.stride(-1) == 1
|
||||
and raw_beta.shape == (1, batch, _HEADS)
|
||||
and raw_beta.dtype == torch.bfloat16
|
||||
and conv_state.ndim == 3
|
||||
and conv_state.shape[1:] == (_CHANNELS, 3)
|
||||
and conv_state.dtype == torch.bfloat16
|
||||
and state.ndim == 4
|
||||
and state.shape[1:] == (_HEADS, _DIM, _DIM)
|
||||
and state.dtype == torch.float32
|
||||
and state.stride()[-3:] == (_DIM * _DIM, _DIM, 1)
|
||||
and state_indices.shape == (batch,)
|
||||
and state_indices.dtype == torch.int32
|
||||
and state_indices.stride(0) == 1
|
||||
and output_gate.shape == (batch, _HEADS, _DIM)
|
||||
and output_gate.dtype == torch.bfloat16
|
||||
and norm_weight.shape == (_DIM,)
|
||||
and norm_weight.dtype == torch.bfloat16
|
||||
)
|
||||
|
||||
|
||||
def run(
|
||||
*,
|
||||
f_a: torch.Tensor,
|
||||
f_b_weight: torch.Tensor,
|
||||
mixed_qkv: torch.Tensor,
|
||||
conv_weight: torch.Tensor,
|
||||
conv_state: torch.Tensor,
|
||||
raw_beta: torch.Tensor,
|
||||
A_log: torch.Tensor,
|
||||
dt_bias: torch.Tensor,
|
||||
lower_bound: float,
|
||||
state: torch.Tensor,
|
||||
state_indices: torch.Tensor,
|
||||
output_gate: torch.Tensor,
|
||||
norm_weight: torch.Tensor,
|
||||
norm_eps: float,
|
||||
out: torch.Tensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
op, _ = _ops()
|
||||
if op is None:
|
||||
raise RuntimeError("AITER Kimi-K3 fused KDA decode is unavailable")
|
||||
return op(
|
||||
f_a=f_a,
|
||||
f_b_weight=f_b_weight,
|
||||
x=mixed_qkv,
|
||||
conv_weight=conv_weight,
|
||||
conv_bias=None,
|
||||
conv_state=conv_state,
|
||||
raw_beta=raw_beta,
|
||||
A_log=A_log,
|
||||
dt_bias=dt_bias,
|
||||
lower_bound=lower_bound,
|
||||
state=state,
|
||||
state_indices=state_indices,
|
||||
output_gate=output_gate,
|
||||
norm_weight=norm_weight,
|
||||
norm_eps=norm_eps,
|
||||
out=out,
|
||||
)
|
||||
|
||||
|
||||
def warmup(
|
||||
*,
|
||||
f_b_weight: torch.Tensor,
|
||||
conv_weight: torch.Tensor,
|
||||
A_log: torch.Tensor,
|
||||
dt_bias: torch.Tensor,
|
||||
lower_bound: float,
|
||||
norm_weight: torch.Tensor,
|
||||
norm_eps: float,
|
||||
) -> None:
|
||||
if not available(f_b_weight.device):
|
||||
return
|
||||
device_index = -1 if f_b_weight.device.index is None else f_b_weight.device.index
|
||||
key = (device_index, float(norm_eps), float(lower_bound))
|
||||
if key in _WARMED:
|
||||
return
|
||||
|
||||
device = f_b_weight.device
|
||||
run(
|
||||
f_a=torch.zeros(1, _DIM, dtype=torch.bfloat16, device=device),
|
||||
f_b_weight=f_b_weight,
|
||||
mixed_qkv=torch.zeros(1, _CHANNELS, dtype=torch.bfloat16, device=device),
|
||||
conv_weight=conv_weight,
|
||||
conv_state=torch.zeros(1, _CHANNELS, 3, dtype=torch.bfloat16, device=device),
|
||||
raw_beta=torch.zeros(1, 1, _HEADS, dtype=torch.bfloat16, device=device),
|
||||
A_log=A_log,
|
||||
dt_bias=dt_bias,
|
||||
lower_bound=lower_bound,
|
||||
state=torch.zeros(1, _HEADS, _DIM, _DIM, dtype=torch.float32, device=device),
|
||||
state_indices=torch.zeros(1, dtype=torch.int32, device=device),
|
||||
output_gate=torch.zeros(1, _HEADS, _DIM, dtype=torch.bfloat16, device=device),
|
||||
norm_weight=norm_weight,
|
||||
norm_eps=norm_eps,
|
||||
)
|
||||
torch.cuda.synchronize(device)
|
||||
_WARMED.add(key)
|
||||
@@ -0,0 +1,17 @@
|
||||
"""SGLang-maintained Kimi-K3 FlyDSL specializations."""
|
||||
|
||||
# AITER owns the FlyDSL toolchain bootstrap and shared tensor/buffer shims.
|
||||
# Import it before local kernel modules so its vendored FlyDSL path is active.
|
||||
import aiter as _aiter # noqa: F401
|
||||
|
||||
from .kimi_k3_kda_decode import (
|
||||
flydsl_kimi_k3_kda_decode,
|
||||
flydsl_kimi_k3_kda_decode_with_f_b,
|
||||
is_flydsl_kimi_k3_kda_decode_supported,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"flydsl_kimi_k3_kda_decode",
|
||||
"flydsl_kimi_k3_kda_decode_with_f_b",
|
||||
"is_flydsl_kimi_k3_kda_decode_supported",
|
||||
]
|
||||
@@ -0,0 +1,564 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
|
||||
|
||||
"""FlyDSL kernel for the fused Kimi-K3 KDA decode path on gfx950."""
|
||||
|
||||
import functools
|
||||
import math
|
||||
|
||||
import flydsl.compiler as flyc
|
||||
import flydsl.expr as fx
|
||||
from aiter.ops.flydsl.kernels import vector
|
||||
from aiter.ops.flydsl.kernels.tensor_shim import GTensor, _to_raw
|
||||
from flydsl._mlir import ir
|
||||
from flydsl._mlir.dialects import gpu as mlir_gpu
|
||||
from flydsl._mlir.dialects import scf
|
||||
from flydsl._mlir.dialects import vector as mlir_vector
|
||||
from flydsl.expr import range_constexpr
|
||||
from flydsl.expr.typing import T
|
||||
|
||||
_HEADS = 12
|
||||
_DIM = 128
|
||||
_LOG2E = math.log2(math.e)
|
||||
_SCALE = _DIM**-0.5
|
||||
_BLOCK_THREADS = 256
|
||||
_NUM_WARPS = 4
|
||||
_WARP_SIZE = 64
|
||||
_WARP_THREADS_K = 8
|
||||
_VALUES_PER_THREAD_K = 4
|
||||
_WARP_TILE_K = _WARP_THREADS_K * _VALUES_PER_THREAD_K
|
||||
_K_ITERS = _DIM // _WARP_TILE_K
|
||||
_WARP_THREADS_V = _WARP_SIZE // _WARP_THREADS_K
|
||||
_V_GROUP_TILE = _NUM_WARPS * _WARP_THREADS_V
|
||||
_V_ITERS = _DIM // _V_GROUP_TILE
|
||||
_WAVES_PER_EU = 3
|
||||
|
||||
|
||||
@functools.cache
|
||||
def create_kimi_k3_kda_decode_kernel(norm_eps: float, lower_bound: float):
|
||||
"""Build the fixed gfx950 BF16 Kimi-K3 decode specialization."""
|
||||
|
||||
@fx.struct
|
||||
class SharedStorage:
|
||||
q: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
k: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
v: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
recurrent_out: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
norm_partial: fx.Array[fx.Float32, 2, 16]
|
||||
|
||||
@flyc.kernel(
|
||||
name="kimi_k3_kda_decode_bf16_gfx950",
|
||||
known_block_size=[_BLOCK_THREADS, 1, 1],
|
||||
)
|
||||
def kernel(
|
||||
x_mem: fx.Tensor,
|
||||
weight_mem: fx.Tensor,
|
||||
conv_state_mem: fx.Tensor,
|
||||
raw_g_mem: fx.Tensor,
|
||||
raw_beta_mem: fx.Tensor,
|
||||
A_log_mem: fx.Tensor,
|
||||
dt_bias_mem: fx.Tensor,
|
||||
state_mem: fx.Tensor,
|
||||
state_indices_mem: fx.Tensor,
|
||||
output_gate_mem: fx.Tensor,
|
||||
norm_weight_mem: fx.Tensor,
|
||||
out_mem: fx.Tensor,
|
||||
batch_size: fx.Int32,
|
||||
stride_x_token: fx.Int32,
|
||||
stride_weight_channel: fx.Int32,
|
||||
stride_weight_width: fx.Int32,
|
||||
stride_conv_slot: fx.Int32,
|
||||
stride_conv_channel: fx.Int32,
|
||||
stride_conv_width: fx.Int32,
|
||||
stride_g_token: fx.Int32,
|
||||
stride_beta_token: fx.Int32,
|
||||
stride_state_slot: fx.Int32,
|
||||
stride_gate_token: fx.Int32,
|
||||
stride_gate_head: fx.Int32,
|
||||
stride_out_token: fx.Int32,
|
||||
stride_out_head: fx.Int32,
|
||||
):
|
||||
del batch_size
|
||||
|
||||
x = GTensor(x_mem, dtype=T.bf16, shape=(-1,))
|
||||
weight = GTensor(weight_mem, dtype=T.f32, shape=(-1,))
|
||||
conv_state = GTensor(conv_state_mem, dtype=T.bf16, shape=(-1,))
|
||||
raw_g = GTensor(raw_g_mem, dtype=T.bf16, shape=(-1,))
|
||||
raw_beta = GTensor(raw_beta_mem, dtype=T.bf16, shape=(-1,))
|
||||
A_log = GTensor(A_log_mem, dtype=T.f32, shape=(-1,))
|
||||
dt_bias = GTensor(dt_bias_mem, dtype=T.f32, shape=(-1,))
|
||||
state = GTensor(state_mem, dtype=T.f32, shape=(-1,))
|
||||
state_indices = GTensor(state_indices_mem, dtype=T.i32, shape=(-1,))
|
||||
output_gate = GTensor(output_gate_mem, dtype=T.bf16, shape=(-1,))
|
||||
norm_weight = GTensor(norm_weight_mem, dtype=T.bf16, shape=(-1,))
|
||||
out = GTensor(out_mem, dtype=T.bf16, shape=(-1,))
|
||||
|
||||
shared = fx.SharedAllocator().allocate(SharedStorage).peek()
|
||||
q_lds = shared.q.ptr
|
||||
k_lds = shared.k.ptr
|
||||
v_lds = shared.v.ptr
|
||||
out_lds = shared.recurrent_out.ptr
|
||||
norm_lds = shared.norm_partial.ptr
|
||||
|
||||
tid = fx.thread_idx.x
|
||||
block = fx.block_idx.x
|
||||
batch = block // fx.Int32(_HEADS)
|
||||
head = block % fx.Int32(_HEADS)
|
||||
lane = tid % fx.Int32(_WARP_SIZE)
|
||||
warp = tid // fx.Int32(_WARP_SIZE)
|
||||
lane_k = lane % fx.Int32(_WARP_THREADS_K)
|
||||
|
||||
state_idx = fx.Int32(state_indices[batch])
|
||||
valid = state_idx > fx.Int32(0)
|
||||
|
||||
valid_if = scf.IfOp(_to_raw(valid), results_=[], has_else=True)
|
||||
with ir.InsertionPoint(valid_if.then_block):
|
||||
# A workgroup exclusively owns all three convolution channels for
|
||||
# its (batch, head), so every cache entry is shifted exactly once.
|
||||
conv_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(conv_if.then_block):
|
||||
channel_local = tid
|
||||
q_channel = head * fx.Int32(_DIM) + channel_local
|
||||
k_channel = (
|
||||
fx.Int32(_HEADS * _DIM) + head * fx.Int32(_DIM) + channel_local
|
||||
)
|
||||
v_channel = (
|
||||
fx.Int32(2 * _HEADS * _DIM) + head * fx.Int32(_DIM) + channel_local
|
||||
)
|
||||
|
||||
def convolve_channel(channel):
|
||||
cs_base = (
|
||||
state_idx * stride_conv_slot + channel * stride_conv_channel
|
||||
)
|
||||
c0 = fx.Float32(conv_state[cs_base])
|
||||
c1 = fx.Float32(conv_state[cs_base + stride_conv_width])
|
||||
c2 = fx.Float32(
|
||||
conv_state[cs_base + fx.Int32(2) * stride_conv_width]
|
||||
)
|
||||
current = fx.BFloat16(x[batch * stride_x_token + channel])
|
||||
current_f32 = fx.Float32(current)
|
||||
w_base = channel * stride_weight_channel
|
||||
acc = c0 * fx.Float32(weight[w_base])
|
||||
acc = acc + c1 * fx.Float32(weight[w_base + stride_weight_width])
|
||||
acc = acc + c2 * fx.Float32(
|
||||
weight[w_base + fx.Int32(2) * stride_weight_width]
|
||||
)
|
||||
acc = acc + current_f32 * fx.Float32(
|
||||
weight[w_base + fx.Int32(3) * stride_weight_width]
|
||||
)
|
||||
silu = acc / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-acc * fx.Float32(_LOG2E))
|
||||
)
|
||||
conv_state.store(
|
||||
cs_base,
|
||||
fx.BFloat16(c1),
|
||||
)
|
||||
conv_state.store(
|
||||
cs_base + stride_conv_width,
|
||||
fx.BFloat16(c2),
|
||||
)
|
||||
conv_state.store(
|
||||
cs_base + fx.Int32(2) * stride_conv_width,
|
||||
current,
|
||||
)
|
||||
return silu.to(fx.BFloat16)
|
||||
|
||||
q_conv = convolve_channel(q_channel)
|
||||
k_conv = convolve_channel(k_channel)
|
||||
v_conv = convolve_channel(v_channel)
|
||||
fx.ptr_store(q_conv, q_lds + tid)
|
||||
fx.ptr_store(k_conv, k_lds + tid)
|
||||
fx.ptr_store(v_conv, v_lds + tid)
|
||||
scf.YieldOp([])
|
||||
|
||||
fx.gpu.barrier()
|
||||
|
||||
# Four waves split V into 32-row groups. Eight-lane subgroups
|
||||
# reduce K; each lane issues one aligned f32x4 state transaction.
|
||||
k_vec_start = lane_k * fx.Int32(_VALUES_PER_THREAD_K)
|
||||
global_v_start = warp * fx.Int32(_WARP_THREADS_V) + lane // fx.Int32(
|
||||
_WARP_THREADS_K
|
||||
)
|
||||
vec_f32 = T.vec(_VALUES_PER_THREAD_K, T.f32)
|
||||
vec_bf16 = T.vec(_VALUES_PER_THREAD_K, T.bf16)
|
||||
zero_vec = fx.full(
|
||||
_VALUES_PER_THREAD_K,
|
||||
0.0,
|
||||
fx.Float32,
|
||||
)
|
||||
|
||||
q_vecs = []
|
||||
k_vecs = []
|
||||
decay_vecs = []
|
||||
sum_q_partial = fx.Float32(0.0)
|
||||
sum_k_partial = fx.Float32(0.0)
|
||||
a = fx.math.exp2(fx.Float32(A_log[head]) * fx.Float32(_LOG2E))
|
||||
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
k_base = k_vec_start + fx.Int32(ki * _WARP_TILE_K)
|
||||
q_bf16 = fx.ptr_load(
|
||||
q_lds + k_base,
|
||||
result_type=vec_bf16,
|
||||
)
|
||||
k_bf16 = fx.ptr_load(
|
||||
k_lds + k_base,
|
||||
result_type=vec_bf16,
|
||||
)
|
||||
q_f32 = q_bf16.extf(vec_f32)
|
||||
k_f32 = k_bf16.extf(vec_f32)
|
||||
q_vecs.append(q_f32)
|
||||
k_vecs.append(k_f32)
|
||||
sum_q_vec = q_f32 * q_f32
|
||||
sum_k_vec = k_f32 * k_f32
|
||||
sum_q_partial = (
|
||||
sum_q_partial
|
||||
+ mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_q_vec,
|
||||
).dest
|
||||
)
|
||||
sum_k_partial = (
|
||||
sum_k_partial
|
||||
+ mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_k_vec,
|
||||
).dest
|
||||
)
|
||||
|
||||
gate_bf16 = raw_g.vec_load(
|
||||
(batch * stride_g_token + head * fx.Int32(_DIM) + k_base,),
|
||||
_VALUES_PER_THREAD_K,
|
||||
)
|
||||
gate_f32 = gate_bf16.extf(vec_f32)
|
||||
dt = dt_bias.vec_load(
|
||||
(head * fx.Int32(_DIM) + k_base,),
|
||||
_VALUES_PER_THREAD_K,
|
||||
)
|
||||
sigmoid_arg = (gate_f32 + dt) * a
|
||||
gate = fx.Float32(lower_bound) / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-sigmoid_arg * fx.Float32(_LOG2E))
|
||||
)
|
||||
decay_vecs.append(fx.math.exp2(gate * fx.Float32(_LOG2E)))
|
||||
|
||||
width = fx.Int32(_WARP_SIZE)
|
||||
for offset in (1, 2, 4):
|
||||
sum_q_partial = (
|
||||
sum_q_partial
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_q_partial),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
sum_k_partial = (
|
||||
sum_k_partial
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_k_partial),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
|
||||
subgroup_leader = (lane // fx.Int32(_WARP_THREADS_K)) * fx.Int32(
|
||||
_WARP_THREADS_K
|
||||
)
|
||||
norm_q = mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_q_partial),
|
||||
_to_raw(subgroup_leader),
|
||||
_to_raw(width),
|
||||
mode="idx",
|
||||
).shuffleResult
|
||||
norm_k = mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_k_partial),
|
||||
_to_raw(subgroup_leader),
|
||||
_to_raw(width),
|
||||
mode="idx",
|
||||
).shuffleResult
|
||||
inv_q = fx.math.rsqrt(fx.Float32(norm_q) + fx.Float32(1e-6))
|
||||
inv_k = fx.math.rsqrt(fx.Float32(norm_k) + fx.Float32(1e-6))
|
||||
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
q_vecs[ki] = q_vecs[ki] * fx.Float32(inv_q) * fx.Float32(_SCALE)
|
||||
k_vecs[ki] = k_vecs[ki] * fx.Float32(inv_k)
|
||||
|
||||
dot_kq_vec = zero_vec
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
dot_kq_vec = mlir_vector.FMAOp(
|
||||
k_vecs[ki],
|
||||
q_vecs[ki],
|
||||
dot_kq_vec,
|
||||
).result
|
||||
dot_kq = mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
dot_kq_vec,
|
||||
).dest
|
||||
for offset in (1, 2, 4):
|
||||
dot_kq = (
|
||||
dot_kq
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(dot_kq),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
|
||||
beta_value = fx.Float32(raw_beta[batch * stride_beta_token + head])
|
||||
beta = fx.Float32(1.0) / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-beta_value * fx.Float32(_LOG2E))
|
||||
)
|
||||
state_head_base = state_idx * stride_state_slot + head * fx.Int32(
|
||||
_DIM * _DIM
|
||||
)
|
||||
|
||||
state_vecs = []
|
||||
for vi in range_constexpr(_V_ITERS):
|
||||
global_v = global_v_start + fx.Int32(vi * _V_GROUP_TILE)
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
k_base = k_vec_start + fx.Int32(ki * _WARP_TILE_K)
|
||||
state_off = state_head_base + global_v * fx.Int32(_DIM) + k_base
|
||||
state_vecs.append(
|
||||
state.vec_load(
|
||||
(state_off,),
|
||||
_VALUES_PER_THREAD_K,
|
||||
)
|
||||
)
|
||||
|
||||
for vi in range_constexpr(_V_ITERS):
|
||||
global_v = global_v_start + fx.Int32(vi * _V_GROUP_TILE)
|
||||
sum_hk_vec = zero_vec
|
||||
sum_hq_vec = zero_vec
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
state_pos = vi * _K_ITERS + ki
|
||||
decayed = state_vecs[state_pos] * decay_vecs[ki]
|
||||
state_vecs[state_pos] = decayed
|
||||
sum_hk_vec = mlir_vector.FMAOp(
|
||||
decayed,
|
||||
k_vecs[ki],
|
||||
sum_hk_vec,
|
||||
).result
|
||||
sum_hq_vec = mlir_vector.FMAOp(
|
||||
decayed,
|
||||
q_vecs[ki],
|
||||
sum_hq_vec,
|
||||
).result
|
||||
|
||||
sum_hk = mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_hk_vec,
|
||||
).dest
|
||||
sum_hq = mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_hq_vec,
|
||||
).dest
|
||||
for offset in (1, 2, 4):
|
||||
sum_hk = (
|
||||
sum_hk
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_hk),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
sum_hq = (
|
||||
sum_hq
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_hq),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
|
||||
conv_v = fx.Float32(fx.ptr_load(v_lds + global_v))
|
||||
v_new = (conv_v - fx.Float32(sum_hk)) * beta
|
||||
v_new = mlir_gpu.ShuffleOp(
|
||||
_to_raw(v_new),
|
||||
_to_raw(subgroup_leader),
|
||||
_to_raw(width),
|
||||
mode="idx",
|
||||
).shuffleResult
|
||||
recurrent_value = fx.Float32(sum_hq) + fx.Float32(v_new) * fx.Float32(
|
||||
dot_kq
|
||||
)
|
||||
v_new_vec = mlir_vector.BroadcastOp(
|
||||
vec_f32,
|
||||
_to_raw(v_new),
|
||||
).vector
|
||||
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
state_pos = vi * _K_ITERS + ki
|
||||
updated = mlir_vector.FMAOp(
|
||||
k_vecs[ki],
|
||||
v_new_vec,
|
||||
state_vecs[state_pos],
|
||||
).result
|
||||
k_base = k_vec_start + fx.Int32(ki * _WARP_TILE_K)
|
||||
state_off = state_head_base + global_v * fx.Int32(_DIM) + k_base
|
||||
state.vec_store(
|
||||
(state_off,),
|
||||
updated,
|
||||
_VALUES_PER_THREAD_K,
|
||||
)
|
||||
|
||||
if lane_k == fx.Int32(0):
|
||||
fx.ptr_store(
|
||||
fx.BFloat16(recurrent_value),
|
||||
out_lds + global_v,
|
||||
)
|
||||
|
||||
fx.gpu.barrier()
|
||||
|
||||
# Preserve the model's BF16 boundary before RMSNorm and gating.
|
||||
output_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(output_if.then_block):
|
||||
recurrent_bf16 = fx.ptr_load(out_lds + tid)
|
||||
recurrent_f32 = fx.Float32(recurrent_bf16)
|
||||
square = recurrent_f32 * recurrent_f32
|
||||
for offset in (32, 16, 8, 4, 2, 1):
|
||||
square = (
|
||||
square
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(square),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
if lane == fx.Int32(0):
|
||||
fx.ptr_store(square, norm_lds + warp)
|
||||
scf.YieldOp([])
|
||||
|
||||
fx.gpu.barrier()
|
||||
|
||||
output_store_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(output_store_if.then_block):
|
||||
norm_sum = fx.Float32(fx.ptr_load(norm_lds))
|
||||
norm_sum = norm_sum + fx.Float32(fx.ptr_load(norm_lds + fx.Int32(1)))
|
||||
inv_rms = fx.math.rsqrt(
|
||||
norm_sum * fx.Float32(1.0 / _DIM) + fx.Float32(norm_eps)
|
||||
)
|
||||
recurrent_f32 = fx.Float32(fx.ptr_load(out_lds + tid))
|
||||
norm_w = fx.Float32(norm_weight[tid])
|
||||
gate_value = fx.Float32(
|
||||
output_gate[
|
||||
batch * stride_gate_token + head * stride_gate_head + tid
|
||||
]
|
||||
)
|
||||
output_sigmoid = fx.Float32(1.0) / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-gate_value * fx.Float32(_LOG2E))
|
||||
)
|
||||
result = recurrent_f32 * inv_rms * norm_w * output_sigmoid
|
||||
out.store(
|
||||
batch * stride_out_token + head * stride_out_head + tid,
|
||||
result.to(fx.BFloat16),
|
||||
)
|
||||
scf.YieldOp([])
|
||||
scf.YieldOp([])
|
||||
with ir.InsertionPoint(valid_if.else_block):
|
||||
zero_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(zero_if.then_block):
|
||||
out.store(
|
||||
batch * stride_out_token + head * stride_out_head + tid,
|
||||
fx.BFloat16(0.0),
|
||||
)
|
||||
scf.YieldOp([])
|
||||
scf.YieldOp([])
|
||||
|
||||
@flyc.jit
|
||||
def launch(
|
||||
x_mem: fx.Tensor,
|
||||
weight_mem: fx.Tensor,
|
||||
conv_state_mem: fx.Tensor,
|
||||
raw_g_mem: fx.Tensor,
|
||||
raw_beta_mem: fx.Tensor,
|
||||
A_log_mem: fx.Tensor,
|
||||
dt_bias_mem: fx.Tensor,
|
||||
state_mem: fx.Tensor,
|
||||
state_indices_mem: fx.Tensor,
|
||||
output_gate_mem: fx.Tensor,
|
||||
norm_weight_mem: fx.Tensor,
|
||||
out_mem: fx.Tensor,
|
||||
batch_size: fx.Int32,
|
||||
stride_x_token: fx.Int32,
|
||||
stride_weight_channel: fx.Int32,
|
||||
stride_weight_width: fx.Int32,
|
||||
stride_conv_slot: fx.Int32,
|
||||
stride_conv_channel: fx.Int32,
|
||||
stride_conv_width: fx.Int32,
|
||||
stride_g_token: fx.Int32,
|
||||
stride_beta_token: fx.Int32,
|
||||
stride_state_slot: fx.Int32,
|
||||
stride_gate_token: fx.Int32,
|
||||
stride_gate_head: fx.Int32,
|
||||
stride_out_token: fx.Int32,
|
||||
stride_out_head: fx.Int32,
|
||||
stream: fx.Stream = fx.Stream(None), # noqa: B008
|
||||
):
|
||||
kernel(
|
||||
x_mem,
|
||||
weight_mem,
|
||||
conv_state_mem,
|
||||
raw_g_mem,
|
||||
raw_beta_mem,
|
||||
A_log_mem,
|
||||
dt_bias_mem,
|
||||
state_mem,
|
||||
state_indices_mem,
|
||||
output_gate_mem,
|
||||
norm_weight_mem,
|
||||
out_mem,
|
||||
batch_size,
|
||||
stride_x_token,
|
||||
stride_weight_channel,
|
||||
stride_weight_width,
|
||||
stride_conv_slot,
|
||||
stride_conv_channel,
|
||||
stride_conv_width,
|
||||
stride_g_token,
|
||||
stride_beta_token,
|
||||
stride_state_slot,
|
||||
stride_gate_token,
|
||||
stride_gate_head,
|
||||
stride_out_token,
|
||||
stride_out_head,
|
||||
).launch(
|
||||
grid=(batch_size * fx.Int32(_HEADS), 1, 1),
|
||||
block=(_BLOCK_THREADS, 1, 1),
|
||||
stream=stream,
|
||||
)
|
||||
|
||||
launch.compile_hints = {
|
||||
"waves_per_eu": _WAVES_PER_EU,
|
||||
"llvm_options": {
|
||||
"amdgpu-expert-scheduling-mode": True,
|
||||
},
|
||||
}
|
||||
return launch
|
||||
|
||||
|
||||
__all__ = ["create_kimi_k3_kda_decode_kernel"]
|
||||
@@ -0,0 +1,779 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
|
||||
|
||||
"""FlyDSL Kimi-K3 KDA decode with a fused head-local f_b projection."""
|
||||
|
||||
import functools
|
||||
import math
|
||||
|
||||
import flydsl.compiler as flyc
|
||||
import flydsl.expr as fx
|
||||
from aiter.ops.flydsl.kernels import vector
|
||||
from aiter.ops.flydsl.kernels.tensor_shim import (
|
||||
AITER_FLYDSL_KERNARG_PRELOAD,
|
||||
AITER_FLYDSL_KERNARG_PRELOAD_COUNT,
|
||||
GTensor,
|
||||
_to_raw,
|
||||
)
|
||||
from flydsl._mlir import ir
|
||||
from flydsl._mlir.dialects import gpu as mlir_gpu
|
||||
from flydsl._mlir.dialects import llvm, scf
|
||||
from flydsl._mlir.dialects import vector as mlir_vector
|
||||
from flydsl.expr import arith, const_expr, range_constexpr
|
||||
from flydsl.expr.arith import ArithValue
|
||||
from flydsl.expr.typing import T
|
||||
|
||||
_HEADS = 12
|
||||
_DIM = 128
|
||||
_LOG2E = math.log2(math.e)
|
||||
_SCALE = _DIM**-0.5
|
||||
_BLOCK_THREADS = 256
|
||||
_NUM_WARPS = 4
|
||||
_WARP_SIZE = 64
|
||||
_WARP_THREADS_K = 8
|
||||
_VALUES_PER_THREAD_K = 4
|
||||
_WARP_TILE_K = _WARP_THREADS_K * _VALUES_PER_THREAD_K
|
||||
_K_ITERS = _DIM // _WARP_TILE_K
|
||||
_WARP_THREADS_V = _WARP_SIZE // _WARP_THREADS_K
|
||||
_V_GROUP_TILE = _NUM_WARPS * _WARP_THREADS_V
|
||||
_V_ITERS = _DIM // _V_GROUP_TILE
|
||||
_PROJECTION_VECTOR = 4
|
||||
_PROJECTION_ITERS = _DIM // _PROJECTION_VECTOR
|
||||
_DEFAULT_WAVES_PER_EU = 2
|
||||
|
||||
|
||||
@functools.cache
|
||||
def create_kimi_k3_kda_decode_fb_kernel(
|
||||
norm_eps: float,
|
||||
lower_bound: float,
|
||||
*,
|
||||
waves_per_eu: int = _DEFAULT_WAVES_PER_EU,
|
||||
cooperative_f_a: bool = False,
|
||||
parallel_front: bool = False,
|
||||
fused_norm_reduce: bool = False,
|
||||
projection_fdot2: bool = False,
|
||||
):
|
||||
"""Build the fixed gfx950 BF16 f_b plus KDA decode specialization."""
|
||||
conv_tid_offset = _DIM if parallel_front else 0
|
||||
conv_tid_upper = 2 * _DIM if parallel_front else _DIM
|
||||
|
||||
if cooperative_f_a:
|
||||
|
||||
@fx.struct
|
||||
class SharedStorage:
|
||||
f_a: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
q: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
k: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
v: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
gate: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
recurrent_out: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
norm_partial: fx.Array[fx.Float32, 4, 16]
|
||||
|
||||
else:
|
||||
|
||||
@fx.struct
|
||||
class SharedStorage:
|
||||
q: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
k: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
v: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
gate: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
recurrent_out: fx.Array[fx.BFloat16, _DIM, 16]
|
||||
norm_partial: fx.Array[fx.Float32, 4, 16]
|
||||
|
||||
kernel_name = "kimi_k3_kda_decode_fb_bf16_gfx950"
|
||||
if (
|
||||
cooperative_f_a
|
||||
or parallel_front
|
||||
or fused_norm_reduce
|
||||
or projection_fdot2
|
||||
or waves_per_eu != _DEFAULT_WAVES_PER_EU
|
||||
):
|
||||
kernel_name += (
|
||||
f"_wpe{waves_per_eu}_cfa{int(cooperative_f_a)}"
|
||||
f"_pf{int(parallel_front)}"
|
||||
f"_fnr{int(fused_norm_reduce)}"
|
||||
f"_fd2{int(projection_fdot2)}"
|
||||
)
|
||||
|
||||
@flyc.kernel(
|
||||
name=kernel_name,
|
||||
known_block_size=[_BLOCK_THREADS, 1, 1],
|
||||
)
|
||||
def kernel(
|
||||
f_a_mem: fx.Tensor,
|
||||
f_b_weight_mem: fx.Tensor,
|
||||
x_mem: fx.Tensor,
|
||||
weight_mem: fx.Tensor,
|
||||
conv_state_mem: fx.Tensor,
|
||||
raw_beta_mem: fx.Tensor,
|
||||
A_log_mem: fx.Tensor,
|
||||
dt_bias_mem: fx.Tensor,
|
||||
state_mem: fx.Tensor,
|
||||
state_indices_mem: fx.Tensor,
|
||||
output_gate_mem: fx.Tensor,
|
||||
norm_weight_mem: fx.Tensor,
|
||||
out_mem: fx.Tensor,
|
||||
batch_size: fx.Int32,
|
||||
stride_f_a_token: fx.Int32,
|
||||
stride_f_b_head: fx.Int32,
|
||||
stride_f_b_output: fx.Int32,
|
||||
stride_x_token: fx.Int32,
|
||||
stride_weight_channel: fx.Int32,
|
||||
stride_weight_width: fx.Int32,
|
||||
stride_conv_slot: fx.Int32,
|
||||
stride_conv_channel: fx.Int32,
|
||||
stride_conv_width: fx.Int32,
|
||||
stride_beta_token: fx.Int32,
|
||||
stride_state_slot: fx.Int32,
|
||||
stride_gate_token: fx.Int32,
|
||||
stride_gate_head: fx.Int32,
|
||||
stride_out_token: fx.Int32,
|
||||
stride_out_head: fx.Int32,
|
||||
):
|
||||
del batch_size
|
||||
|
||||
f_a = GTensor(f_a_mem, dtype=T.bf16, shape=(-1,))
|
||||
f_b_weight = GTensor(f_b_weight_mem, dtype=T.bf16, shape=(-1,))
|
||||
x = GTensor(x_mem, dtype=T.bf16, shape=(-1,))
|
||||
weight = GTensor(weight_mem, dtype=T.f32, shape=(-1,))
|
||||
conv_state = GTensor(conv_state_mem, dtype=T.bf16, shape=(-1,))
|
||||
raw_beta = GTensor(raw_beta_mem, dtype=T.bf16, shape=(-1,))
|
||||
A_log = GTensor(A_log_mem, dtype=T.f32, shape=(-1,))
|
||||
dt_bias = GTensor(dt_bias_mem, dtype=T.f32, shape=(-1,))
|
||||
state = GTensor(state_mem, dtype=T.f32, shape=(-1,))
|
||||
state_indices = GTensor(state_indices_mem, dtype=T.i32, shape=(-1,))
|
||||
output_gate = GTensor(output_gate_mem, dtype=T.bf16, shape=(-1,))
|
||||
norm_weight = GTensor(norm_weight_mem, dtype=T.bf16, shape=(-1,))
|
||||
out = GTensor(out_mem, dtype=T.bf16, shape=(-1,))
|
||||
|
||||
shared = fx.SharedAllocator().allocate(SharedStorage).peek()
|
||||
f_a_lds = shared.f_a.ptr if cooperative_f_a else shared.q.ptr
|
||||
q_lds = shared.q.ptr
|
||||
k_lds = shared.k.ptr
|
||||
v_lds = shared.v.ptr
|
||||
gate_lds = shared.gate.ptr
|
||||
out_lds = shared.recurrent_out.ptr
|
||||
norm_lds = shared.norm_partial.ptr
|
||||
|
||||
tid = fx.thread_idx.x
|
||||
block = fx.block_idx.x
|
||||
batch = block // fx.Int32(_HEADS)
|
||||
head = block % fx.Int32(_HEADS)
|
||||
lane = tid % fx.Int32(_WARP_SIZE)
|
||||
warp = tid // fx.Int32(_WARP_SIZE)
|
||||
lane_k = lane % fx.Int32(_WARP_THREADS_K)
|
||||
|
||||
state_idx = fx.Int32(state_indices[batch])
|
||||
valid = state_idx > fx.Int32(0)
|
||||
|
||||
valid_if = scf.IfOp(_to_raw(valid), results_=[], has_else=True)
|
||||
with ir.InsertionPoint(valid_if.then_block):
|
||||
if const_expr(cooperative_f_a):
|
||||
f_a_load_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(f_a_load_if.then_block):
|
||||
fx.ptr_store(
|
||||
fx.BFloat16(f_a[batch * stride_f_a_token + tid]),
|
||||
f_a_lds + tid,
|
||||
)
|
||||
scf.YieldOp([])
|
||||
fx.gpu.barrier()
|
||||
|
||||
# Threads 0..127 own one output each. Accumulation is FP32 and the
|
||||
# single BF16 store is the same numerical boundary as F.linear.
|
||||
projection_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(projection_if.then_block):
|
||||
i1 = ir.IntegerType.get_signless(1)
|
||||
vec_f32_projection = T.vec(_PROJECTION_VECTOR, T.f32)
|
||||
vec2_bf16 = T.vec(2, T.bf16)
|
||||
accum = fx.full(
|
||||
_PROJECTION_VECTOR,
|
||||
0.0,
|
||||
fx.Float32,
|
||||
)
|
||||
local_dot = fx.Float32(0.0)
|
||||
f_a_base = batch * stride_f_a_token
|
||||
f_b_base = head * stride_f_b_head + tid * stride_f_b_output
|
||||
for projection_iter in range_constexpr(_PROJECTION_ITERS):
|
||||
projection_offset = fx.Int32(projection_iter * _PROJECTION_VECTOR)
|
||||
if const_expr(cooperative_f_a):
|
||||
f_a_values = fx.ptr_load(
|
||||
f_a_lds + projection_offset,
|
||||
result_type=T.vec(_PROJECTION_VECTOR, T.bf16),
|
||||
).extf(vec_f32_projection)
|
||||
else:
|
||||
f_a_values = f_a.vec_load(
|
||||
(f_a_base + projection_offset,),
|
||||
_PROJECTION_VECTOR,
|
||||
).extf(vec_f32_projection)
|
||||
weight_values = f_b_weight.vec_load(
|
||||
(f_b_base + projection_offset,),
|
||||
_PROJECTION_VECTOR,
|
||||
).extf(vec_f32_projection)
|
||||
if const_expr(projection_fdot2):
|
||||
f_a_bf16 = f_a_values.truncf(T.vec(_PROJECTION_VECTOR, T.bf16))
|
||||
weight_bf16 = weight_values.truncf(
|
||||
T.vec(_PROJECTION_VECTOR, T.bf16)
|
||||
)
|
||||
for pair_index in range_constexpr(_PROJECTION_VECTOR // 2):
|
||||
f_a_pair = vector.from_elements(
|
||||
vec2_bf16,
|
||||
[
|
||||
vector.extract(
|
||||
f_a_bf16,
|
||||
static_position=[pair_index * 2],
|
||||
dynamic_position=[],
|
||||
),
|
||||
vector.extract(
|
||||
f_a_bf16,
|
||||
static_position=[pair_index * 2 + 1],
|
||||
dynamic_position=[],
|
||||
),
|
||||
],
|
||||
)
|
||||
weight_pair = vector.from_elements(
|
||||
vec2_bf16,
|
||||
[
|
||||
vector.extract(
|
||||
weight_bf16,
|
||||
static_position=[pair_index * 2],
|
||||
dynamic_position=[],
|
||||
),
|
||||
vector.extract(
|
||||
weight_bf16,
|
||||
static_position=[pair_index * 2 + 1],
|
||||
dynamic_position=[],
|
||||
),
|
||||
],
|
||||
)
|
||||
local_dot = ArithValue(
|
||||
llvm.call_intrinsic(
|
||||
T.f32,
|
||||
"llvm.amdgcn.fdot2.f32.bf16",
|
||||
[
|
||||
f_a_pair,
|
||||
weight_pair,
|
||||
_to_raw(local_dot),
|
||||
arith.constant(False, type=i1),
|
||||
],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
)
|
||||
else:
|
||||
accum = mlir_vector.FMAOp(
|
||||
f_a_values,
|
||||
weight_values,
|
||||
accum,
|
||||
).result
|
||||
if const_expr(projection_fdot2):
|
||||
projected = local_dot
|
||||
else:
|
||||
projected = mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
accum,
|
||||
).dest
|
||||
fx.ptr_store(
|
||||
fx.BFloat16(projected),
|
||||
gate_lds + tid,
|
||||
)
|
||||
scf.YieldOp([])
|
||||
|
||||
# A workgroup exclusively owns all three convolution channels for
|
||||
# its (batch, head), so every cache entry is shifted exactly once.
|
||||
conv_if = scf.IfOp(
|
||||
_to_raw(
|
||||
(tid >= fx.Int32(conv_tid_offset))
|
||||
& (tid < fx.Int32(conv_tid_upper))
|
||||
),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(conv_if.then_block):
|
||||
channel_local = tid - fx.Int32(conv_tid_offset)
|
||||
q_channel = head * fx.Int32(_DIM) + channel_local
|
||||
k_channel = (
|
||||
fx.Int32(_HEADS * _DIM) + head * fx.Int32(_DIM) + channel_local
|
||||
)
|
||||
v_channel = (
|
||||
fx.Int32(2 * _HEADS * _DIM) + head * fx.Int32(_DIM) + channel_local
|
||||
)
|
||||
|
||||
def convolve_channel(channel):
|
||||
cs_base = (
|
||||
state_idx * stride_conv_slot + channel * stride_conv_channel
|
||||
)
|
||||
c0 = fx.Float32(conv_state[cs_base])
|
||||
c1 = fx.Float32(conv_state[cs_base + stride_conv_width])
|
||||
c2 = fx.Float32(
|
||||
conv_state[cs_base + fx.Int32(2) * stride_conv_width]
|
||||
)
|
||||
current = fx.BFloat16(x[batch * stride_x_token + channel])
|
||||
current_f32 = fx.Float32(current)
|
||||
w_base = channel * stride_weight_channel
|
||||
acc = c0 * fx.Float32(weight[w_base])
|
||||
acc = acc + c1 * fx.Float32(weight[w_base + stride_weight_width])
|
||||
acc = acc + c2 * fx.Float32(
|
||||
weight[w_base + fx.Int32(2) * stride_weight_width]
|
||||
)
|
||||
acc = acc + current_f32 * fx.Float32(
|
||||
weight[w_base + fx.Int32(3) * stride_weight_width]
|
||||
)
|
||||
silu = acc / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-acc * fx.Float32(_LOG2E))
|
||||
)
|
||||
conv_state.store(
|
||||
cs_base,
|
||||
fx.BFloat16(c1),
|
||||
)
|
||||
conv_state.store(
|
||||
cs_base + stride_conv_width,
|
||||
fx.BFloat16(c2),
|
||||
)
|
||||
conv_state.store(
|
||||
cs_base + fx.Int32(2) * stride_conv_width,
|
||||
current,
|
||||
)
|
||||
return silu.to(fx.BFloat16)
|
||||
|
||||
q_conv = convolve_channel(q_channel)
|
||||
k_conv = convolve_channel(k_channel)
|
||||
v_conv = convolve_channel(v_channel)
|
||||
fx.ptr_store(q_conv, q_lds + channel_local)
|
||||
fx.ptr_store(k_conv, k_lds + channel_local)
|
||||
fx.ptr_store(v_conv, v_lds + channel_local)
|
||||
scf.YieldOp([])
|
||||
|
||||
# Both projection and convolution LDS values must be visible
|
||||
# before the recurrent core begins.
|
||||
fx.gpu.barrier()
|
||||
|
||||
# Four waves split V into 32-row groups. Eight-lane subgroups
|
||||
# reduce K; each lane issues one aligned f32x4 state transaction.
|
||||
k_vec_start = lane_k * fx.Int32(_VALUES_PER_THREAD_K)
|
||||
global_v_start = warp * fx.Int32(_WARP_THREADS_V) + lane // fx.Int32(
|
||||
_WARP_THREADS_K
|
||||
)
|
||||
vec_f32 = T.vec(_VALUES_PER_THREAD_K, T.f32)
|
||||
vec_bf16 = T.vec(_VALUES_PER_THREAD_K, T.bf16)
|
||||
zero_vec = fx.full(
|
||||
_VALUES_PER_THREAD_K,
|
||||
0.0,
|
||||
fx.Float32,
|
||||
)
|
||||
|
||||
q_vecs = []
|
||||
k_vecs = []
|
||||
decay_vecs = []
|
||||
sum_q_partial = fx.Float32(0.0)
|
||||
sum_k_partial = fx.Float32(0.0)
|
||||
a = fx.math.exp2(fx.Float32(A_log[head]) * fx.Float32(_LOG2E))
|
||||
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
k_base = k_vec_start + fx.Int32(ki * _WARP_TILE_K)
|
||||
q_bf16 = fx.ptr_load(
|
||||
q_lds + k_base,
|
||||
result_type=vec_bf16,
|
||||
)
|
||||
k_bf16 = fx.ptr_load(
|
||||
k_lds + k_base,
|
||||
result_type=vec_bf16,
|
||||
)
|
||||
q_f32 = q_bf16.extf(vec_f32)
|
||||
k_f32 = k_bf16.extf(vec_f32)
|
||||
q_vecs.append(q_f32)
|
||||
k_vecs.append(k_f32)
|
||||
sum_q_vec = q_f32 * q_f32
|
||||
sum_k_vec = k_f32 * k_f32
|
||||
sum_q_partial = (
|
||||
sum_q_partial
|
||||
+ mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_q_vec,
|
||||
).dest
|
||||
)
|
||||
sum_k_partial = (
|
||||
sum_k_partial
|
||||
+ mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_k_vec,
|
||||
).dest
|
||||
)
|
||||
|
||||
# The projection is rounded in LDS before the lower-bound gate.
|
||||
gate_bf16 = fx.ptr_load(
|
||||
gate_lds + k_base,
|
||||
result_type=vec_bf16,
|
||||
)
|
||||
gate_f32 = gate_bf16.extf(vec_f32)
|
||||
dt = dt_bias.vec_load(
|
||||
(head * fx.Int32(_DIM) + k_base,),
|
||||
_VALUES_PER_THREAD_K,
|
||||
)
|
||||
sigmoid_arg = (gate_f32 + dt) * a
|
||||
gate = fx.Float32(lower_bound) / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-sigmoid_arg * fx.Float32(_LOG2E))
|
||||
)
|
||||
decay_vecs.append(fx.math.exp2(gate * fx.Float32(_LOG2E)))
|
||||
|
||||
width = fx.Int32(_WARP_SIZE)
|
||||
for offset in (1, 2, 4):
|
||||
sum_q_partial = (
|
||||
sum_q_partial
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_q_partial),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
sum_k_partial = (
|
||||
sum_k_partial
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_k_partial),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
|
||||
subgroup_leader = (lane // fx.Int32(_WARP_THREADS_K)) * fx.Int32(
|
||||
_WARP_THREADS_K
|
||||
)
|
||||
norm_q = mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_q_partial),
|
||||
_to_raw(subgroup_leader),
|
||||
_to_raw(width),
|
||||
mode="idx",
|
||||
).shuffleResult
|
||||
norm_k = mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_k_partial),
|
||||
_to_raw(subgroup_leader),
|
||||
_to_raw(width),
|
||||
mode="idx",
|
||||
).shuffleResult
|
||||
inv_q = fx.math.rsqrt(fx.Float32(norm_q) + fx.Float32(1e-6))
|
||||
inv_k = fx.math.rsqrt(fx.Float32(norm_k) + fx.Float32(1e-6))
|
||||
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
q_vecs[ki] = q_vecs[ki] * fx.Float32(inv_q) * fx.Float32(_SCALE)
|
||||
k_vecs[ki] = k_vecs[ki] * fx.Float32(inv_k)
|
||||
|
||||
dot_kq_vec = zero_vec
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
dot_kq_vec = mlir_vector.FMAOp(
|
||||
k_vecs[ki],
|
||||
q_vecs[ki],
|
||||
dot_kq_vec,
|
||||
).result
|
||||
dot_kq = mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
dot_kq_vec,
|
||||
).dest
|
||||
for offset in (1, 2, 4):
|
||||
dot_kq = (
|
||||
dot_kq
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(dot_kq),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
|
||||
beta_value = fx.Float32(raw_beta[batch * stride_beta_token + head])
|
||||
beta = fx.Float32(1.0) / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-beta_value * fx.Float32(_LOG2E))
|
||||
)
|
||||
state_head_base = state_idx * stride_state_slot + head * fx.Int32(
|
||||
_DIM * _DIM
|
||||
)
|
||||
|
||||
def process_state_row(vi, row_state_vecs):
|
||||
global_v = global_v_start + fx.Int32(vi * _V_GROUP_TILE)
|
||||
sum_hk_vec = zero_vec
|
||||
sum_hq_vec = zero_vec
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
decayed = row_state_vecs[ki] * decay_vecs[ki]
|
||||
row_state_vecs[ki] = decayed
|
||||
sum_hk_vec = mlir_vector.FMAOp(
|
||||
decayed,
|
||||
k_vecs[ki],
|
||||
sum_hk_vec,
|
||||
).result
|
||||
sum_hq_vec = mlir_vector.FMAOp(
|
||||
decayed,
|
||||
q_vecs[ki],
|
||||
sum_hq_vec,
|
||||
).result
|
||||
|
||||
sum_hk = mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_hk_vec,
|
||||
).dest
|
||||
sum_hq = mlir_vector.ReductionOp(
|
||||
T.f32,
|
||||
vector.CombiningKind.ADD,
|
||||
sum_hq_vec,
|
||||
).dest
|
||||
for offset in (1, 2, 4):
|
||||
sum_hk = (
|
||||
sum_hk
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_hk),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
sum_hq = (
|
||||
sum_hq
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(sum_hq),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
|
||||
conv_v = fx.Float32(fx.ptr_load(v_lds + global_v))
|
||||
v_new = (conv_v - fx.Float32(sum_hk)) * beta
|
||||
v_new = mlir_gpu.ShuffleOp(
|
||||
_to_raw(v_new),
|
||||
_to_raw(subgroup_leader),
|
||||
_to_raw(width),
|
||||
mode="idx",
|
||||
).shuffleResult
|
||||
recurrent_value = fx.Float32(sum_hq) + fx.Float32(v_new) * fx.Float32(
|
||||
dot_kq
|
||||
)
|
||||
v_new_vec = mlir_vector.BroadcastOp(
|
||||
vec_f32,
|
||||
_to_raw(v_new),
|
||||
).vector
|
||||
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
updated = mlir_vector.FMAOp(
|
||||
k_vecs[ki],
|
||||
v_new_vec,
|
||||
row_state_vecs[ki],
|
||||
).result
|
||||
k_base = k_vec_start + fx.Int32(ki * _WARP_TILE_K)
|
||||
state_off = state_head_base + global_v * fx.Int32(_DIM) + k_base
|
||||
state.vec_store(
|
||||
(state_off,),
|
||||
updated,
|
||||
_VALUES_PER_THREAD_K,
|
||||
)
|
||||
|
||||
if lane_k == fx.Int32(0):
|
||||
fx.ptr_store(
|
||||
fx.BFloat16(recurrent_value),
|
||||
out_lds + global_v,
|
||||
)
|
||||
rounded = fx.BFloat16(recurrent_value)
|
||||
rounded_f32 = fx.Float32(rounded)
|
||||
return rounded_f32 * rounded_f32
|
||||
|
||||
norm_accum = fx.Float32(0.0)
|
||||
state_vecs = []
|
||||
for vi in range_constexpr(_V_ITERS):
|
||||
global_v = global_v_start + fx.Int32(vi * _V_GROUP_TILE)
|
||||
for ki in range_constexpr(_K_ITERS):
|
||||
k_base = k_vec_start + fx.Int32(ki * _WARP_TILE_K)
|
||||
state_off = state_head_base + global_v * fx.Int32(_DIM) + k_base
|
||||
state_vecs.append(state.vec_load((state_off,), 4))
|
||||
for vi in range_constexpr(_V_ITERS):
|
||||
norm_accum = norm_accum + process_state_row(
|
||||
vi,
|
||||
state_vecs[vi * _K_ITERS : (vi + 1) * _K_ITERS],
|
||||
)
|
||||
|
||||
if const_expr(fused_norm_reduce):
|
||||
for offset in (32, 16, 8, 4, 2, 1):
|
||||
norm_accum = (
|
||||
norm_accum
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(norm_accum),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
if lane == fx.Int32(0):
|
||||
fx.ptr_store(
|
||||
norm_accum * fx.Float32(1.0 / _WARP_THREADS_K),
|
||||
norm_lds + warp,
|
||||
)
|
||||
|
||||
fx.gpu.barrier()
|
||||
|
||||
# Preserve the model's BF16 boundary before RMSNorm and gating.
|
||||
if const_expr(not fused_norm_reduce):
|
||||
output_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(output_if.then_block):
|
||||
recurrent_bf16 = fx.ptr_load(out_lds + tid)
|
||||
recurrent_f32 = fx.Float32(recurrent_bf16)
|
||||
square = recurrent_f32 * recurrent_f32
|
||||
for offset in (32, 16, 8, 4, 2, 1):
|
||||
square = (
|
||||
square
|
||||
+ mlir_gpu.ShuffleOp(
|
||||
_to_raw(square),
|
||||
_to_raw(fx.Int32(offset)),
|
||||
_to_raw(width),
|
||||
mode="xor",
|
||||
).shuffleResult
|
||||
)
|
||||
if lane == fx.Int32(0):
|
||||
fx.ptr_store(square, norm_lds + warp)
|
||||
scf.YieldOp([])
|
||||
|
||||
fx.gpu.barrier()
|
||||
|
||||
output_store_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(output_store_if.then_block):
|
||||
norm_sum = fx.Float32(fx.ptr_load(norm_lds))
|
||||
norm_sum = norm_sum + fx.Float32(fx.ptr_load(norm_lds + fx.Int32(1)))
|
||||
if const_expr(fused_norm_reduce):
|
||||
norm_sum = norm_sum + fx.Float32(
|
||||
fx.ptr_load(norm_lds + fx.Int32(2))
|
||||
)
|
||||
norm_sum = norm_sum + fx.Float32(
|
||||
fx.ptr_load(norm_lds + fx.Int32(3))
|
||||
)
|
||||
inv_rms = fx.math.rsqrt(
|
||||
norm_sum * fx.Float32(1.0 / _DIM) + fx.Float32(norm_eps)
|
||||
)
|
||||
recurrent_f32 = fx.Float32(fx.ptr_load(out_lds + tid))
|
||||
norm_w = fx.Float32(norm_weight[tid])
|
||||
gate_value = fx.Float32(
|
||||
output_gate[
|
||||
batch * stride_gate_token + head * stride_gate_head + tid
|
||||
]
|
||||
)
|
||||
output_sigmoid = fx.Float32(1.0) / (
|
||||
fx.Float32(1.0) + fx.math.exp2(-gate_value * fx.Float32(_LOG2E))
|
||||
)
|
||||
result = recurrent_f32 * inv_rms * norm_w * output_sigmoid
|
||||
out.store(
|
||||
batch * stride_out_token + head * stride_out_head + tid,
|
||||
result.to(fx.BFloat16),
|
||||
)
|
||||
scf.YieldOp([])
|
||||
scf.YieldOp([])
|
||||
with ir.InsertionPoint(valid_if.else_block):
|
||||
zero_if = scf.IfOp(
|
||||
_to_raw(tid < fx.Int32(_DIM)),
|
||||
results_=[],
|
||||
has_else=False,
|
||||
)
|
||||
with ir.InsertionPoint(zero_if.then_block):
|
||||
out.store(
|
||||
batch * stride_out_token + head * stride_out_head + tid,
|
||||
fx.BFloat16(0.0),
|
||||
)
|
||||
scf.YieldOp([])
|
||||
scf.YieldOp([])
|
||||
|
||||
@flyc.jit
|
||||
def launch(
|
||||
f_a_mem: fx.Tensor,
|
||||
f_b_weight_mem: fx.Tensor,
|
||||
x_mem: fx.Tensor,
|
||||
weight_mem: fx.Tensor,
|
||||
conv_state_mem: fx.Tensor,
|
||||
raw_beta_mem: fx.Tensor,
|
||||
A_log_mem: fx.Tensor,
|
||||
dt_bias_mem: fx.Tensor,
|
||||
state_mem: fx.Tensor,
|
||||
state_indices_mem: fx.Tensor,
|
||||
output_gate_mem: fx.Tensor,
|
||||
norm_weight_mem: fx.Tensor,
|
||||
out_mem: fx.Tensor,
|
||||
batch_size: fx.Int32,
|
||||
stride_f_a_token: fx.Int32,
|
||||
stride_f_b_head: fx.Int32,
|
||||
stride_f_b_output: fx.Int32,
|
||||
stride_x_token: fx.Int32,
|
||||
stride_weight_channel: fx.Int32,
|
||||
stride_weight_width: fx.Int32,
|
||||
stride_conv_slot: fx.Int32,
|
||||
stride_conv_channel: fx.Int32,
|
||||
stride_conv_width: fx.Int32,
|
||||
stride_beta_token: fx.Int32,
|
||||
stride_state_slot: fx.Int32,
|
||||
stride_gate_token: fx.Int32,
|
||||
stride_gate_head: fx.Int32,
|
||||
stride_out_token: fx.Int32,
|
||||
stride_out_head: fx.Int32,
|
||||
stream: fx.Stream = fx.Stream(None), # noqa: B008
|
||||
):
|
||||
kernel(
|
||||
f_a_mem,
|
||||
f_b_weight_mem,
|
||||
x_mem,
|
||||
weight_mem,
|
||||
conv_state_mem,
|
||||
raw_beta_mem,
|
||||
A_log_mem,
|
||||
dt_bias_mem,
|
||||
state_mem,
|
||||
state_indices_mem,
|
||||
output_gate_mem,
|
||||
norm_weight_mem,
|
||||
out_mem,
|
||||
batch_size,
|
||||
stride_f_a_token,
|
||||
stride_f_b_head,
|
||||
stride_f_b_output,
|
||||
stride_x_token,
|
||||
stride_weight_channel,
|
||||
stride_weight_width,
|
||||
stride_conv_slot,
|
||||
stride_conv_channel,
|
||||
stride_conv_width,
|
||||
stride_beta_token,
|
||||
stride_state_slot,
|
||||
stride_gate_token,
|
||||
stride_gate_head,
|
||||
stride_out_token,
|
||||
stride_out_head,
|
||||
).launch(
|
||||
grid=(batch_size * fx.Int32(_HEADS), 1, 1),
|
||||
block=(_BLOCK_THREADS, 1, 1),
|
||||
stream=stream,
|
||||
)
|
||||
|
||||
launch.compile_hints = {
|
||||
"waves_per_eu": waves_per_eu,
|
||||
"llvm_options": {
|
||||
"amdgpu-expert-scheduling-mode": True,
|
||||
"amdgpu-kernarg-preload": AITER_FLYDSL_KERNARG_PRELOAD,
|
||||
"amdgpu-kernarg-preload-count": AITER_FLYDSL_KERNARG_PRELOAD_COUNT,
|
||||
},
|
||||
}
|
||||
return launch
|
||||
|
||||
|
||||
__all__ = ["create_kimi_k3_kda_decode_fb_kernel"]
|
||||
@@ -0,0 +1,477 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
|
||||
|
||||
"""High-level API for the fused Kimi-K3 KDA decode specialization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from collections.abc import Iterable
|
||||
|
||||
import torch
|
||||
from aiter.ops.flydsl.kernels.tensor_shim import _run_compiled
|
||||
|
||||
from .kernels.kimi_k3_kda_decode import (
|
||||
create_kimi_k3_kda_decode_kernel,
|
||||
)
|
||||
from .kernels.kimi_k3_kda_decode_fb import (
|
||||
create_kimi_k3_kda_decode_fb_kernel,
|
||||
)
|
||||
|
||||
_HEADS = 12
|
||||
_DIM = 128
|
||||
_CONV_CHANNELS = 3 * _HEADS * _DIM
|
||||
_CONV_WIDTH = 4
|
||||
|
||||
|
||||
def _fb_build_options(batch: int) -> dict[str, int | bool]:
|
||||
"""Use the validated gfx950 winner only for the exact-C2 bucket."""
|
||||
if batch != 2:
|
||||
return {}
|
||||
return {
|
||||
"waves_per_eu": 3,
|
||||
"cooperative_f_a": True,
|
||||
"parallel_front": True,
|
||||
"fused_norm_reduce": True,
|
||||
"projection_fdot2": True,
|
||||
}
|
||||
|
||||
|
||||
@functools.cache
|
||||
def _rocm_arch(device: torch.device) -> str | None:
|
||||
properties = torch.cuda.get_device_properties(device)
|
||||
arch = getattr(properties, "gcnArchName", None)
|
||||
return arch.split(":", 1)[0] if arch is not None else None
|
||||
|
||||
|
||||
def is_flydsl_kimi_k3_kda_decode_supported(
|
||||
device: torch.device | str | int | None = None,
|
||||
) -> bool:
|
||||
"""Return whether ``device`` can run this gfx950-only specialization."""
|
||||
if not torch.cuda.is_available():
|
||||
return False
|
||||
try:
|
||||
resolved = torch.device(
|
||||
"cuda",
|
||||
torch.cuda.current_device(),
|
||||
)
|
||||
if device is not None:
|
||||
resolved = (
|
||||
torch.device("cuda", device)
|
||||
if isinstance(device, int)
|
||||
else torch.device(device)
|
||||
)
|
||||
if resolved.type != "cuda":
|
||||
return False
|
||||
if resolved.index is None:
|
||||
resolved = torch.device(
|
||||
"cuda",
|
||||
torch.cuda.current_device(),
|
||||
)
|
||||
return _rocm_arch(resolved) == "gfx950"
|
||||
except (AssertionError, RuntimeError, TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
def _check_tensor(
|
||||
name: str,
|
||||
tensor: torch.Tensor,
|
||||
*,
|
||||
shape: tuple[int, ...],
|
||||
dtype: torch.dtype,
|
||||
device: torch.device,
|
||||
inner_strides: tuple[int, ...] = (),
|
||||
) -> None:
|
||||
if tensor.shape != shape:
|
||||
raise ValueError(
|
||||
f"`{name}` must have shape {list(shape)}, got {list(tensor.shape)}."
|
||||
)
|
||||
if tensor.dtype != dtype:
|
||||
raise ValueError(f"`{name}` must have dtype {dtype}, got {tensor.dtype}.")
|
||||
if tensor.device != device:
|
||||
raise ValueError(f"`{name}` must be on {device}, got {tensor.device}.")
|
||||
if inner_strides and tensor.stride()[-len(inner_strides) :] != inner_strides:
|
||||
raise ValueError(
|
||||
f"`{name}` must have inner strides {inner_strides}, got {tensor.stride()}."
|
||||
)
|
||||
|
||||
|
||||
def _check_same_device(
|
||||
tensors: Iterable[tuple[str, torch.Tensor]],
|
||||
device: torch.device,
|
||||
) -> None:
|
||||
for name, tensor in tensors:
|
||||
if not tensor.is_cuda:
|
||||
raise ValueError(f"`{name}` must be a CUDA tensor.")
|
||||
if tensor.device != device:
|
||||
raise ValueError(f"`{name}` must be on {device}, got {tensor.device}.")
|
||||
|
||||
|
||||
def _validate_kda_inputs(
|
||||
*,
|
||||
api_name: str,
|
||||
batch_source: str,
|
||||
device: torch.device,
|
||||
batch: int,
|
||||
x: torch.Tensor,
|
||||
conv_weight: torch.Tensor,
|
||||
conv_bias: torch.Tensor | None,
|
||||
conv_state: torch.Tensor,
|
||||
raw_beta: torch.Tensor,
|
||||
A_log: torch.Tensor,
|
||||
dt_bias: torch.Tensor,
|
||||
lower_bound: float | None,
|
||||
state: torch.Tensor,
|
||||
state_indices: torch.Tensor,
|
||||
output_gate: torch.Tensor,
|
||||
norm_weight: torch.Tensor,
|
||||
out: torch.Tensor | None,
|
||||
) -> torch.Tensor:
|
||||
"""Validate operands shared by both explicit KDA specializations."""
|
||||
if not is_flydsl_kimi_k3_kda_decode_supported(device):
|
||||
raise RuntimeError(f"`{api_name}` requires a gfx950 GPU.")
|
||||
if batch <= 0:
|
||||
raise ValueError(f"`{batch_source}` must have a non-empty batch dimension.")
|
||||
if conv_bias is not None:
|
||||
raise ValueError("This specialization requires `conv_bias=None`.")
|
||||
if lower_bound is None:
|
||||
raise ValueError("This specialization requires the KDA lower-bound gate.")
|
||||
|
||||
_check_same_device(
|
||||
(
|
||||
("x", x),
|
||||
("conv_weight", conv_weight),
|
||||
("conv_state", conv_state),
|
||||
("raw_beta", raw_beta),
|
||||
("A_log", A_log),
|
||||
("dt_bias", dt_bias),
|
||||
("state", state),
|
||||
("state_indices", state_indices),
|
||||
("output_gate", output_gate),
|
||||
("norm_weight", norm_weight),
|
||||
),
|
||||
device,
|
||||
)
|
||||
_check_tensor(
|
||||
"x",
|
||||
x,
|
||||
shape=(batch, _CONV_CHANNELS),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
_check_tensor(
|
||||
"conv_weight",
|
||||
conv_weight,
|
||||
shape=(_CONV_CHANNELS, _CONV_WIDTH),
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
)
|
||||
if conv_state.ndim != 3 or conv_state.shape[1:] != (
|
||||
_CONV_CHANNELS,
|
||||
_CONV_WIDTH - 1,
|
||||
):
|
||||
raise ValueError(
|
||||
"`conv_state` must have shape [cache, 4608, 3], "
|
||||
f"got {list(conv_state.shape)}."
|
||||
)
|
||||
if conv_state.dtype != torch.bfloat16:
|
||||
raise ValueError("`conv_state` must have dtype torch.bfloat16.")
|
||||
if state.ndim != 4 or state.shape[1:] != (
|
||||
_HEADS,
|
||||
_DIM,
|
||||
_DIM,
|
||||
):
|
||||
raise ValueError(
|
||||
f"`state` must have shape [cache, 12, 128, 128], got {list(state.shape)}."
|
||||
)
|
||||
if state.dtype != torch.float32:
|
||||
raise ValueError("`state` must have dtype torch.float32.")
|
||||
if state.stride()[-3:] != (_DIM * _DIM, _DIM, 1):
|
||||
raise ValueError("`state` must be contiguous within each cache slot.")
|
||||
_check_tensor(
|
||||
"raw_beta",
|
||||
raw_beta,
|
||||
shape=(1, batch, _HEADS),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
_check_tensor(
|
||||
"A_log",
|
||||
A_log,
|
||||
shape=(_HEADS,),
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
_check_tensor(
|
||||
"dt_bias",
|
||||
dt_bias,
|
||||
shape=(_HEADS * _DIM,),
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
_check_tensor(
|
||||
"state_indices",
|
||||
state_indices,
|
||||
shape=(batch,),
|
||||
dtype=torch.int32,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
_check_tensor(
|
||||
"output_gate",
|
||||
output_gate,
|
||||
shape=(batch, _HEADS, _DIM),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
_check_tensor(
|
||||
"norm_weight",
|
||||
norm_weight,
|
||||
shape=(_DIM,),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
|
||||
if out is None:
|
||||
return torch.empty(
|
||||
(1, batch, _HEADS, _DIM),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
)
|
||||
_check_same_device((("out", out),), device)
|
||||
_check_tensor(
|
||||
"out",
|
||||
out,
|
||||
shape=(1, batch, _HEADS, _DIM),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def flydsl_kimi_k3_kda_decode(
|
||||
x: torch.Tensor,
|
||||
conv_weight: torch.Tensor,
|
||||
conv_bias: torch.Tensor | None,
|
||||
conv_state: torch.Tensor,
|
||||
raw_g: torch.Tensor,
|
||||
raw_beta: torch.Tensor,
|
||||
A_log: torch.Tensor,
|
||||
dt_bias: torch.Tensor,
|
||||
lower_bound: float | None,
|
||||
state: torch.Tensor,
|
||||
state_indices: torch.Tensor,
|
||||
output_gate: torch.Tensor,
|
||||
norm_weight: torch.Tensor,
|
||||
norm_eps: float,
|
||||
out: torch.Tensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
"""Run fused Kimi-K3 KDA decode on MI350-series GPUs.
|
||||
|
||||
This pure-decode specialization fuses the packed width-4 Q/K/V causal
|
||||
convolution, the FP32 recurrent-state update, and the BF16
|
||||
RMSNorm/sigmoid output gate. Slot zero is reserved: non-positive
|
||||
``state_indices`` produce zero output without modifying either cache.
|
||||
|
||||
The layout is fixed to Kimi-K3 TP8: 12 local heads and 128-dimensional
|
||||
key/value state. Call
|
||||
:func:`is_flydsl_kimi_k3_kda_decode_supported` before dispatching from a
|
||||
model implementation.
|
||||
"""
|
||||
if x.ndim != 2:
|
||||
raise ValueError(f"`x` must have rank 2, got rank {x.ndim}.")
|
||||
if not x.is_cuda:
|
||||
raise ValueError("`x` must be a CUDA tensor.")
|
||||
device = x.device
|
||||
batch = x.shape[0]
|
||||
out = _validate_kda_inputs(
|
||||
api_name="flydsl_kimi_k3_kda_decode",
|
||||
batch_source="x",
|
||||
device=device,
|
||||
batch=batch,
|
||||
x=x,
|
||||
conv_weight=conv_weight,
|
||||
conv_bias=conv_bias,
|
||||
conv_state=conv_state,
|
||||
raw_beta=raw_beta,
|
||||
A_log=A_log,
|
||||
dt_bias=dt_bias,
|
||||
lower_bound=lower_bound,
|
||||
state=state,
|
||||
state_indices=state_indices,
|
||||
output_gate=output_gate,
|
||||
norm_weight=norm_weight,
|
||||
out=out,
|
||||
)
|
||||
_check_same_device((("raw_g", raw_g),), device)
|
||||
_check_tensor(
|
||||
"raw_g",
|
||||
raw_g,
|
||||
shape=(1, batch, _HEADS, _DIM),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(_DIM, 1),
|
||||
)
|
||||
|
||||
executable = create_kimi_k3_kda_decode_kernel(
|
||||
float(norm_eps),
|
||||
float(lower_bound),
|
||||
)
|
||||
with torch.cuda.device(device):
|
||||
stream = torch.cuda.current_stream(device)
|
||||
_run_compiled(
|
||||
executable,
|
||||
x,
|
||||
conv_weight,
|
||||
conv_state,
|
||||
raw_g,
|
||||
raw_beta,
|
||||
A_log,
|
||||
dt_bias,
|
||||
state,
|
||||
state_indices,
|
||||
output_gate,
|
||||
norm_weight,
|
||||
out,
|
||||
batch,
|
||||
x.stride(0),
|
||||
conv_weight.stride(0),
|
||||
conv_weight.stride(1),
|
||||
conv_state.stride(0),
|
||||
conv_state.stride(1),
|
||||
conv_state.stride(2),
|
||||
raw_g.stride(1),
|
||||
raw_beta.stride(1),
|
||||
state.stride(0),
|
||||
output_gate.stride(0),
|
||||
output_gate.stride(1),
|
||||
out.stride(1),
|
||||
out.stride(2),
|
||||
stream,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def flydsl_kimi_k3_kda_decode_with_f_b(
|
||||
f_a: torch.Tensor,
|
||||
f_b_weight: torch.Tensor,
|
||||
x: torch.Tensor,
|
||||
conv_weight: torch.Tensor,
|
||||
conv_bias: torch.Tensor | None,
|
||||
conv_state: torch.Tensor,
|
||||
raw_beta: torch.Tensor,
|
||||
A_log: torch.Tensor,
|
||||
dt_bias: torch.Tensor,
|
||||
lower_bound: float | None,
|
||||
state: torch.Tensor,
|
||||
state_indices: torch.Tensor,
|
||||
output_gate: torch.Tensor,
|
||||
norm_weight: torch.Tensor,
|
||||
norm_eps: float,
|
||||
out: torch.Tensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
"""Run the explicit gfx950 Kimi-K3 f_b plus KDA decode specialization.
|
||||
|
||||
The kernel consumes ``f_a`` and the head-local ``f_b_weight`` directly,
|
||||
accumulates the projection in FP32, and rounds once to BF16 before the KDA
|
||||
lower-bound decay gate. It does not materialize the projected raw-g tensor
|
||||
in global memory.
|
||||
"""
|
||||
if f_a.ndim != 2:
|
||||
raise ValueError(f"`f_a` must have rank 2, got rank {f_a.ndim}.")
|
||||
if not f_a.is_cuda:
|
||||
raise ValueError("`f_a` must be a CUDA tensor.")
|
||||
device = f_a.device
|
||||
batch = f_a.shape[0]
|
||||
_check_same_device((("f_b_weight", f_b_weight),), device)
|
||||
_check_tensor(
|
||||
"f_a",
|
||||
f_a,
|
||||
shape=(batch, _DIM),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(1,),
|
||||
)
|
||||
_check_tensor(
|
||||
"f_b_weight",
|
||||
f_b_weight,
|
||||
shape=(_HEADS, _DIM, _DIM),
|
||||
dtype=torch.bfloat16,
|
||||
device=device,
|
||||
inner_strides=(_DIM, 1),
|
||||
)
|
||||
out = _validate_kda_inputs(
|
||||
api_name="flydsl_kimi_k3_kda_decode_with_f_b",
|
||||
batch_source="f_a",
|
||||
device=device,
|
||||
batch=batch,
|
||||
x=x,
|
||||
conv_weight=conv_weight,
|
||||
conv_bias=conv_bias,
|
||||
conv_state=conv_state,
|
||||
raw_beta=raw_beta,
|
||||
A_log=A_log,
|
||||
dt_bias=dt_bias,
|
||||
lower_bound=lower_bound,
|
||||
state=state,
|
||||
state_indices=state_indices,
|
||||
output_gate=output_gate,
|
||||
norm_weight=norm_weight,
|
||||
out=out,
|
||||
)
|
||||
|
||||
executable = create_kimi_k3_kda_decode_fb_kernel(
|
||||
float(norm_eps),
|
||||
float(lower_bound),
|
||||
**_fb_build_options(batch),
|
||||
)
|
||||
with torch.cuda.device(device):
|
||||
stream = torch.cuda.current_stream(device)
|
||||
_run_compiled(
|
||||
executable,
|
||||
f_a,
|
||||
f_b_weight,
|
||||
x,
|
||||
conv_weight,
|
||||
conv_state,
|
||||
raw_beta,
|
||||
A_log,
|
||||
dt_bias,
|
||||
state,
|
||||
state_indices,
|
||||
output_gate,
|
||||
norm_weight,
|
||||
out,
|
||||
batch,
|
||||
f_a.stride(0),
|
||||
f_b_weight.stride(0),
|
||||
f_b_weight.stride(1),
|
||||
x.stride(0),
|
||||
conv_weight.stride(0),
|
||||
conv_weight.stride(1),
|
||||
conv_state.stride(0),
|
||||
conv_state.stride(1),
|
||||
conv_state.stride(2),
|
||||
raw_beta.stride(1),
|
||||
state.stride(0),
|
||||
output_gate.stride(0),
|
||||
output_gate.stride(1),
|
||||
out.stride(1),
|
||||
out.stride(2),
|
||||
stream,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
__all__ = [
|
||||
"flydsl_kimi_k3_kda_decode",
|
||||
"flydsl_kimi_k3_kda_decode_with_f_b",
|
||||
"is_flydsl_kimi_k3_kda_decode_supported",
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Select SGLang-vendored or upstream AITER Kimi-K3 FlyDSL operators."""
|
||||
|
||||
import importlib
|
||||
import os
|
||||
|
||||
|
||||
def load_module(local_module: str, aiter_module: str):
|
||||
mode = os.environ.get("SGLANG_K3_FLYDSL_SOURCE", "auto").lower()
|
||||
if mode not in ("auto", "sglang", "aiter"):
|
||||
raise ValueError(
|
||||
"SGLANG_K3_FLYDSL_SOURCE must be one of auto, sglang, or aiter"
|
||||
)
|
||||
|
||||
candidates = (
|
||||
((local_module, "sglang"), (aiter_module, "aiter"))
|
||||
if mode in ("auto", "sglang")
|
||||
else ((aiter_module, "aiter"),)
|
||||
)
|
||||
errors = []
|
||||
for module_name, source in candidates:
|
||||
if mode == "sglang" and source != "sglang":
|
||||
continue
|
||||
try:
|
||||
return importlib.import_module(module_name)
|
||||
except (ImportError, ModuleNotFoundError) as error:
|
||||
errors.append(f"{source}: {error}")
|
||||
raise ImportError("Kimi-K3 FlyDSL source unavailable: " + "; ".join(errors))
|
||||
@@ -3,7 +3,7 @@ from typing import Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.kernels.ops.attention import kda_fused_decode
|
||||
from sglang.kernels.ops.attention import kda_fused_decode, kda_fused_decode_aiter_hip
|
||||
from sglang.kernels.ops.mamba.causal_conv1d_triton import (
|
||||
causal_conv1d_fn,
|
||||
causal_conv1d_update,
|
||||
@@ -20,7 +20,7 @@ from sglang.srt.layers.attention.linear.utils import (
|
||||
)
|
||||
from sglang.srt.layers.radix_linear_attention import RadixLinearAttention
|
||||
from sglang.srt.utils import is_cpu, is_cuda, is_npu
|
||||
from sglang.srt.utils.common import rank0_log
|
||||
from sglang.srt.utils.common import is_gfx95_supported, rank0_log
|
||||
|
||||
# KDA always uses the triton causal_conv1d_fn (no CUDA override).
|
||||
# Only causal_conv1d_update needs platform-specific overrides for decode.
|
||||
@@ -555,6 +555,92 @@ class KDAAttnBackend(MambaAttnBackendBase):
|
||||
replayssm_k = layer_cache.replayssm_k
|
||||
replayssm_g = layer_cache.replayssm_g
|
||||
|
||||
deferred_f_b = bool(getattr(layer, "_k3_deferred_f_b", False))
|
||||
if replayssm_d is None and deferred_f_b and is_gfx95_supported():
|
||||
fused_static = getattr(layer, "_k3_hip_fused_decode_args", None)
|
||||
fused_backend = getattr(layer, "_k3_hip_fused_decode_backend", "")
|
||||
onorm_gate = getattr(layer, "_k3_onorm_gate", None)
|
||||
if fused_static is not None and onorm_gate is not None:
|
||||
f_b_weight, norm_weight, norm_eps, a_log = fused_static
|
||||
conv_state_view = conv_states.transpose(-1, -2)
|
||||
output_gate = onorm_gate.view(
|
||||
onorm_gate.shape[0], layer.num_v_heads, layer.head_v_dim
|
||||
)
|
||||
out = mixed_qkv.new_empty(
|
||||
(1, mixed_qkv.shape[0], layer.num_v_heads, layer.head_v_dim)
|
||||
)
|
||||
if fused_backend == "aiter" and kda_fused_decode_aiter_hip.covered(
|
||||
a,
|
||||
f_b_weight,
|
||||
mixed_qkv,
|
||||
b,
|
||||
conv_state_view,
|
||||
ssm_states,
|
||||
cache_indices,
|
||||
output_gate,
|
||||
norm_weight,
|
||||
):
|
||||
core_attn_out = kda_fused_decode_aiter_hip.run(
|
||||
f_a=a,
|
||||
f_b_weight=f_b_weight,
|
||||
mixed_qkv=mixed_qkv,
|
||||
conv_weight=layer.conv_weights,
|
||||
conv_state=conv_state_view,
|
||||
raw_beta=b,
|
||||
A_log=a_log,
|
||||
dt_bias=layer.dt_bias,
|
||||
lower_bound=float(layer.lower_bound),
|
||||
state=ssm_states,
|
||||
state_indices=cache_indices,
|
||||
output_gate=output_gate,
|
||||
norm_weight=norm_weight,
|
||||
norm_eps=norm_eps,
|
||||
out=out,
|
||||
)
|
||||
else:
|
||||
core_attn_out = None
|
||||
if not getattr(KDAAttnBackend, "_hip_fused_reject_logged", False):
|
||||
KDAAttnBackend._hip_fused_reject_logged = True
|
||||
rank0_log(
|
||||
"K3 HIP fused KDA rejected: "
|
||||
f"backend={fused_backend}, "
|
||||
f"f_a={tuple(a.shape)}/{a.dtype}/{a.stride()}, "
|
||||
f"mixed={tuple(mixed_qkv.shape)}/{mixed_qkv.dtype}/"
|
||||
f"{mixed_qkv.stride()}, beta={tuple(b.shape)}/{b.dtype}/"
|
||||
f"{b.stride()}, conv={tuple(conv_state_view.shape)}/"
|
||||
f"{conv_state_view.dtype}/{conv_state_view.stride()}, "
|
||||
f"state={tuple(ssm_states.shape)}/{ssm_states.dtype}/"
|
||||
f"{ssm_states.stride()}, indices={cache_indices.dtype}/"
|
||||
f"{cache_indices.stride()}, gate={tuple(output_gate.shape)}/"
|
||||
f"{output_gate.dtype}/{output_gate.stride()}, "
|
||||
f"norm={norm_weight.dtype}/{norm_weight.stride()}"
|
||||
)
|
||||
|
||||
if core_attn_out is not None:
|
||||
layer._k3_onorm_consumed = True
|
||||
self._track_mamba_state_decode(
|
||||
forward_batch,
|
||||
conv_states,
|
||||
ssm_states,
|
||||
cache_indices,
|
||||
layer.layer_id,
|
||||
)
|
||||
return core_attn_out
|
||||
|
||||
# The model deferred f_b only after publishing static fallback
|
||||
# weights. Materialize the original gate before entering the
|
||||
# unchanged conv + packed-KDA fallback chain.
|
||||
from sglang.kernels.ops.kimi_k3 import kimi_k3_tiny_gemm
|
||||
|
||||
if fused_static is None:
|
||||
raise RuntimeError("K3 deferred f_b is missing fallback weights")
|
||||
a = kimi_k3_tiny_gemm(
|
||||
a,
|
||||
fused_static[0].view(
|
||||
layer.num_v_heads * layer.head_v_dim, layer.head_v_dim
|
||||
),
|
||||
)
|
||||
|
||||
# Fully fused decode step: conv1d update + delta-rule recurrence +
|
||||
# gated RMSNorm in one kernel. Engages only when the model handed off
|
||||
# the output-norm gate for this forward (attempt-and-verify stash,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
# - Full-rank KDA gate (use_full_rank_gate)
|
||||
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Iterable
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
@@ -1695,6 +1696,7 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
self.attn.lower_bound = config.linear_attn_config.get("gate_lower_bound", None)
|
||||
# Set by _prepare_fused_decode() once weights are loaded.
|
||||
self._kda_fused_decode_ready = False
|
||||
self._kda_hip_fused_decode_ready = False
|
||||
|
||||
def forward_qkvbfg(self, hidden_states: torch.Tensor):
|
||||
qkv, _ = self.qkv_proj(hidden_states)
|
||||
@@ -1746,7 +1748,51 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
unfused chain. Called once from load_weights (after all weights are
|
||||
loaded, before cuda graph capture)."""
|
||||
if _is_hip:
|
||||
# The fused KDA decode kernel is NVIDIA-only
|
||||
from sglang.kernels.ops.attention import kda_fused_decode_aiter_hip
|
||||
|
||||
layer = self.attn
|
||||
w = layer.conv_weights
|
||||
f_b_weight = self.f_b_proj.weight
|
||||
backend = os.environ.get("SGLANG_K3_KDA_FUSED_BACKEND", "").lower()
|
||||
backend_available = (
|
||||
backend == "aiter"
|
||||
and kda_fused_decode_aiter_hip.available(f_b_weight.device)
|
||||
)
|
||||
if (
|
||||
backend_available
|
||||
and w is not None
|
||||
and tuple(w.shape) == (3 * 12 * 128, 4)
|
||||
and w.dtype == torch.float32
|
||||
and f_b_weight.shape == (12 * 128, 128)
|
||||
and f_b_weight.dtype == torch.bfloat16
|
||||
and layer.A_log is not None
|
||||
and layer.A_log.numel() == 12
|
||||
and layer.A_log.dtype == torch.float32
|
||||
and layer.dt_bias is not None
|
||||
and tuple(layer.dt_bias.shape) == (12 * 128,)
|
||||
and layer.dt_bias.dtype == torch.float32
|
||||
and layer.lower_bound is not None
|
||||
):
|
||||
norm_weight = self.o_norm.weight.data.to(torch.bfloat16).contiguous()
|
||||
f_b_weight = f_b_weight.view(12, 128, 128).contiguous()
|
||||
a_log = layer.A_log.detach().reshape(-1).contiguous()
|
||||
layer._k3_hip_fused_decode_args = (
|
||||
f_b_weight,
|
||||
norm_weight,
|
||||
float(self.o_norm.eps),
|
||||
a_log,
|
||||
)
|
||||
kda_fused_decode_aiter_hip.warmup(
|
||||
f_b_weight=f_b_weight,
|
||||
conv_weight=w,
|
||||
A_log=a_log,
|
||||
dt_bias=layer.dt_bias,
|
||||
lower_bound=float(layer.lower_bound),
|
||||
norm_weight=norm_weight,
|
||||
norm_eps=float(self.o_norm.eps),
|
||||
)
|
||||
layer._k3_hip_fused_decode_backend = backend
|
||||
self._kda_hip_fused_decode_ready = True
|
||||
return
|
||||
layer = self.attn
|
||||
w = layer.conv_weights
|
||||
@@ -1792,7 +1838,9 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
)
|
||||
self._kda_fused_decode_ready = True
|
||||
|
||||
def forward_qkvbfg_fused(self, hidden_states: torch.Tensor):
|
||||
def forward_qkvbfg_fused(
|
||||
self, hidden_states: torch.Tensor, defer_f_b: bool = False
|
||||
):
|
||||
if self.use_full_rank_gate:
|
||||
if self._bfa_w is not None:
|
||||
w = self._bfa_w
|
||||
@@ -1813,7 +1861,11 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
alt.wait_stream(cur)
|
||||
with torch.cuda.stream(alt):
|
||||
bfa = gemm(hidden_states, w)
|
||||
forget_gate = gemm(bfa[..., :n_fa], self._bfa_f_b_w)
|
||||
forget_gate = (
|
||||
bfa[..., :n_fa]
|
||||
if defer_f_b
|
||||
else gemm(bfa[..., :n_fa], self._bfa_f_b_w)
|
||||
)
|
||||
beta = bfa[..., n_fa : n_fa + n_b]
|
||||
fused_states, _ = self.fused_qkvg_proj(hidden_states)
|
||||
qkv, g_proj_states = torch.split(
|
||||
@@ -1825,13 +1877,18 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
fused_states, _ = self.fused_qkvg_proj(hidden_states)
|
||||
qkv, g_proj_states = torch.split(fused_states, self.split_sizes, dim=-1)
|
||||
bfa = gemm(hidden_states, w)
|
||||
forget_gate = gemm(bfa[..., :n_fa], self._bfa_f_b_w)
|
||||
forget_gate = (
|
||||
bfa[..., :n_fa]
|
||||
if defer_f_b
|
||||
else gemm(bfa[..., :n_fa], self._bfa_f_b_w)
|
||||
)
|
||||
beta = bfa[..., n_fa : n_fa + n_b]
|
||||
else:
|
||||
fused_states, _ = self.fused_qkvg_proj(hidden_states)
|
||||
qkv, g_proj_states = torch.split(fused_states, self.split_sizes, dim=-1)
|
||||
beta = self.b_proj(hidden_states)[0]
|
||||
forget_gate = self.f_b_proj(self.f_a_proj(hidden_states)[0])[0]
|
||||
f_a = self.f_a_proj(hidden_states)[0]
|
||||
forget_gate = f_a if defer_f_b else self.f_b_proj(f_a)[0]
|
||||
else:
|
||||
fused_states = self.fused_qkvbfg_a_proj(hidden_states)
|
||||
qkv, beta, fg_a_states = torch.split(fused_states, self.split_sizes, dim=-1)
|
||||
@@ -1847,9 +1904,12 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
forward_batch: ForwardBatch,
|
||||
zero_allocator: BumpAllocator,
|
||||
) -> torch.Tensor:
|
||||
defer_f_b = (
|
||||
self._kda_hip_fused_decode_ready and forward_batch.forward_mode.is_decode()
|
||||
)
|
||||
if self.do_fuse_qkvbfg:
|
||||
mixed_qkv, beta, forget_gate, g_proj_states = self.forward_qkvbfg_fused(
|
||||
hidden_states
|
||||
hidden_states, defer_f_b=defer_f_b
|
||||
)
|
||||
else:
|
||||
mixed_qkv, beta, forget_gate, g_proj_states = self.forward_qkvbfg(
|
||||
@@ -1870,13 +1930,15 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
# into the recurrence kernel. If the backend leaves the stash
|
||||
# unconsumed (env off or shape not covered), apply o_norm here as
|
||||
# before.
|
||||
fused_onorm = self._kda_fused_decode_ready and (
|
||||
fused_onorm = (self._kda_fused_decode_ready or defer_f_b) and (
|
||||
forward_batch.forward_mode.is_decode()
|
||||
or forward_batch.forward_mode.is_target_verify()
|
||||
)
|
||||
if fused_onorm:
|
||||
self.attn._k3_onorm_gate = g_proj_states
|
||||
self.attn._k3_onorm_consumed = False
|
||||
if defer_f_b:
|
||||
self.attn._k3_deferred_f_b = True
|
||||
|
||||
core_attn_out = self.attn(
|
||||
forward_batch,
|
||||
@@ -1888,6 +1950,8 @@ class KimiK3DeltaAttention(nn.Module):
|
||||
if fused_onorm:
|
||||
self.attn._k3_onorm_gate = None
|
||||
fused_onorm = self.attn._k3_onorm_consumed
|
||||
if defer_f_b:
|
||||
self.attn._k3_deferred_f_b = False
|
||||
if not fused_onorm:
|
||||
norm_gate = g_proj_states.unflatten(-1, (-1, self.head_dim))
|
||||
core_attn_out = self.o_norm(core_attn_out, norm_gate)
|
||||
|
||||
Reference in New Issue
Block a user