feat(SM70): add 1Cat-vLLM FlashQLA fused GDN CUDA kernel for BI-V100

Source: github.com/1CatAI/1Cat-vLLM (MIT license)
flash_qla/ops/gated_delta_rule/chunk/sm70/

Files added:
- csrc/gdn_forward.cu (1919 lines) — 4 CUDA kernels for SM70/SM75:
  gdn_forward, gdn_forward_vlk_varlen,
  gdn_decode_mixed_qkv_global_state, gdn_decode_mixed_qkv_ddtree_state
- fused_fwd.py — Python wrapper, JIT compiles via torch.utils.cpp_extension.load()
- naive_gdn.py — fla reference PyTorch implementation for fallback
- __init__.py — exports chunk_gated_delta_rule_fwd_sm70

Build: JIT compiled at runtime (TORCH_CUDA_ARCH_LIST=7.0;7.5 -O3)
Deploy: patch_ops.sh copies flash_qla_sm70/ to vllm models dir

qwen3_5.py updated to try import flash_qla_sm70 before PyTorch fallback
This commit is contained in:
Claude
2026-08-10 01:06:32 +00:00
parent 3d5f75fefd
commit 8cf73ad39c
6 changed files with 2621 additions and 5 deletions

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2026 The Qwen team, Alibaba Group.
# Licensed under The MIT License [see LICENSE for details]
from .fused_fwd import (
chunk_gated_delta_rule_fwd_sm70,
chunk_gated_delta_rule_fwd_sm70_vlk_varlen,
resolve_column_groups_per_block_sm70,
)
__all__ = [
"chunk_gated_delta_rule_fwd_sm70",
"chunk_gated_delta_rule_fwd_sm70_vlk_varlen",
"resolve_column_groups_per_block_sm70",
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,490 @@
# Copyright (c) 2026 The Qwen team, Alibaba Group.
# Licensed under The MIT License [see LICENSE for details]
from __future__ import annotations
import os
from pathlib import Path
import torch
from torch.utils.cpp_extension import load
_EXT = None
def _load_ext():
global _EXT
if _EXT is not None:
return _EXT
if not torch.cuda.is_available():
raise RuntimeError("SM70 FlashQLA backend requires CUDA.")
os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "7.0;7.5")
src = Path(__file__).with_name("csrc") / "gdn_forward.cu"
_EXT = load(
name="flash_qla_sm70_gdn_strided",
sources=[str(src)],
extra_cuda_cflags=["-O3"],
extra_cflags=["-O3"],
verbose=bool(int(os.environ.get("FLASH_QLA_SM70_VERBOSE_BUILD", "0"))),
)
return _EXT
def _check_inputs(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
initial_state: torch.Tensor | None,
) -> None:
tensors = [q, k, v, g, beta]
if initial_state is not None:
tensors.append(initial_state)
if any(not tensor.is_cuda for tensor in tensors):
raise ValueError("SM70 GDN tensors must be CUDA tensors.")
if any(tensor.device != q.device for tensor in tensors):
raise ValueError("SM70 GDN tensors must be on the same CUDA device.")
if any(not tensor.is_contiguous() for tensor in tensors):
raise ValueError("SM70 GDN tensors must be contiguous.")
if q.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("SM70 GDN backend supports fp16, bf16, and fp32 tensors.")
if k.dtype != q.dtype or v.dtype != q.dtype:
raise ValueError("q, k, and v must have the same dtype.")
if g.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("g must be fp16, bf16, or fp32.")
if beta.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("beta must be fp16, bf16, or fp32.")
if initial_state is not None and initial_state.dtype not in (
torch.float16,
torch.bfloat16,
torch.float32,
):
raise ValueError("initial_state must be fp16, bf16, or fp32.")
if q.ndim != 4 or k.ndim != 4 or v.ndim != 4:
raise ValueError("q, k, and v must have shape [B, T, H, D].")
if g.ndim != 3 or beta.ndim != 3:
raise ValueError("g and beta must have shape [B, T, Hv].")
if q.shape != k.shape:
raise ValueError("q and k must have the same shape.")
batch, tokens, q_heads, k_dim = q.shape
_, _, v_heads, v_dim = v.shape
if v.shape[0] != batch or v.shape[1] != tokens:
raise ValueError("v must have shape [B, T, Hv, V] matching q/k.")
if g.shape != beta.shape or g.shape != v.shape[:3]:
raise ValueError("g and beta must have shape [B, T, Hv].")
if v_heads % q_heads != 0:
raise ValueError("Hv must be divisible by Hq.")
if k_dim != 128 or v_dim != 128:
raise ValueError("SM70 FlashQLA backend currently supports K=V=128.")
if initial_state is not None and initial_state.shape != (
batch,
v_heads,
k_dim,
v_dim,
):
raise ValueError("initial_state must have shape [B, Hv, K, V].")
def _check_vlk_varlen_inputs(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
initial_state: torch.Tensor | None,
cu_seqlens: torch.Tensor,
output: torch.Tensor | None = None,
validate_cu_seqlens: bool = True,
) -> None:
tensors = [q, k, v, g, beta, cu_seqlens]
if initial_state is not None:
tensors.append(initial_state)
if output is not None:
tensors.append(output)
if any(not tensor.is_cuda for tensor in tensors):
raise ValueError("SM70 GDN tensors must be CUDA tensors.")
if any(tensor.device != q.device for tensor in tensors):
raise ValueError("SM70 GDN tensors must be on the same CUDA device.")
if any(not tensor.is_contiguous() for tensor in tensors):
raise ValueError("SM70 GDN tensors must be contiguous.")
if q.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("SM70 GDN backend supports fp16, bf16, and fp32 tensors.")
if k.dtype != q.dtype or v.dtype != q.dtype:
raise ValueError("q, k, and v must have the same dtype.")
if g.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("g must be fp16, bf16, or fp32.")
if beta.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("beta must be fp16, bf16, or fp32.")
if initial_state is not None and initial_state.dtype not in (
torch.float16,
torch.bfloat16,
torch.float32,
):
raise ValueError("initial_state must be fp16, bf16, or fp32.")
if cu_seqlens.dtype != torch.int32:
raise ValueError("cu_seqlens must be int32.")
if q.ndim != 4 or k.ndim != 4 or v.ndim != 4:
raise ValueError("q, k, and v must have shape [1, T, H, D].")
if q.shape[0] != 1:
raise ValueError("SM70 varlen GDN expects flattened q/k/v with batch=1.")
if g.ndim != 3 or beta.ndim != 3:
raise ValueError("g and beta must have shape [1, T, Hv].")
if cu_seqlens.ndim != 1 or cu_seqlens.numel() < 2:
raise ValueError("cu_seqlens must have shape [N + 1].")
if q.shape != k.shape:
raise ValueError("q and k must have the same shape.")
_, tokens, q_heads, k_dim = q.shape
_, _, v_heads, v_dim = v.shape
num_sequences = cu_seqlens.numel() - 1
if v.shape[0] != 1 or v.shape[1] != tokens:
raise ValueError("v must have shape [1, T, Hv, V] matching q/k.")
if g.shape != beta.shape or g.shape != v.shape[:3]:
raise ValueError("g and beta must have shape [1, T, Hv].")
if v_heads % q_heads != 0:
raise ValueError("Hv must be divisible by Hq.")
if k_dim != 128 or v_dim != 128:
raise ValueError("SM70 FlashQLA backend currently supports K=V=128.")
if initial_state is not None and initial_state.shape != (
num_sequences,
v_heads,
v_dim,
k_dim,
):
raise ValueError("initial_state must have shape [N, Hv, V, K].")
if output is not None:
if output.dtype != v.dtype:
raise ValueError("output must match v dtype.")
if output.shape != (1, tokens, v_heads, v_dim):
raise ValueError("output must have shape [1, T, Hv, V].")
if validate_cu_seqlens:
cu_cpu = cu_seqlens.detach().cpu()
if int(cu_cpu[0]) != 0:
raise ValueError("cu_seqlens must start at 0.")
if int(cu_cpu[-1]) != tokens:
raise ValueError("cu_seqlens must end at the flattened token count.")
if not bool((cu_cpu[1:] >= cu_cpu[:-1]).all()):
raise ValueError("cu_seqlens must be non-decreasing.")
def chunk_gated_delta_rule_fwd_sm70(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
scale: float | None = None,
initial_state: torch.Tensor | None = None,
output_final_state: bool = True,
gate_is_exp: bool = False,
) -> tuple[torch.Tensor, torch.Tensor | None]:
"""Run the experimental SM70/SM75 forward GDN backend.
This keeps the public FlashQLA tensor contract:
q/k: [B, T, Hq, K], v/o: [B, T, Hv, V], state: [B, Hv, K, V].
"""
_check_inputs(q, k, v, g, beta, initial_state)
if scale is None:
scale = q.shape[-1] ** -0.5
ext = _load_ext()
output, final_state = ext.gdn_forward(
q,
k,
v,
g,
beta,
initial_state,
float(scale),
output_final_state,
gate_is_exp,
)
if not output_final_state:
final_state = None
return output, final_state
def chunk_gated_delta_rule_fwd_sm70_vlk_varlen(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
cu_seqlens: torch.Tensor,
scale: float | None = None,
initial_state: torch.Tensor | None = None,
output_final_state: bool = True,
validate_cu_seqlens: bool = True,
gate_is_exp: bool = False,
output: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor | None]:
"""Run SM70/SM75 forward with vLLM-only state layout [N, Hv, V, K].
This is not a public FlashQLA varlen drop-in: K and V are both 128 for
Qwen GDN, so the vLLM layout cannot be shape-distinguished from the public
[N, Hv, K, V] contract.
"""
_check_vlk_varlen_inputs(
q,
k,
v,
g,
beta,
initial_state,
cu_seqlens,
output,
validate_cu_seqlens=validate_cu_seqlens,
)
if scale is None:
scale = q.shape[-1] ** -0.5
ext = _load_ext()
output, final_state = ext.gdn_forward_vlk_varlen(
q,
k,
v,
g,
beta,
initial_state,
cu_seqlens,
float(scale),
output_final_state,
validate_cu_seqlens,
gate_is_exp,
output,
)
if not output_final_state:
final_state = None
return output, final_state
def gdn_decode_mixed_qkv_global_state_sm70(
mixed_qkv: torch.Tensor,
a: torch.Tensor,
b: torch.Tensor,
A_log: torch.Tensor,
dt_bias: torch.Tensor,
state: torch.Tensor,
state_indices: torch.Tensor,
output: torch.Tensor,
scale: float | None = None,
use_qk_l2norm_in_kernel: bool = True,
) -> torch.Tensor:
"""Run fused SM70 mixed-QKV decode against vLLM global state slots."""
tensors = [mixed_qkv, a, b, A_log, dt_bias, state, state_indices, output]
if any(not tensor.is_cuda for tensor in tensors):
raise ValueError("SM70 GDN decode tensors must be CUDA tensors.")
if any(tensor.device != mixed_qkv.device for tensor in tensors):
raise ValueError("SM70 GDN decode tensors must be on the same CUDA device.")
contiguous_tensors = {
"a": a,
"b": b,
"A_log": A_log,
"dt_bias": dt_bias,
"state_indices": state_indices,
"output": output,
}
non_contiguous = [
name
for name, tensor in contiguous_tensors.items()
if not tensor.is_contiguous()
]
if non_contiguous:
raise ValueError(
"SM70 GDN decode tensors must be contiguous except mixed_qkv/state; "
f"non-contiguous={non_contiguous}"
)
if mixed_qkv.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("mixed_qkv must be fp16, bf16, or fp32.")
if a.dtype != mixed_qkv.dtype or b.dtype != mixed_qkv.dtype:
raise ValueError("a and b must match mixed_qkv dtype.")
if output.dtype != mixed_qkv.dtype:
raise ValueError("output must match mixed_qkv dtype.")
if A_log.dtype != torch.float32:
raise ValueError("A_log must be float32.")
if dt_bias.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("dt_bias must be fp16, bf16, or fp32.")
if state_indices.dtype != torch.int32:
raise ValueError("state_indices must be int32.")
if mixed_qkv.ndim != 2 or a.ndim != 2 or b.ndim != 2:
raise ValueError("mixed_qkv, a, and b must be rank-2 tensors.")
if mixed_qkv.stride(1) != 1 or mixed_qkv.stride(0) < mixed_qkv.shape[1]:
raise ValueError(
"mixed_qkv must have dense columns and row stride >= logical width; "
f"shape={tuple(mixed_qkv.shape)} stride={tuple(mixed_qkv.stride())}"
)
if state.ndim != 4 or output.ndim != 3:
raise ValueError("state must be [slots,Hv,V,K], output [T,Hv,V].")
tokens = mixed_qkv.shape[0]
_, v_heads, v_dim, k_dim = state.shape
if k_dim != 128 or v_dim != 128:
raise ValueError("SM70 FlashQLA decode currently supports K=V=128.")
if state.stride()[1:] != (v_dim * k_dim, k_dim, 1):
raise ValueError(
"state inner layout must be [slots,Hv,V,K] with contiguous [Hv,V,K] "
f"pages; got stride={tuple(state.stride())}"
)
if a.shape != (tokens, v_heads) or b.shape != (tokens, v_heads):
raise ValueError("a/b must have shape [T,Hv].")
if A_log.shape != (v_heads,) or dt_bias.shape != (v_heads,):
raise ValueError("A_log/dt_bias must have shape [Hv].")
if state_indices.shape != (tokens,):
raise ValueError("state_indices must have shape [T].")
if output.shape != (tokens, v_heads, v_dim):
raise ValueError("output must have shape [T,Hv,V].")
if scale is None:
scale = k_dim**-0.5
ext = _load_ext()
ext.gdn_decode_mixed_qkv_global_state(
mixed_qkv,
a,
b,
A_log,
dt_bias,
state,
state_indices,
output,
float(scale),
bool(use_qk_l2norm_in_kernel),
)
return output
def gdn_decode_mixed_qkv_ddtree_state_sm70(
mixed_qkv: torch.Tensor,
a: torch.Tensor,
b: torch.Tensor,
A_log: torch.Tensor,
dt_bias: torch.Tensor,
state: torch.Tensor,
state_indices: torch.Tensor,
parent_ids: torch.Tensor,
num_accepted_tokens: torch.Tensor,
cu_seqlens: torch.Tensor,
output: torch.Tensor,
scale: float | None = None,
use_qk_l2norm_in_kernel: bool = True,
) -> torch.Tensor:
"""Run parent-aware DDTree mixed-QKV decode against vLLM global state."""
tensors = [
mixed_qkv,
a,
b,
A_log,
dt_bias,
state,
state_indices,
parent_ids,
num_accepted_tokens,
cu_seqlens,
output,
]
if any(not tensor.is_cuda for tensor in tensors):
raise ValueError("SM70 DDTree GDN tensors must be CUDA tensors.")
if any(tensor.device != mixed_qkv.device for tensor in tensors):
raise ValueError("SM70 DDTree GDN tensors must be on the same CUDA device.")
contiguous_tensors = {
"a": a,
"b": b,
"A_log": A_log,
"dt_bias": dt_bias,
"state_indices": state_indices,
"parent_ids": parent_ids,
"num_accepted_tokens": num_accepted_tokens,
"cu_seqlens": cu_seqlens,
"output": output,
}
non_contiguous = [
name
for name, tensor in contiguous_tensors.items()
if not tensor.is_contiguous()
]
if non_contiguous:
raise ValueError(
"SM70 DDTree GDN tensors must be contiguous except mixed_qkv/state; "
f"non-contiguous={non_contiguous}"
)
if mixed_qkv.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("mixed_qkv must be fp16, bf16, or fp32.")
if a.dtype != mixed_qkv.dtype or b.dtype != mixed_qkv.dtype:
raise ValueError("a and b must match mixed_qkv dtype.")
if output.dtype != mixed_qkv.dtype:
raise ValueError("output must match mixed_qkv dtype.")
if A_log.dtype != torch.float32:
raise ValueError("A_log must be float32.")
if dt_bias.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise ValueError("dt_bias must be fp16, bf16, or fp32.")
if state_indices.dtype != torch.int32:
raise ValueError("state_indices must be int32.")
if parent_ids.dtype != torch.int32:
raise ValueError("parent_ids must be int32.")
if num_accepted_tokens.dtype != torch.int32:
raise ValueError("num_accepted_tokens must be int32.")
if cu_seqlens.dtype != torch.int32:
raise ValueError("cu_seqlens must be int32.")
if mixed_qkv.ndim != 2 or a.ndim != 2 or b.ndim != 2:
raise ValueError("mixed_qkv, a, and b must be rank-2 tensors.")
if mixed_qkv.stride(1) != 1 or mixed_qkv.stride(0) < mixed_qkv.shape[1]:
raise ValueError(
"mixed_qkv must have dense columns and row stride >= logical width; "
f"shape={tuple(mixed_qkv.shape)} stride={tuple(mixed_qkv.stride())}"
)
if state.ndim != 4 or output.ndim != 3:
raise ValueError("state must be [slots,Hv,V,K], output [T,Hv,V].")
if state_indices.ndim != 2 or parent_ids.ndim != 2:
raise ValueError("state_indices and parent_ids must be rank-2 tensors.")
if parent_ids.shape != state_indices.shape:
raise ValueError("parent_ids must match state_indices shape.")
tokens = mixed_qkv.shape[0]
num_sequences = state_indices.shape[0]
_, v_heads, v_dim, k_dim = state.shape
if k_dim != 128 or v_dim != 128:
raise ValueError("SM70 FlashQLA DDTree decode currently supports K=V=128.")
if state.stride()[1:] != (v_dim * k_dim, k_dim, 1):
raise ValueError(
"state inner layout must be [slots,Hv,V,K] with contiguous [Hv,V,K] "
f"pages; got stride={tuple(state.stride())}"
)
if a.shape != (tokens, v_heads) or b.shape != (tokens, v_heads):
raise ValueError("a/b must have shape [T,Hv].")
if A_log.shape != (v_heads,) or dt_bias.shape != (v_heads,):
raise ValueError("A_log/dt_bias must have shape [Hv].")
if num_accepted_tokens.shape != (num_sequences,):
raise ValueError("num_accepted_tokens must have shape [N].")
if cu_seqlens.shape != (num_sequences + 1,):
raise ValueError("cu_seqlens must have shape [N + 1].")
if output.shape != (tokens, v_heads, v_dim):
raise ValueError("output must have shape [T,Hv,V].")
if scale is None:
scale = k_dim**-0.5
ext = _load_ext()
ext.gdn_decode_mixed_qkv_ddtree_state(
mixed_qkv,
a,
b,
A_log,
dt_bias,
state,
state_indices,
parent_ids,
num_accepted_tokens,
cu_seqlens,
output,
float(scale),
bool(use_qk_l2norm_in_kernel),
)
return output
def resolve_column_groups_per_block_sm70(
tokens: int,
q_heads: int,
v_heads: int,
) -> int:
ext = _load_ext()
return int(ext.resolve_column_groups_per_block(tokens, q_heads, v_heads))

View File

@@ -0,0 +1,161 @@
# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
# For a list of all contributors, visit:
# https://github.com/fla-org/flash-linear-attention/graphs/contributors
import torch
import torch.nn.functional as F
from einops import rearrange
def naive_recurrent_gated_delta_rule(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
beta: torch.Tensor,
g: torch.Tensor,
scale: float = None,
initial_state: torch.Tensor = None,
output_final_state: bool = False,
):
"""
Reference PyTorch implementation of recurrent gated delta rule.
Args:
q: [B, T, H, K]
k: [B, T, H, K]
v: [B, T, H, V]
beta: [B, T, H]
g: [B, T, H]
scale: float, optional
initial_state: [B, H, K, V], optional
output_final_state: bool
Returns:
o: [B, T, H, V]
final_state: [B, H, K, V] if output_final_state else None
"""
q, k, v, beta, g = map(lambda x: x.transpose(1, 2).contiguous().to(torch.float32), [q, k, v, beta, g])
B, H, T, K, V = *k.shape, v.shape[-1]
o = torch.zeros(B, H, T, V).to(v)
h = torch.zeros(B, H, K, V).to(v)
if initial_state is not None:
h = initial_state.to(torch.float32)
if scale is None:
scale = 1 / (q.shape[-1] ** 0.5)
q = q * scale
for i in range(T):
b_q = q[:, :, i]
b_k = k[:, :, i]
b_v = v[:, :, i].clone()
h = h.clone() * g[:, :, i].exp()[..., None, None]
b_beta = beta[:, :, i]
b_v = b_v - (h.clone() * b_k[..., None]).sum(-2)
b_v = b_v * b_beta[..., None]
h = h.clone() + b_k.unsqueeze(-1) * b_v.unsqueeze(-2)
o[:, :, i] = torch.einsum('bhd,bhdm->bhm', b_q, h)
if not output_final_state:
h = None
o = o.transpose(1, 2).contiguous()
return o, h
def naive_chunk_gated_delta_rule(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
chunk_size: int = 64,
scale: float = None,
initial_state: torch.Tensor = None,
output_final_state: bool = False,
):
"""
Reference PyTorch implementation of chunk gated delta rule.
Args:
q: [B, T, H, K]
k: [B, T, H, K]
v: [B, T, H, V]
g: [B, T, H]
beta: [B, T, H]
chunk_size: int
scale: float, optional
initial_state: [B, H, K, V], optional
output_final_state: bool
Returns:
o: [B, T, H, V]
final_state: [B, H, K, V] if output_final_state else None
"""
BT = chunk_size
if scale is None:
scale = 1 / (q.shape[-1] ** 0.5)
q, k, v, beta, g = map(lambda x: x.transpose(1, 2).contiguous().to(torch.float32), [q, k, v, beta, g])
T = q.shape[-2]
pad_len = (BT - (T % BT)) % BT
if pad_len > 0:
q = F.pad(q, (0, 0, 0, pad_len))
k = F.pad(k, (0, 0, 0, pad_len))
v = F.pad(v, (0, 0, 0, pad_len))
beta = F.pad(beta, (0, pad_len))
g = F.pad(g, (0, pad_len))
q, k, v, beta, g = map(lambda x: x.to(torch.float32), [q, k, v, beta, g])
decay = g
chunk_size = BT
b, h, l, d_k = q.shape
d_v = v.shape[-1]
q = q * scale
v = v * beta[..., None]
k_beta = k * beta[..., None]
assert l % chunk_size == 0
# note that diagonal is masked.
mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0)
q, k, v, k_beta, decay = map(
lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size),
[q, k, v, k_beta, decay.unsqueeze(-1)],
)
decay = decay.squeeze(-1).cumsum(-1)
decay_exp = decay.exp()[..., None]
L_mask = ((decay.unsqueeze(-1) - decay.unsqueeze(-2)).tril().exp().float()).tril()
attn = -((k_beta @ k.transpose(-1, -2)) * L_mask).masked_fill(mask, 0)
for i in range(1, chunk_size):
attn[..., i, :i] = attn[..., i, :i].clone() + (attn[..., i, :i, None].clone() * attn[..., :i, :i].clone()).sum(-2)
attn = attn + torch.eye(chunk_size, dtype=torch.float, device=q.device)
attn = attn
k_cumsum = attn @ v
k_cumdecay = attn @ (k_beta * decay_exp)
v = k_cumsum
S = k.new_zeros(b, h, d_k, d_v)
if initial_state is not None:
S = initial_state.to(torch.float32)
o = torch.zeros_like(v)
mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1)
for i in range(0, l // chunk_size):
q_i, k_i, v_i = q[:, :, i], k[:, :, i], v[:, :, i]
attn = (q_i @ k_i.transpose(-1, -2) * L_mask[:, :, i]).masked_fill_(mask, 0)
v_prime = (k_cumdecay[:, :, i]) @ S
v_new = v_i - v_prime
o_inter = (q_i * decay[:, :, i, :, None].exp()) @ S
o[:, :, i] = o_inter + attn @ v_new
S = S * decay[:, :, i, -1, None, None].exp() + (k_i * (decay[:, :, i, -1, None] - decay[:, :, i]).exp()
[..., None]).transpose(-1, -2) @ v_new
if not output_final_state:
S = None
# unpad
o = rearrange(o, 'b h n c d -> b h (n c) d')
o = o[:, :, :T]
o = o.transpose(1, 2)
return o, S

View File

@@ -175,6 +175,22 @@ if [ -n "$VLLM2" ]; then
cp ./chat_utils.py "$VLLM2/entrypoints/chat_utils.py" 2>/dev/null || true
fi
echo "[patch_ops] DONE — all patches deployed"
echo "[patch_ops] Deployed: qwen3_5.py, paged_attn.py, mamba_cache.py, sequence.py, scheduler.py, xformers patches, tool/reasoning parsers, serving layer"
echo "[patch_ops] NOT deployed (using base image native): model_runner.py, _custom_ops.py, sampler.py, logits_processor.py, arg_utils.py"
echo "[patch_ops] DONE — SM70 GDN kernel + serving layer + engine patches deployed"
echo "[patch_ops] Deployed: qwen3_5.py, flash_qla_sm70 (SM70 GDN CUDA kernel), paged_attn.py, mamba_cache.py, sequence.py, scheduler.py, xformers patches, serving layer"
echo "[patch_ops] SM70 GDN kernel: JIT compiles on first forward pass (~2min), then cached"
echo "[patch_ops] NOT deployed (base image native): model_runner.py, _custom_ops.py, sampler.py, logits_processor.py, arg_utils.py"
# Deploy flash_qla SM70 GDN kernel (from 1Cat-vLLM, MIT license)
# This is a fused CUDA kernel for GatedDeltaNet on SM70/SM75 (V100/BI-V100)
# JIT compiled at runtime via torch.utils.cpp_extension.load()
FLASH_QLA_DST="$VLLM/model_executor/models/flash_qla_sm70"
if [ -d "./flash_qla_sm70" ]; then
rm -rf "$FLASH_QLA_DST" 2>/dev/null
cp -r ./flash_qla_sm70 "$FLASH_QLA_DST" 2>/dev/null && \
echo "[patch_ops] flash_qla_sm70 deployed to $FLASH_QLA_DST" || true
# Also deploy to VLLM2 if present
if [ -n "$VLLM2" ]; then
rm -rf "$VLLM2/model_executor/models/flash_qla_sm70" 2>/dev/null
cp -r ./flash_qla_sm70 "$VLLM2/model_executor/models/flash_qla_sm70" 2>/dev/null || true
fi
fi

View File

@@ -76,19 +76,35 @@ _corex_moe_module = None
_corex_gdn_available = False
_corex_moe_available = False
# SM70 FlashQLA GDN kernel (from 1Cat-vLLM, MIT license)
# Fused CUDA kernel for GatedDeltaNet on SM70/SM75 (V100/BI-V100)
# JIT compiled via torch.utils.cpp_extension.load() on first call
_flash_qla_sm70 = None
_flash_qla_available = False
try:
from vllm.model_executor.models.flash_qla_sm70 import (
chunk_gated_delta_rule_fwd_sm70,
chunk_gated_delta_rule_fwd_sm70_vlk_varlen,
)
_flash_qla_available = True
logger.info("FlashQLA SM70 GDN module found — fused CUDA kernel available (JIT on first call)")
except ImportError as e:
logger.warning("FlashQLA SM70 GDN not found (%s) — using PyTorch GDN", e)
try:
from vllm.model_executor.models import corex_gdn as _corex_gdn_module
_corex_gdn_available = True
logger.info("CoreX GDN module found — fused GDN kernels available")
except ImportError:
pass # expected if not packaged; ixformer ops used instead
pass
try:
from vllm.model_executor.models import corex_moe as _corex_moe_module
_corex_moe_available = True
logger.info("CoreX MoE module found — fused MoE kernels available")
except ImportError:
pass # expected; MoE uses PyTorch loop
pass
# ---------------------------------------------------------------------------