[XPU] Enable qwen3.5 on XPU (#21668)
Co-authored-by: Ma Mingfei <mingfei.ma@intel.com>
This commit is contained in:
@@ -54,6 +54,8 @@ RUN --mount=type=secret,id=github_token \
|
||||
cd /home/sdp && \
|
||||
. /home/sdp/miniforge3/bin/activate && \
|
||||
conda activate py${PYTHON_VERSION} && \
|
||||
conda install libsqlite=3.48.0 -y && \
|
||||
pip install msgspec blake3 py-cpuinfo compressed_tensors gguf partial_json_parser einops tabulate --root-user-action=ignore && \
|
||||
pip3 install torch==2.11.0+xpu torchao torchvision torchaudio==2.11.0+xpu --index-url https://download.pytorch.org/whl/xpu
|
||||
|
||||
RUN --mount=type=secret,id=github_token \
|
||||
@@ -66,8 +68,8 @@ RUN --mount=type=secret,id=github_token \
|
||||
cp pyproject_xpu.toml pyproject.toml && \
|
||||
pip install . --extra-index-url https://download.pytorch.org/whl/xpu && \
|
||||
pip install --no-deps xgrammar==0.1.33 && \
|
||||
pip install msgspec blake3 py-cpuinfo compressed_tensors gguf partial_json_parser einops tabulate --root-user-action=ignore && \
|
||||
conda install libsqlite=3.48.0 -y && \
|
||||
# index will change after torch 2.12 release
|
||||
pip install triton-xpu --index-url https://download.pytorch.org/whl/test/xpu --force-reinstall && \
|
||||
# Add environment setup commands to .bashrc again (in case it was overwritten)
|
||||
echo ". /home/sdp/miniforge3/bin/activate; conda activate py${PYTHON_VERSION}; cd /home/sdp" >> /home/sdp/.bashrc
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ cp pyproject_xpu.toml pyproject.toml
|
||||
# Install SGLang dependent libs, and build SGLang main package
|
||||
pip install --upgrade pip setuptools
|
||||
pip install -v . --extra-index-url https://download.pytorch.org/whl/xpu
|
||||
# Using this version of triton-xpu to avoid a bug in the version shipped with torch 2.11.0+xpu
|
||||
pip install triton-xpu --index-url https://download.pytorch.org/whl/test/xpu --force-reinstall # index will change after torch 2.12 release
|
||||
```
|
||||
|
||||
### Install Using Docker
|
||||
|
||||
@@ -174,7 +174,9 @@ def stop_profile(
|
||||
if save_trace:
|
||||
if profiler is not None:
|
||||
if trace_filename:
|
||||
_save_profile_trace_results(profiler, trace_filename)
|
||||
_save_profile_trace_results(
|
||||
profiler, profile_activities, trace_filename
|
||||
)
|
||||
stage_desc = f"for {stage}" if stage else ""
|
||||
rank_print(
|
||||
f"torch profiler chrome trace {stage_desc} saved to {trace_filename}"
|
||||
@@ -597,15 +599,17 @@ def _create_torch_profiler_filename(
|
||||
return os.path.join(output_dir, filename)
|
||||
|
||||
|
||||
def _save_profile_trace_results(profiler, filename):
|
||||
def _save_profile_trace_results(profiler, profile_activities, filename):
|
||||
parent_dir = os.path.dirname(os.path.abspath(filename))
|
||||
os.makedirs(parent_dir, exist_ok=True)
|
||||
profiler.export_chrome_trace(filename)
|
||||
print(
|
||||
profiler.key_averages(group_by_input_shape=True).table(
|
||||
sort_by="self_cpu_time_total"
|
||||
)
|
||||
)
|
||||
if "GPU" in profile_activities:
|
||||
sort_by = "self_cuda_time_total"
|
||||
elif "XPU" in profile_activities:
|
||||
sort_by = "self_xpu_time_total"
|
||||
else:
|
||||
sort_by = "self_cpu_time_total"
|
||||
print(profiler.key_averages(group_by_input_shape=True).table(sort_by=sort_by))
|
||||
|
||||
|
||||
def correctness_test(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# XPU (Intel GPU) hardware backend
|
||||
@@ -0,0 +1,240 @@
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.layers.attention.fla.index import (
|
||||
prepare_chunk_indices,
|
||||
prepare_chunk_offsets,
|
||||
)
|
||||
from sglang.srt.layers.attention.fla.op import exp, make_tensor_descriptor, safe_exp
|
||||
from sglang.srt.layers.attention.fla.utils import (
|
||||
autotune_cache_kwargs,
|
||||
)
|
||||
|
||||
CHUNK_SIZE = 64
|
||||
|
||||
|
||||
# This kernel handles K blocks in a for loop to minimize register spills
|
||||
@triton.autotune(
|
||||
configs=[triton.Config({"BV": 64}, num_warps=8, num_stages=2)],
|
||||
key=["H", "K", "V", "BT", "USE_GK", "USE_INITIAL_STATE", "NT_BUCKET"],
|
||||
**autotune_cache_kwargs,
|
||||
)
|
||||
@triton.jit(do_not_specialize=["T"])
|
||||
def chunk_gated_delta_rule_fwd_kernel_h_blockdim64_k_loop(
|
||||
k,
|
||||
v,
|
||||
w,
|
||||
v_new,
|
||||
g,
|
||||
gk,
|
||||
h,
|
||||
initial_state,
|
||||
initial_state_indices,
|
||||
cu_seqlens,
|
||||
chunk_offsets,
|
||||
T,
|
||||
H: tl.constexpr,
|
||||
Hg: tl.constexpr,
|
||||
K: tl.constexpr,
|
||||
V: tl.constexpr,
|
||||
BT: tl.constexpr,
|
||||
BV: tl.constexpr,
|
||||
USE_G: tl.constexpr,
|
||||
USE_GK: tl.constexpr,
|
||||
USE_INITIAL_STATE: tl.constexpr,
|
||||
INPLACE_UPDATE: tl.constexpr,
|
||||
SAVE_NEW_VALUE: tl.constexpr,
|
||||
IS_VARLEN: tl.constexpr,
|
||||
NT_BUCKET: tl.constexpr, # this arg is kept to align with the triton kernel for CUDA
|
||||
):
|
||||
i_v, i_nh = tl.program_id(0), tl.program_id(1)
|
||||
i_n, i_h = i_nh // H, i_nh % H
|
||||
if IS_VARLEN:
|
||||
bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
|
||||
cu_seqlens + i_n + 1
|
||||
).to(tl.int32)
|
||||
T = eos - bos
|
||||
NT = tl.cdiv(T, BT)
|
||||
boh = tl.load(chunk_offsets + i_n).to(tl.int32)
|
||||
else:
|
||||
bos, eos = i_n * T, i_n * T + T
|
||||
NT = tl.cdiv(T, BT)
|
||||
boh = i_n * NT
|
||||
|
||||
# calculate offset
|
||||
h += ((boh * H + i_h) * V * K).to(tl.int64)
|
||||
v += ((bos * H + i_h) * V).to(tl.int64)
|
||||
k += ((bos * Hg + i_h // (H // Hg)) * K).to(tl.int64)
|
||||
w += ((bos * H + i_h) * K).to(tl.int64)
|
||||
if SAVE_NEW_VALUE:
|
||||
v_new += ((bos * H + i_h) * V).to(tl.int64)
|
||||
stride_v = H * V
|
||||
stride_h = H * V * K
|
||||
stride_k = Hg * K
|
||||
stride_w = H * K
|
||||
|
||||
w_desc = make_tensor_descriptor(
|
||||
base=w,
|
||||
shape=(T, K),
|
||||
strides=(stride_w, 1),
|
||||
block_shape=(BT, 64),
|
||||
)
|
||||
v_desc = make_tensor_descriptor(
|
||||
base=v,
|
||||
shape=(T, V),
|
||||
strides=(stride_v, 1),
|
||||
block_shape=(BT, BV),
|
||||
)
|
||||
k_desc = make_tensor_descriptor(
|
||||
base=k,
|
||||
shape=(T, K),
|
||||
strides=(stride_k, 1),
|
||||
block_shape=(BT, 64),
|
||||
)
|
||||
if SAVE_NEW_VALUE:
|
||||
v_new_desc = make_tensor_descriptor(
|
||||
base=v_new,
|
||||
shape=(T, V),
|
||||
strides=(stride_v, 1),
|
||||
block_shape=(BT, BV),
|
||||
)
|
||||
|
||||
index = tl.load(initial_state_indices + i_n).to(tl.int32)
|
||||
h0 = initial_state + index * stride_h
|
||||
ht = initial_state + index * stride_h
|
||||
if USE_INITIAL_STATE:
|
||||
h0 = h0 + i_h * V * K
|
||||
if INPLACE_UPDATE:
|
||||
ht = ht + i_h * V * K
|
||||
|
||||
# Explicit K loop here to reduce register pressure
|
||||
for k_start in range(0, K, 64):
|
||||
# [BV, BK]
|
||||
b_h1 = tl.zeros([BV, 64], dtype=tl.float32)
|
||||
|
||||
# load initial state
|
||||
if USE_INITIAL_STATE:
|
||||
p_h0_1 = tl.make_block_ptr(
|
||||
h0, (V, K), (K, 1), (i_v * BV, k_start), (BV, 64), (1, 0)
|
||||
)
|
||||
b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32)
|
||||
|
||||
# main recurrence
|
||||
for i_t in range(NT):
|
||||
p_h1 = tl.make_block_ptr(
|
||||
h + i_t * stride_h,
|
||||
(V, K),
|
||||
(K, 1),
|
||||
(i_v * BV, k_start),
|
||||
(BV, 64),
|
||||
(1, 0),
|
||||
)
|
||||
tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1))
|
||||
|
||||
b_w = w_desc.load([i_t * BT, k_start])
|
||||
b_v = tl.dot(b_w, tl.trans(b_h1).to(b_w.dtype))
|
||||
b_v = v_desc.load([i_t * BT, i_v * BV]) - b_v
|
||||
|
||||
if SAVE_NEW_VALUE:
|
||||
v_new_desc.store([i_t * BT, i_v * BV], b_v.to(v_new.dtype.element_ty))
|
||||
|
||||
last_idx = min((i_t + 1) * BT, T) - 1
|
||||
if USE_G:
|
||||
b_g_last = tl.load(g + bos * H + last_idx * H + i_h)
|
||||
p_g = tl.make_block_ptr(
|
||||
g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)
|
||||
)
|
||||
b_g = tl.load(p_g, boundary_check=(0,))
|
||||
b_v = b_v * safe_exp(b_g_last - b_g)[:, None]
|
||||
b_g_last = exp(b_g_last)
|
||||
b_h1 = b_h1 * b_g_last
|
||||
|
||||
if USE_GK:
|
||||
o_k1 = tl.arange(0, 64) + k_start
|
||||
b_gk_last1 = tl.load(
|
||||
gk + (bos + last_idx) * H * K + i_h * K + o_k1,
|
||||
mask=(o_k1 < K),
|
||||
other=0.0,
|
||||
)
|
||||
b_h1 *= exp(b_gk_last1)[None, :]
|
||||
b_v = b_v.to(k.dtype.element_ty)
|
||||
|
||||
b_k = tl.trans(k_desc.load([i_t * BT, k_start]))
|
||||
b_h1 += tl.trans(tl.dot(b_k, b_v))
|
||||
|
||||
# epilogue
|
||||
if INPLACE_UPDATE:
|
||||
p_ht = tl.make_block_ptr(
|
||||
ht, (V, K), (K, 1), (i_v * BV, k_start), (BV, 64), (1, 0)
|
||||
)
|
||||
tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
|
||||
|
||||
|
||||
def chunk_gated_delta_rule_fwd_h(
|
||||
k: torch.Tensor,
|
||||
w: torch.Tensor,
|
||||
u: torch.Tensor,
|
||||
g: Optional[torch.Tensor] = None,
|
||||
gk: Optional[torch.Tensor] = None,
|
||||
initial_state: Optional[torch.Tensor] = None,
|
||||
initial_state_indices: Optional[torch.Tensor] = None,
|
||||
save_new_value: bool = True,
|
||||
cu_seqlens: Optional[torch.LongTensor] = None,
|
||||
chunk_indices: Optional[torch.LongTensor] = None,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
B, T, Hg, K, V = *k.shape, u.shape[-1]
|
||||
H = u.shape[-2]
|
||||
BT = CHUNK_SIZE
|
||||
|
||||
if chunk_indices is None and cu_seqlens is not None:
|
||||
chunk_indices = prepare_chunk_indices(cu_seqlens, CHUNK_SIZE)
|
||||
# N: the actual number of sequences in the batch with either equal or variable lengths
|
||||
if cu_seqlens is None:
|
||||
N, NT, chunk_offsets = B, triton.cdiv(T, BT), None
|
||||
else:
|
||||
N, NT, chunk_offsets = (
|
||||
len(cu_seqlens) - 1,
|
||||
len(chunk_indices),
|
||||
prepare_chunk_offsets(cu_seqlens, BT),
|
||||
)
|
||||
assert K <= 256, "current kernel does not support head dimension larger than 256."
|
||||
|
||||
h = k.new_empty(B, NT, H, V, K)
|
||||
|
||||
v_new = torch.empty_like(u) if save_new_value else None
|
||||
|
||||
def grid(meta):
|
||||
return (triton.cdiv(V, meta["BV"]), N * H)
|
||||
|
||||
kernel = chunk_gated_delta_rule_fwd_kernel_h_blockdim64_k_loop
|
||||
|
||||
kernel[grid](
|
||||
k=k,
|
||||
v=u,
|
||||
w=w,
|
||||
v_new=v_new,
|
||||
g=g,
|
||||
gk=gk,
|
||||
h=h,
|
||||
initial_state=initial_state,
|
||||
initial_state_indices=initial_state_indices,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_offsets=chunk_offsets,
|
||||
T=T,
|
||||
H=H,
|
||||
Hg=Hg,
|
||||
K=K,
|
||||
V=V,
|
||||
BT=BT,
|
||||
USE_G=g is not None,
|
||||
USE_GK=gk is not None,
|
||||
USE_INITIAL_STATE=initial_state is not None,
|
||||
INPLACE_UPDATE=True,
|
||||
SAVE_NEW_VALUE=v_new is not None,
|
||||
IS_VARLEN=cu_seqlens is not None,
|
||||
NT_BUCKET=(0 if NT <= 32 else (1 if NT <= 128 else 2)),
|
||||
)
|
||||
return h, v_new
|
||||
@@ -0,0 +1,315 @@
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.layers.attention.fla.index import prepare_chunk_indices
|
||||
from sglang.srt.layers.attention.fla.op import safe_exp
|
||||
from sglang.srt.layers.attention.fla.utils import (
|
||||
autotune_cache_kwargs,
|
||||
)
|
||||
from sglang.srt.layers.attention.fla.wy_fast import recompute_w_u_fwd
|
||||
|
||||
_MERGE_DOT_PRECISION = tl.constexpr("ieee")
|
||||
|
||||
|
||||
@triton.heuristics(
|
||||
{
|
||||
"USE_G": lambda args: args["g"] is not None,
|
||||
"IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
|
||||
}
|
||||
)
|
||||
@triton.autotune(
|
||||
configs=[
|
||||
triton.Config({"BK": BK}, num_warps=num_warps)
|
||||
for BK in [16, 32, 64]
|
||||
for num_warps in [2, 4, 8, 16, 32]
|
||||
],
|
||||
key=["H", "Hg", "K", "BC", "BK", "USE_G", "IS_VARLEN"],
|
||||
**autotune_cache_kwargs,
|
||||
)
|
||||
@triton.jit(do_not_specialize=["T"])
|
||||
def chunk_gated_delta_rule_fwd_kkt_solve_kernel_low_reg(
|
||||
k,
|
||||
g,
|
||||
beta,
|
||||
A,
|
||||
cu_seqlens,
|
||||
chunk_indices,
|
||||
T,
|
||||
H: tl.constexpr,
|
||||
Hg: tl.constexpr,
|
||||
K: tl.constexpr,
|
||||
BT: tl.constexpr,
|
||||
BC: tl.constexpr,
|
||||
BK: tl.constexpr,
|
||||
USE_G: tl.constexpr,
|
||||
IS_VARLEN: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Low-reg version: one [BC,BC] accumulator at a time to minimise register pressure.
|
||||
|
||||
Pass 1: loop over 4 diagonal blocks (tl.static_range unrolls to 4 K-loops).
|
||||
Pass 2: nested loop over off-diagonal distance d=1,2,3 and column j.
|
||||
d=1 (nearest): Ai_{ij} = -Ai_ii @ A_ij_raw @ Ai_jj
|
||||
d>1 (farther): Ai_{ij} = -(Ai_ii @ A_ij_raw
|
||||
+ sum_{m=j+1}^{i-1} Ai_im @ A_mj_raw) @ Ai_jj
|
||||
Each K-loop holds exactly one [BC,BC] accumulator. Raw blocks needed by
|
||||
later correction terms are spilled to upper-triangular scratch slots in A
|
||||
(see _KKT_SCRATCH_COL for the layout; boundary_check makes out-of-bounds
|
||||
stores/loads safe so no runtime `if i_tcX < T` guards are needed).
|
||||
"""
|
||||
i_t, i_bh = tl.program_id(0), tl.program_id(1)
|
||||
i_b, i_h = i_bh // H, i_bh % H
|
||||
|
||||
if IS_VARLEN:
|
||||
i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(
|
||||
chunk_indices + i_t * 2 + 1
|
||||
).to(tl.int32)
|
||||
bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
|
||||
cu_seqlens + i_n + 1
|
||||
).to(tl.int32)
|
||||
T = eos - bos
|
||||
else:
|
||||
bos, eos = i_b * T, i_b * T + T
|
||||
|
||||
if i_t * BT >= T:
|
||||
return
|
||||
|
||||
i_tc0 = i_t * BT
|
||||
k += (bos * Hg + i_h // (H // Hg)) * K
|
||||
A += (bos * H + i_h) * BT
|
||||
|
||||
o_i = tl.arange(0, BC)
|
||||
m_d = o_i[:, None] > o_i[None, :]
|
||||
m_I = o_i[:, None] == o_i[None, :]
|
||||
|
||||
############################################################################
|
||||
# Pass 1: diagonal blocks — one K-loop per sub-chunk (tl.static_range → 4)
|
||||
############################################################################
|
||||
for i_b in tl.static_range(4):
|
||||
i_tci = i_tc0 + i_b * BC
|
||||
m_tci = (i_tci + o_i) < T
|
||||
p_bi = tl.make_block_ptr(
|
||||
beta + bos * H + i_h, (T,), (H,), (i_tci,), (BC,), (0,)
|
||||
)
|
||||
b_bi = tl.load(p_bi, boundary_check=(0,)).to(tl.float32)
|
||||
if USE_G:
|
||||
p_gi = tl.make_block_ptr(
|
||||
g + bos * H + i_h, (T,), (H,), (i_tci,), (BC,), (0,)
|
||||
)
|
||||
b_gi = tl.load(p_gi, boundary_check=(0,)).to(tl.float32)
|
||||
b_A = tl.zeros([BC, BC], dtype=tl.float32)
|
||||
for i_k in range(tl.cdiv(K, BK)):
|
||||
p_k = tl.make_block_ptr(
|
||||
k, (T, K), (Hg * K, 1), (i_tci, i_k * BK), (BC, BK), (1, 0)
|
||||
)
|
||||
b_k = tl.load(p_k, boundary_check=(0, 1))
|
||||
b_A += tl.dot(b_k, tl.trans(b_k))
|
||||
if USE_G:
|
||||
b_A *= safe_exp(b_gi[:, None] - b_gi[None, :])
|
||||
b_A = (
|
||||
tl.where(m_d & (m_tci[:, None] & m_tci[None, :]), b_A, 0.0) * b_bi[:, None]
|
||||
)
|
||||
# Forward substitution: solve (I + A_diag) x = I column by column.
|
||||
# Extra iterations for out-of-bounds rows are no-ops (b_A rows are zero).
|
||||
b_Ai = -b_A
|
||||
for i in range(2, BC):
|
||||
b_a = tl.sum(tl.where((o_i == i)[:, None], -b_A, 0.0), 0)
|
||||
b_a = tl.where(o_i < i, b_a, 0.0)
|
||||
b_a = b_a + tl.sum(b_a[:, None] * b_Ai, 0)
|
||||
b_Ai = tl.where((o_i == i)[:, None], b_a, b_Ai)
|
||||
b_Ai += m_I
|
||||
p_Aii = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tci, i_b * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
tl.store(p_Aii, b_Ai.to(A.dtype.element_ty), boundary_check=(0, 1))
|
||||
|
||||
############################################################################
|
||||
# Pass 2: off-diagonal blocks — one K-loop per (i,j) pair.
|
||||
# Outer loop: d = i-j (diagonal distance) from 1 to 3.
|
||||
# Inner loop: j from 0 to 3-d (so i = j+d).
|
||||
# Processing order ensures Ai_im (m in j+1..i-1) is already in A when needed.
|
||||
# Formula: Ai_ij = -(Ai_ii @ A_ij_raw
|
||||
# + sum_{m=j+1}^{i-1} Ai_im @ A_mj_raw) @ Ai_jj
|
||||
############################################################################
|
||||
for d in tl.static_range(1, 4):
|
||||
for j in tl.static_range(0, 4 - d):
|
||||
i = j + d # compile-time ints from static_range
|
||||
i_tci = i_tc0 + i * BC
|
||||
i_tcj = i_tc0 + j * BC
|
||||
p_bi = tl.make_block_ptr(
|
||||
beta + bos * H + i_h, (T,), (H,), (i_tci,), (BC,), (0,)
|
||||
)
|
||||
b_bi = tl.load(p_bi, boundary_check=(0,)).to(tl.float32)
|
||||
if USE_G:
|
||||
p_gi = tl.make_block_ptr(
|
||||
g + bos * H + i_h, (T,), (H,), (i_tci,), (BC,), (0,)
|
||||
)
|
||||
p_gj = tl.make_block_ptr(
|
||||
g + bos * H + i_h, (T,), (H,), (i_tcj,), (BC,), (0,)
|
||||
)
|
||||
b_gi = tl.load(p_gi, boundary_check=(0,)).to(tl.float32)
|
||||
b_gj = tl.load(p_gj, boundary_check=(0,)).to(tl.float32)
|
||||
# K-loop: accumulate k_i @ k_j^T
|
||||
b_A = tl.zeros([BC, BC], dtype=tl.float32)
|
||||
for i_k in range(tl.cdiv(K, BK)):
|
||||
p_ki = tl.make_block_ptr(
|
||||
k, (T, K), (Hg * K, 1), (i_tci, i_k * BK), (BC, BK), (1, 0)
|
||||
)
|
||||
p_kj = tl.make_block_ptr(
|
||||
k, (T, K), (Hg * K, 1), (i_tcj, i_k * BK), (BC, BK), (1, 0)
|
||||
)
|
||||
b_A += tl.dot(
|
||||
tl.load(p_ki, boundary_check=(0, 1)),
|
||||
tl.trans(tl.load(p_kj, boundary_check=(0, 1))),
|
||||
)
|
||||
if USE_G:
|
||||
b_A *= safe_exp(b_gi[:, None] - b_gj[None, :])
|
||||
b_A *= b_bi[:, None]
|
||||
# Scratch this raw block if a later row in this column needs it as
|
||||
# a correction term. i < 3 means rows i+1..3 exist and will use it.
|
||||
# Scratch column layout (compile-time ternary, evaluated at trace time):
|
||||
# (i=1,j=0)->col1 (i=2,j=1)->col2 (i=2,j=0)->col3
|
||||
if i < 3:
|
||||
sc = 1 if (i == 1 and j == 0) else (2 if (i == 2 and j == 1) else 3)
|
||||
p_s = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tc0, sc * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
tl.store(p_s, b_A.to(A.dtype.element_ty), boundary_check=(0, 1))
|
||||
# Correction sum: sum_{m=j+1}^{i-1} Ai_im @ A_mj_raw
|
||||
# Unrolled manually (d is a compile-time Python int from tl.static_range):
|
||||
# d=1: no corrections; d=2: m=j+1; d=3: m=j+1 then m=j+2
|
||||
b_corr = tl.zeros([BC, BC], dtype=tl.float32)
|
||||
if d >= 2:
|
||||
m1 = j + 1
|
||||
sc_m1j = (
|
||||
1 if (m1 == 1 and j == 0) else (2 if (m1 == 2 and j == 1) else 3)
|
||||
)
|
||||
p_s_m1j = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tc0, sc_m1j * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
b_A_m1j = tl.load(p_s_m1j, boundary_check=(0, 1)).to(tl.float32)
|
||||
p_Ai_im1 = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tci, m1 * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
b_corr += tl.dot(
|
||||
tl.load(p_Ai_im1, boundary_check=(0, 1)).to(tl.float32),
|
||||
b_A_m1j,
|
||||
input_precision=_MERGE_DOT_PRECISION,
|
||||
)
|
||||
if d >= 3:
|
||||
m2 = j + 2
|
||||
sc_m2j = (
|
||||
1 if (m2 == 1 and j == 0) else (2 if (m2 == 2 and j == 1) else 3)
|
||||
)
|
||||
p_s_m2j = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tc0, sc_m2j * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
b_A_m2j = tl.load(p_s_m2j, boundary_check=(0, 1)).to(tl.float32)
|
||||
p_Ai_im2 = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tci, m2 * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
b_corr += tl.dot(
|
||||
tl.load(p_Ai_im2, boundary_check=(0, 1)).to(tl.float32),
|
||||
b_A_m2j,
|
||||
input_precision=_MERGE_DOT_PRECISION,
|
||||
)
|
||||
# Compute and store Ai_ij
|
||||
p_Ai_ii = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tci, i * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
p_Ai_jj = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tcj, j * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
b_Ai_ii = tl.load(p_Ai_ii, boundary_check=(0, 1)).to(tl.float32)
|
||||
b_Ai_jj = tl.load(p_Ai_jj, boundary_check=(0, 1)).to(tl.float32)
|
||||
b_Ai_ij = -tl.dot(
|
||||
tl.dot(b_Ai_ii, b_A, input_precision=_MERGE_DOT_PRECISION) + b_corr,
|
||||
b_Ai_jj,
|
||||
input_precision=_MERGE_DOT_PRECISION,
|
||||
)
|
||||
p_Ai_ij = tl.make_block_ptr(
|
||||
A, (T, BT), (H * BT, 1), (i_tci, j * BC), (BC, BC), (1, 0)
|
||||
)
|
||||
tl.store(p_Ai_ij, b_Ai_ij.to(A.dtype.element_ty), boundary_check=(0, 1))
|
||||
|
||||
|
||||
def chunk_gated_delta_rule_fwd_intra(
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
g: torch.Tensor | None = None,
|
||||
beta: torch.Tensor | None = None,
|
||||
cu_seqlens: torch.LongTensor | None = None,
|
||||
chunk_size: int = 64,
|
||||
chunk_indices: torch.LongTensor | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
r"""
|
||||
GDN intra-chunk forward: fused kkt + solve_tril + recompute_w_u.
|
||||
|
||||
Equivalent to:
|
||||
A = chunk_scaled_dot_kkt_fwd(k, g, beta, ...) # kernel 1
|
||||
A = solve_tril(A, ...) # kernel 2
|
||||
w, u = recompute_w_u_fwd(k, v, beta, A, g, ...) # kernel 3
|
||||
|
||||
Fuses kernels 1+2 into a single kernel, reducing from 3 to 2 kernel launches
|
||||
and eliminating the HBM round-trip for the intermediate A matrix.
|
||||
|
||||
Args:
|
||||
k (torch.Tensor):
|
||||
The key tensor of shape `[B, T, H, K]`.
|
||||
v (torch.Tensor):
|
||||
The value tensor of shape `[B, T, H, V]`.
|
||||
g (torch.Tensor):
|
||||
The cumulative sum of the gate tensor of shape `[B, T, H]`. Default: `None`.
|
||||
beta (torch.Tensor):
|
||||
The beta tensor of shape `[B, T, H]`.
|
||||
cu_seqlens (torch.LongTensor):
|
||||
The cumulative sequence lengths. Default: `None`.
|
||||
chunk_size (int):
|
||||
The chunk size. Default: 64.
|
||||
chunk_indices (torch.LongTensor):
|
||||
Precomputed chunk indices. Default: `None`.
|
||||
|
||||
Returns:
|
||||
w (torch.Tensor): shape `[B, T, H, K]`
|
||||
u (torch.Tensor): shape `[B, T, H, V]`
|
||||
A (torch.Tensor): shape `[B, T, H, BT]`, the solved (I+A)^{-1} matrix
|
||||
"""
|
||||
B, T, Hg, K = k.shape
|
||||
H = beta.shape[-1]
|
||||
BT = chunk_size
|
||||
BC = 16
|
||||
|
||||
if chunk_indices is None and cu_seqlens is not None:
|
||||
chunk_indices = prepare_chunk_indices(cu_seqlens, BT)
|
||||
NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
|
||||
|
||||
# Step 1: fused kkt + solve_tril
|
||||
A = torch.zeros(B, T, H, BT, device=k.device, dtype=k.dtype)
|
||||
kernel = chunk_gated_delta_rule_fwd_kkt_solve_kernel_low_reg
|
||||
kernel[(NT, B * H)](
|
||||
k=k,
|
||||
g=g,
|
||||
beta=beta,
|
||||
A=A,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_indices=chunk_indices,
|
||||
T=T,
|
||||
H=H,
|
||||
Hg=Hg,
|
||||
K=K,
|
||||
BT=BT,
|
||||
BC=BC,
|
||||
)
|
||||
|
||||
# Step 2: recompute_w_u
|
||||
w, u = recompute_w_u_fwd(
|
||||
k=k,
|
||||
v=v,
|
||||
beta=beta,
|
||||
A=A,
|
||||
g_cumsum=g,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_indices=chunk_indices,
|
||||
)
|
||||
return w, u, A
|
||||
@@ -0,0 +1,128 @@
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import triton
|
||||
|
||||
from sglang.srt.layers.attention.fla.fused_sigmoid_gating_recurrent import (
|
||||
fused_sigmoid_gating_delta_rule_update_kernel,
|
||||
)
|
||||
|
||||
|
||||
def fused_sigmoid_gating_delta_rule_update(
|
||||
A_log: torch.Tensor,
|
||||
a: torch.Tensor,
|
||||
dt_bias: torch.Tensor,
|
||||
softplus_beta: float,
|
||||
softplus_threshold: float,
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
b: torch.Tensor,
|
||||
initial_state_source: torch.Tensor,
|
||||
initial_state_indices: torch.Tensor,
|
||||
scale: Optional[float] = None,
|
||||
use_qk_l2norm_in_kernel: bool = False,
|
||||
cu_seqlens: Optional[torch.Tensor] = None,
|
||||
is_kda: bool = False,
|
||||
# Optional parameters for target_verify support
|
||||
disable_state_update: bool = False,
|
||||
intermediate_states_buffer: Optional[torch.Tensor] = None,
|
||||
intermediate_state_indices: Optional[torch.Tensor] = None,
|
||||
cache_steps: Optional[int] = None,
|
||||
retrieve_parent_token: Optional[torch.Tensor] = None,
|
||||
):
|
||||
"""
|
||||
Fused triton implementation of sigmoid gating delta rule update.
|
||||
This function uses a single fused kernel that combines both sigmoid gating computation
|
||||
and the recurrent delta rule update for better performance.
|
||||
|
||||
Supports both decode and target_verify modes:
|
||||
- decode: standard single-step update with state write-back
|
||||
- target_verify: multi-step with intermediate state caching, optional tree attention,
|
||||
and optional state update disable
|
||||
"""
|
||||
B, T, H, K, V = *k.shape, v.shape[-1]
|
||||
stride_q = q.stride()[1]
|
||||
stride_k = k.stride()[1]
|
||||
stride_v = v.stride()[1]
|
||||
stride_b = b.stride()[-2]
|
||||
# Both paths (KDA/GDN) advance p_a once per token, so use the token-axis stride.
|
||||
# For 2D a ([T, ...]) this is stride(0); for 3D a ([B, T, ...]) this is stride(1).
|
||||
# Using stride()[-2] covers GDN [T, HV] and KDA layouts ([T, HV*K] / [B, T, HV*K]).
|
||||
stride_a = a.stride()[-2]
|
||||
HV = v.shape[2]
|
||||
N = B if cu_seqlens is None else len(cu_seqlens) - 1
|
||||
BK, BV = triton.next_power_of_2(K), min(
|
||||
triton.next_power_of_2(V), 16
|
||||
) # use 16 here to reduce register pressure
|
||||
NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV)
|
||||
assert NK == 1, "NK > 1 is not supported yet"
|
||||
num_stages = 3
|
||||
num_warps = 1
|
||||
|
||||
if scale is None:
|
||||
scale = k.shape[-1] ** -0.5
|
||||
else:
|
||||
assert scale > 0, "scale must be positive"
|
||||
|
||||
o = q.new_empty(NK, *v.shape)
|
||||
|
||||
# Prepare retrieve_parent_token strides
|
||||
if retrieve_parent_token is not None:
|
||||
stride_retrieve_parent_token_seq = retrieve_parent_token.stride(0)
|
||||
stride_retrieve_parent_token_token = retrieve_parent_token.stride(1)
|
||||
else:
|
||||
stride_retrieve_parent_token_seq = 0
|
||||
stride_retrieve_parent_token_token = 0
|
||||
|
||||
NP2_T = triton.next_power_of_2(T)
|
||||
|
||||
grid = (NK, NV, N * HV)
|
||||
|
||||
fused_sigmoid_gating_delta_rule_update_kernel[grid](
|
||||
A_log=A_log,
|
||||
a=a,
|
||||
dt_bias=dt_bias,
|
||||
softplus_beta=softplus_beta,
|
||||
softplus_threshold=softplus_threshold,
|
||||
q=q,
|
||||
k=k,
|
||||
v=v,
|
||||
b=b,
|
||||
o=o,
|
||||
h0_source=initial_state_source,
|
||||
h0_indices=initial_state_indices,
|
||||
cu_seqlens=cu_seqlens,
|
||||
intermediate_states_buffer=intermediate_states_buffer,
|
||||
intermediate_state_indices=intermediate_state_indices,
|
||||
cache_steps=0 if cache_steps is None else cache_steps,
|
||||
retrieve_parent_token_ptr=retrieve_parent_token,
|
||||
stride_retrieve_parent_token_seq=stride_retrieve_parent_token_seq,
|
||||
stride_retrieve_parent_token_token=stride_retrieve_parent_token_token,
|
||||
scale=scale,
|
||||
T=T,
|
||||
stride_a=stride_a,
|
||||
stride_q=stride_q,
|
||||
stride_k=stride_k,
|
||||
stride_v=stride_v,
|
||||
stride_b=stride_b,
|
||||
NP2_T=NP2_T,
|
||||
B=B,
|
||||
H=H,
|
||||
HV=HV,
|
||||
K=K,
|
||||
V=V,
|
||||
BK=BK,
|
||||
BV=BV,
|
||||
USE_INITIAL_STATE=initial_state_source is not None,
|
||||
USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel,
|
||||
IS_VARLEN=cu_seqlens is not None,
|
||||
IS_KDA=is_kda,
|
||||
DISABLE_STATE_UPDATE=disable_state_update,
|
||||
CACHE_INTERMEDIATE_STATES=intermediate_states_buffer is not None,
|
||||
HAS_EAGLE_TREE_CUSTOM_ATTN_MASK=retrieve_parent_token is not None,
|
||||
num_warps=num_warps,
|
||||
num_stages=num_stages,
|
||||
)
|
||||
o = o.squeeze(0)
|
||||
return o
|
||||
@@ -19,8 +19,17 @@ from sglang.srt.layers.attention.fla.utils import (
|
||||
SUPPRESS_LEVEL,
|
||||
autocast_custom_fwd,
|
||||
input_guard,
|
||||
is_intel,
|
||||
)
|
||||
|
||||
if is_intel:
|
||||
from sglang.srt.hardware_backend.xpu.kernels.fla.chunk_delta_h import (
|
||||
chunk_gated_delta_rule_fwd_h,
|
||||
)
|
||||
from sglang.srt.hardware_backend.xpu.kernels.fla.chunk_fwd import (
|
||||
chunk_gated_delta_rule_fwd_intra,
|
||||
)
|
||||
|
||||
CHUNK_SIZE = 64
|
||||
|
||||
|
||||
|
||||
@@ -26,8 +26,15 @@ from sglang.srt.layers.attention.fla.l2norm import l2norm_fwd
|
||||
from sglang.srt.layers.attention.fla.op import exp, log
|
||||
from sglang.srt.layers.attention.fla.utils import (
|
||||
check_shared_mem,
|
||||
is_intel,
|
||||
)
|
||||
|
||||
if is_intel:
|
||||
from sglang.srt.hardware_backend.xpu.kernels.fla.chunk_delta_h import (
|
||||
chunk_gated_delta_rule_fwd_h,
|
||||
)
|
||||
|
||||
|
||||
BS_LIST = [32, 64] if check_shared_mem() else [16, 32]
|
||||
|
||||
|
||||
|
||||
@@ -172,6 +172,9 @@ def _layer_norm_fwd_1pass_kernel(
|
||||
@lru_cache
|
||||
def _get_sm_count(device: torch.device) -> int:
|
||||
"""Get and cache the SM count for a given device."""
|
||||
if device.type == "xpu":
|
||||
assert torch.xpu.is_available(), "XPU device is not available"
|
||||
return torch.xpu.get_device_properties(device).gpu_subslice_count
|
||||
props = torch.cuda.get_device_properties(device)
|
||||
return props.multi_processor_count
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import torch
|
||||
from sglang.srt.layers.attention.linear.kernels.kernel_backend import (
|
||||
LinearAttnKernelBase,
|
||||
)
|
||||
from sglang.srt.utils import is_cpu, is_npu
|
||||
from sglang.srt.utils import is_cpu, is_npu, is_xpu
|
||||
|
||||
if not is_cpu():
|
||||
from sglang.srt.layers.attention.fla.chunk import chunk_gated_delta_rule
|
||||
@@ -29,6 +29,10 @@ elif is_cpu():
|
||||
fused_sigmoid_gating_delta_rule_update = (
|
||||
torch.ops.sgl_kernel.fused_sigmoid_gating_delta_rule_update_cpu
|
||||
)
|
||||
elif is_xpu():
|
||||
from sglang.srt.hardware_backend.xpu.kernels.fla.fused_sigmoid_gating_recurrent import (
|
||||
fused_sigmoid_gating_delta_rule_update,
|
||||
)
|
||||
|
||||
|
||||
class TritonGDNKernel(LinearAttnKernelBase):
|
||||
|
||||
@@ -278,6 +278,18 @@ class MRotaryEmbedding(RotaryEmbedding):
|
||||
)
|
||||
return query_out, key_out
|
||||
|
||||
def forward_xpu(
|
||||
self,
|
||||
positions: torch.Tensor,
|
||||
query: torch.Tensor,
|
||||
key: torch.Tensor,
|
||||
fused_set_kv_buffer_arg=None,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
assert positions.ndim in (1, 2)
|
||||
if positions.ndim == 2 and self.mrope_section:
|
||||
return self.forward_triton(positions, query, key)
|
||||
return self.forward_native(positions, query, key, fused_set_kv_buffer_arg)
|
||||
|
||||
@staticmethod
|
||||
def get_rope_index(
|
||||
spatial_merge_size,
|
||||
|
||||
@@ -30,6 +30,7 @@ class HWBackend(Enum):
|
||||
CUDA = auto()
|
||||
AMD = auto()
|
||||
NPU = auto()
|
||||
XPU = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -104,11 +105,22 @@ def register_npu_ci(
|
||||
return None
|
||||
|
||||
|
||||
def register_xpu_ci(
|
||||
est_time: float,
|
||||
suite: str,
|
||||
nightly: bool = False,
|
||||
disabled: Optional[str] = None,
|
||||
):
|
||||
"""Marker for XPU CI registration (parsed via AST; runtime no-op)."""
|
||||
return None
|
||||
|
||||
|
||||
REGISTER_MAPPING = {
|
||||
"register_cpu_ci": HWBackend.CPU,
|
||||
"register_cuda_ci": HWBackend.CUDA,
|
||||
"register_amd_ci": HWBackend.AMD,
|
||||
"register_npu_ci": HWBackend.NPU,
|
||||
"register_xpu_ci": HWBackend.XPU,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,12 +6,17 @@ from sglang.srt.layers.attention.fla.chunk import chunk_gated_delta_rule
|
||||
from sglang.srt.layers.attention.fla.fused_recurrent import (
|
||||
fused_recurrent_gated_delta_rule,
|
||||
)
|
||||
from sglang.test.ci.ci_register import register_cuda_ci
|
||||
from sglang.srt.utils import get_device
|
||||
from sglang.test.ci.ci_register import register_cuda_ci, register_xpu_ci
|
||||
|
||||
register_cuda_ci(est_time=11, stage="base-b", runner_config="1-gpu-large")
|
||||
register_xpu_ci(est_time=30, suite="xpu")
|
||||
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "Test requires CUDA")
|
||||
@unittest.skipIf(
|
||||
not (torch.cuda.is_available() or torch.xpu.is_available()),
|
||||
"Test requires CUDA or XPU",
|
||||
)
|
||||
class TestChunkGatedDeltaRule(unittest.TestCase):
|
||||
"""Test chunk_gated_delta_rule against token-by-token fused_recurrent reference."""
|
||||
|
||||
@@ -68,7 +73,7 @@ class TestChunkGatedDeltaRule(unittest.TestCase):
|
||||
self, B, T_per_seq, H, K, V, pool_size, sequential_indices=False, seed=42
|
||||
):
|
||||
"""Run correctness check for one (B, T_per_seq, H, K, V, pool_size) config."""
|
||||
device = "cuda"
|
||||
device = get_device()
|
||||
dtype = torch.bfloat16
|
||||
T = B * T_per_seq
|
||||
|
||||
|
||||
Reference in New Issue
Block a user