multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
7755bd1 verified
Raw
History Blame Contribute Delete
22.8 kB
import torch
import triton
import triton.language as tl
from functools import lru_cache
try:
from flash_attn.cute.block_sparsity import BlockSparseTensorsTorch
FLASH_ATTN_BLOCK_SPARSE_AVAILABLE = True
except (ImportError, ModuleNotFoundError):
BlockSparseTensorsTorch = None
FLASH_ATTN_BLOCK_SPARSE_AVAILABLE = False
__all__ = [
'get_sm_80_120_block_map',
'get_sm_90_100_block_map',
'_attention',
]
@triton.jit
def block_mean_kernel(
X, XM,
H,
L: tl.constexpr,
D: tl.constexpr,
BLOCK_L: tl.constexpr,
):
idx_l = tl.program_id(0)
idx_bh = tl.program_id(1)
idx_b = idx_bh // H
idx_h = idx_bh % H
offs_l = idx_l * BLOCK_L + tl.arange(0, BLOCK_L)
offs_d = tl.arange(0, D)
# Input: (B, L, H, D) contiguous -> stride_l = H*D
x_base = idx_b * L * H * D + idx_h * D
x = tl.load(X + x_base + offs_l[:, None] * (H * D) + offs_d[None, :], mask=offs_l[:, None] < L)
# Output: (B, L_BLOCKS, H, D) contiguous
L_BLOCKS = (L + BLOCK_L - 1) // BLOCK_L
xm_offset = idx_b * L_BLOCKS * H * D + idx_l * H * D + idx_h * D
nx = min(BLOCK_L, L - idx_l * BLOCK_L)
x_mean = tl.sum(x, axis=0, dtype=tl.float32) / nx
tl.store(XM + xm_offset + offs_d, x_mean.to(XM.dtype.element_ty))
def mean_pool_blhd(x, BLK, out=None):
"""Triton mean pool, input (B, L, H, D) -> output (B, L_BLOCKS, H, D).
If out is provided with exact matching shape, reuse it (kernel needs contiguous layout)."""
B, L, H, D = x.shape
L_BLOCKS = (L + BLK - 1) // BLK
if out is not None and out.shape == (B, L_BLOCKS, H, D):
x_mean = out
else:
x_mean = torch.empty((B, L_BLOCKS, H, D), device=x.device, dtype=x.dtype)
grid = (L_BLOCKS, B * H)
block_mean_kernel[grid](x, x_mean, H, L, D, BLK, num_warps=4, num_stages=3)
return x_mean
@triton.jit
def full_selected_block_score_from_frames_kernel(
QP,
KP,
KEEP,
SCORES,
H: tl.constexpr,
Q_BLOCKS: tl.constexpr,
K_BLOCKS: tl.constexpr,
D: tl.constexpr,
KEEP_FRAMES: tl.constexpr,
KEEP_OFFSET: tl.constexpr,
KEEP_SINK: tl.constexpr,
KEEP_NEAR: tl.constexpr,
FRAME_BLK: tl.constexpr,
F_PAST: tl.constexpr,
BLOCK_K: tl.constexpr,
BLOCK_D: tl.constexpr,
):
idx_k = tl.program_id(0)
idx_bhq = tl.program_id(1).to(tl.int64)
idx_q = idx_bhq % Q_BLOCKS
idx_bh = idx_bhq // Q_BLOCKS
idx_b = idx_bh // H
idx_h = idx_bh % H
offs_k = idx_k * BLOCK_K + tl.arange(0, BLOCK_K)
offs_d = tl.arange(0, BLOCK_D)
past_blocks: tl.constexpr = F_PAST * FRAME_BLK
is_tail = offs_k >= past_blocks
frame_id = offs_k // FRAME_BLK
keep_base = (idx_bh * Q_BLOCKS + idx_q) * KEEP_FRAMES
is_sink = frame_id < KEEP_SINK
is_near = (frame_id >= F_PAST - KEEP_NEAR) & (frame_id < F_PAST)
is_keep = is_tail | is_sink | is_near
for i in tl.static_range(0, KEEP_FRAMES):
keep_frame = tl.load(KEEP + keep_base + i).to(tl.int64) + KEEP_OFFSET
is_keep = is_keep | (frame_id == keep_frame)
is_keep = is_keep & (offs_k < K_BLOCKS)
q_base = idx_b * Q_BLOCKS * H * D + idx_q * H * D + idx_h * D
q = tl.load(QP + q_base + offs_d, mask=offs_d < D, other=0.0)
k_base = idx_b * K_BLOCKS * H * D + offs_k[:, None] * H * D + idx_h * D
k = tl.load(KP + k_base + offs_d[None, :], mask=is_keep[:, None] & (offs_d[None, :] < D), other=0.0)
score = tl.sum(k * q[None, :], axis=1)
score = tl.where(is_keep, score, -float("inf"))
score_base = (idx_bh * Q_BLOCKS + idx_q) * K_BLOCKS
tl.store(SCORES + score_base + offs_k, score, mask=offs_k < K_BLOCKS)
def score_full_selected_blocks_from_frames(pooled_qblocks, pooled_kblocks, keep_idx, frame_blk, f_past, keep_offset=0, keep_sink=0, keep_near=0, BLOCK_K=64):
B, Q, H, D = pooled_qblocks.shape
K = pooled_kblocks.shape[1]
scores = torch.empty((B, H, Q, K), device=pooled_qblocks.device, dtype=pooled_qblocks.dtype)
block_d = triton.next_power_of_2(D)
grid = (triton.cdiv(K, BLOCK_K), B * H * Q)
full_selected_block_score_from_frames_kernel[grid](
pooled_qblocks,
pooled_kblocks,
keep_idx,
scores,
H,
Q,
K,
D,
keep_idx.shape[-1],
keep_offset,
keep_sink,
keep_near,
frame_blk,
f_past,
BLOCK_K,
block_d,
num_warps=4,
num_stages=3,
)
return scores
def get_sm_80_120_block_map_1stage(q, k, topk_ratio, BLKQ=64, BLKK=64):
# q, k: (B, L, H, D)
pooled_qblocks = mean_pool_blhd(q, BLKQ) # (B, M_BLOCKS, H, D)
pooled_kblocks = mean_pool_blhd(k, BLKK) # (B, N_BLOCKS, H, D)
pooled_score = pooled_qblocks.transpose(1, 2) @ pooled_kblocks.permute(0, 2, 3, 1)
K = pooled_score.shape[-1]
topk = min(K, int(topk_ratio * K))
lut = torch.topk(pooled_score, topk, dim=-1, sorted=False).indices
sparse_map = torch.zeros_like(pooled_score, dtype=torch.int8)
sparse_map.scatter_(-1, lut, 1)
return sparse_map, lut, topk
def _select_2stage_middle_frames(pooled_qblocks, pooled_kblocks, frame_blk, f_past, keep_frames, keep_sink, keep_near):
if keep_sink < 0 or keep_near < 0:
raise ValueError("keep_sink and keep_near must be non-negative.")
if keep_sink + keep_near > keep_frames:
raise ValueError("keep_sink + keep_near must be <= keep_frames.")
B, Q, H, _ = pooled_qblocks.shape
middle_start = keep_sink
middle_end = f_past - keep_near
middle_frames = middle_end - middle_start
keep_middle = keep_frames - keep_sink - keep_near
if keep_middle == 0:
return torch.empty((B, H, Q, 0), device=pooled_qblocks.device, dtype=torch.int64)
pooled_middle_frames = (
pooled_kblocks[:, middle_start * frame_blk:middle_end * frame_blk]
.reshape(B, middle_frames, frame_blk, H, -1)
.mean(dim=2)
)
pooled_frame_score = pooled_qblocks.transpose(1, 2) @ pooled_middle_frames.permute(0, 2, 3, 1)
return torch.topk(pooled_frame_score, keep_middle, dim=-1, largest=True, sorted=False).indices
def get_sm_80_120_block_map_2stage(q, k, topk_ratio, BLKQ=64, BLKK=64, frame_seq=1536, keep_frames=6, keep_sink=0, keep_near=0):
# q, k: (B, L, H, D)
pooled_qblocks = mean_pool_blhd(q, BLKQ) # (B, M_BLOCKS, H, D)
pooled_kblocks = mean_pool_blhd(k, BLKK) # (B, N_BLOCKS, H, D)
K = pooled_kblocks.shape[1]
frame_blk = frame_seq // BLKK
F = K // frame_blk
num_frame_per_block = q.shape[1] // frame_seq
F_past = F - num_frame_per_block
keep_idx = _select_2stage_middle_frames(pooled_qblocks, pooled_kblocks, frame_blk, F_past, keep_frames, keep_sink, keep_near)
pooled_score = score_full_selected_blocks_from_frames(pooled_qblocks, pooled_kblocks, keep_idx, frame_blk, F_past, keep_sink, keep_sink, keep_near)
topk = min(K, int(topk_ratio * K))
lut = torch.topk(pooled_score, topk, dim=-1, sorted=False).indices
sparse_map = torch.zeros_like(pooled_score, dtype=torch.int8)
sparse_map.scatter_(-1, lut, 1)
return sparse_map, lut, topk
def get_sm_80_120_block_map(q, k, topk_ratio, BLKQ=64, BLKK=64, frame_seq=1536, keep_frames=6, keep_sink=0, keep_near=0):
past_num_frames = (k.shape[1] - q.shape[1]) // frame_seq
if k.shape[1] - q.shape[1] == 0:
# Use dense attention for the first chunk.
return get_sm_80_120_dense_map(*q.shape[:-1], q.device, BLKQ, BLKK)
elif past_num_frames > keep_frames:
# Use Hierarchical Sparse Attention when enough past frames are available.
return get_sm_80_120_block_map_2stage(q, k, topk_ratio, BLKQ, BLKK, frame_seq, keep_frames, keep_sink, keep_near)
else:
return get_sm_80_120_block_map_1stage(q, k, topk_ratio, BLKQ, BLKK)
@lru_cache(maxsize=32)
def get_sm_80_120_dense_map(B, L, H, device, BLKQ=64, BLKK=64):
M_BLOCKS = (L + BLKQ - 1) // BLKQ
N_BLOCKS = (L + BLKK - 1) // BLKK
dense_map = torch.ones((B, H, M_BLOCKS, N_BLOCKS), device=device, dtype=torch.int8)
block_ids = torch.arange(N_BLOCKS, device=device, dtype=torch.int64)
lut = block_ids.view(1, 1, 1, N_BLOCKS).expand(B, H, M_BLOCKS, N_BLOCKS).contiguous()
return dense_map, lut, N_BLOCKS
_sm_90_100_const_cache = {}
_sm_90_100_pool_cache = {}
def _mean_pool_sm_90_100(x, BLK, cache_name):
B, L, H, D = x.shape
L_BLOCKS = (L + BLK - 1) // BLK
key = (cache_name, B, L_BLOCKS, H, D, BLK, x.device, x.dtype)
out = _sm_90_100_pool_cache.get(key)
if out is None:
out = torch.empty((B, L_BLOCKS, H, D), device=x.device, dtype=x.dtype)
_sm_90_100_pool_cache[key] = out
return mean_pool_blhd(x, BLK, out=out)
def _get_sm_90_100_const_tensors(B, H, M_BLOCKS, topk, device):
key = (B, H, M_BLOCKS, topk, device)
tensors = _sm_90_100_const_cache.get(key)
if tensors is None:
tensors = {
"mask_block_cnt": torch.zeros(B, H, M_BLOCKS, dtype=torch.int32, device=device),
"mask_block_idx": torch.zeros(B, H, M_BLOCKS, 1, dtype=torch.int32, device=device),
"full_block_cnt": torch.full((B, H, M_BLOCKS), topk, dtype=torch.int32, device=device),
}
_sm_90_100_const_cache[key] = tensors
return tensors
def _make_sm_90_100_sparse_kwargs(lut, topk, block_size):
if not FLASH_ATTN_BLOCK_SPARSE_AVAILABLE:
raise RuntimeError("FA4 BlockSparseTensorsTorch is not available for SM90/SM100 sparse attention.")
B, H, M_BLOCKS = lut.shape[:3]
device = lut.device
const = _get_sm_90_100_const_tensors(B, H, M_BLOCKS, topk, device)
return {
"block_sparse_tensors": BlockSparseTensorsTorch(
mask_block_cnt=const["mask_block_cnt"],
mask_block_idx=const["mask_block_idx"],
full_block_cnt=const["full_block_cnt"],
full_block_idx=lut.to(torch.int32),
block_size=block_size,
)
}
def _check_sm_90_100_block_size(BLKQ, BLKK):
if BLKQ % 128 != 0 or BLKK != 128:
raise ValueError("FA4 block sparsity on SM90/SM100 expects BLKQ to be a multiple of 128 and BLKK to be 128.")
def get_sm_90_100_block_map_1stage(q, k, topk_ratio, BLKQ=128, BLKK=128):
_check_sm_90_100_block_size(BLKQ, BLKK)
# q, k: (B, L, H, D)
pooled_qblocks = _mean_pool_sm_90_100(q, BLKQ, "q") # (B, M_BLOCKS, H, D)
pooled_kblocks = _mean_pool_sm_90_100(k, BLKK, "k") # (B, N_BLOCKS, H, D)
pooled_score = pooled_qblocks.transpose(1, 2) @ pooled_kblocks.permute(0, 2, 3, 1)
K = pooled_score.shape[-1]
topk = min(K, int(topk_ratio * K))
lut = torch.topk(pooled_score, topk, dim=-1, sorted=False).indices
return _make_sm_90_100_sparse_kwargs(lut, topk, (BLKQ, BLKK))
def get_sm_90_100_block_map_2stage(q, k, topk_ratio, BLKQ=128, BLKK=128, frame_seq=1536, keep_frames=6, keep_sink=0, keep_near=0):
_check_sm_90_100_block_size(BLKQ, BLKK)
# q, k: (B, L, H, D)
pooled_qblocks = _mean_pool_sm_90_100(q, BLKQ, "q") # (B, M_BLOCKS, H, D)
pooled_kblocks = _mean_pool_sm_90_100(k, BLKK, "k") # (B, N_BLOCKS, H, D)
K = pooled_kblocks.shape[1]
frame_blk = frame_seq // BLKK
F = K // frame_blk
num_frame_per_block = q.shape[1] // frame_seq
F_past = F - num_frame_per_block
keep_idx = _select_2stage_middle_frames(pooled_qblocks, pooled_kblocks, frame_blk, F_past, keep_frames, keep_sink, keep_near)
pooled_score = score_full_selected_blocks_from_frames(pooled_qblocks, pooled_kblocks, keep_idx, frame_blk, F_past, keep_sink, keep_sink, keep_near)
topk = min(K, int(topk_ratio * K))
lut = torch.topk(pooled_score, topk, dim=-1, sorted=False).indices
return _make_sm_90_100_sparse_kwargs(lut, topk, (BLKQ, BLKK))
def get_sm_90_100_block_map(q, k, topk_ratio, BLKQ=128, BLKK=128, frame_seq=1536, keep_frames=6, keep_sink=0, keep_near=0):
_check_sm_90_100_block_size(BLKQ, BLKK)
past_num_frames = (k.shape[1] - q.shape[1]) // frame_seq
if k.shape[1] - q.shape[1] == 0:
# Use dense attention for the first chunk.
return {}
elif past_num_frames > keep_frames:
# Use Hierarchical Sparse Attention when enough past frames are available.
return get_sm_90_100_block_map_2stage(q, k, topk_ratio, BLKQ, BLKK, frame_seq, keep_frames, keep_sink, keep_near)
else:
return get_sm_90_100_block_map_1stage(q, k, topk_ratio, BLKQ, BLKK)
@triton.jit
def _attn_fwd(
Q, K, V,
qk_scale: tl.constexpr,
topk: tl.constexpr,
LUT, LSE, OS,
H: tl.constexpr,
LQ: tl.constexpr,
LK: tl.constexpr,
M_BLOCKS: tl.constexpr,
D: tl.constexpr,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
idx_m = tl.program_id(0).to(tl.int64)
idx_bh = tl.program_id(1).to(tl.int64)
idx_b = idx_bh // H
idx_h = idx_bh % H
HD: tl.constexpr = H * D
# Q/K/V/O: (B, L, H, D) -> base = b*L*H*D + h*D, stride_l = H*D
q_offset = idx_b * LQ * HD + idx_h * D
kv_offset = idx_b * LK * HD + idx_h * D
# LUT: (B, H, M_BLOCKS, topk) -> flat (B*H, M_BLOCKS, topk)
lut_offset = (idx_bh * M_BLOCKS + idx_m) * topk
# LSE: (B, H, LQ) -> flat (B*H, LQ)
lse_offset = idx_bh * LQ
offs_m = idx_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_n = tl.arange(0, BLOCK_N)
offs_d = tl.arange(0, D)
Q_ptrs = Q + q_offset + offs_m[:, None] * HD + offs_d[None, :]
OS_ptrs = OS + q_offset + offs_m[:, None] * HD + offs_d[None, :]
LUT_ptr = LUT + lut_offset
LSE_ptrs = LSE + lse_offset + offs_m
m_i = tl.full([BLOCK_M], -float("inf"), dtype=tl.float32)
l_i = tl.zeros([BLOCK_M], dtype=tl.float32)
o_s = tl.zeros([BLOCK_M, D], dtype=tl.float32)
q = tl.load(Q_ptrs, mask=offs_m[:, None] < LQ)
for block_idx in tl.range(topk):
idx_n = tl.load(LUT_ptr + block_idx).to(tl.int64)
k_start = idx_n * BLOCK_N
k_mask = (k_start + offs_n) < LK
K_ptrs = K + kv_offset + (k_start + offs_n)[None, :] * HD + offs_d[:, None]
V_ptrs = V + kv_offset + (k_start + offs_n)[:, None] * HD + offs_d[None, :]
k = tl.load(K_ptrs, mask=k_mask[None, :])
qk = tl.dot(q, k) * (qk_scale * 1.4426950408889634)
qk = tl.where(k_mask[None, :], qk, float("-inf"))
v = tl.load(V_ptrs, mask=k_mask[:, None])
local_m = tl.max(qk, 1)
new_m = tl.maximum(m_i, local_m)
qk = qk - new_m[:, None]
p = tl.math.exp2(qk)
l_ij = tl.sum(p, 1)
alpha = tl.math.exp2(m_i - new_m)
o_s = o_s * alpha[:, None]
o_s += tl.dot(p.to(v.dtype), v)
l_i = l_i * alpha + l_ij
m_i = new_m
o_s = o_s / l_i[:, None]
tl.store(OS_ptrs, o_s.to(OS.type.element_ty), mask=offs_m[:, None] < LQ)
m_i += tl.math.log2(l_i)
tl.store(LSE_ptrs, m_i, mask=offs_m < LQ)
@triton.jit
def _attn_bwd_preprocess(
OS, DOS, DELTAS,
H: tl.constexpr,
LQ,
D: tl.constexpr,
BLOCK_M: tl.constexpr,
):
idx_m = tl.program_id(0).to(tl.int64)
idx_bh = tl.program_id(1).to(tl.int64)
idx_b = idx_bh // H
idx_h = idx_bh % H
HD: tl.constexpr = H * D
os_base = idx_b * LQ * HD + idx_h * D
OS += os_base
DOS += os_base
DELTAS += idx_bh * LQ
offs_m = idx_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_d = tl.arange(0, D)
o_s = tl.load(OS + offs_m[:, None] * HD + offs_d[None, :], mask=offs_m[:, None] < LQ)
do_s = tl.load(DOS + offs_m[:, None] * HD + offs_d[None, :], mask=offs_m[:, None] < LQ)
delta_s = tl.sum(o_s * do_s, axis=1).to(DELTAS.type.element_ty)
tl.store(DELTAS + offs_m, delta_s, mask=offs_m < LQ)
@triton.jit
def _attn_bwd_dq(
Q, K, V, LSE, DELTAS,
DOS, DQ, LUT,
qk_scale: tl.constexpr,
topk: tl.constexpr,
H: tl.constexpr,
LQ: tl.constexpr,
LK: tl.constexpr,
M_BLOCKS: tl.constexpr,
D: tl.constexpr,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
idx_m = tl.program_id(0).to(tl.int64)
idx_bh = tl.program_id(1).to(tl.int64)
idx_b = idx_bh // H
idx_h = idx_bh % H
HD: tl.constexpr = H * D
offs_m = idx_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_n = tl.arange(0, BLOCK_N)
offs_d = tl.arange(0, D)
q_offset = idx_b * LQ * HD + idx_h * D
kv_offset = idx_b * LK * HD + idx_h * D
lse_offset = idx_bh * LQ
lut_offset = (idx_bh * M_BLOCKS + idx_m) * topk
Q_ptrs = Q + q_offset + offs_m[:, None] * HD + offs_d[None, :]
DQ_ptrs = DQ + q_offset + offs_m[:, None] * HD + offs_d[None, :]
DOS_ptrs = DOS + q_offset + offs_m[:, None] * HD + offs_d[None, :]
LSE_ptrs = LSE + lse_offset + offs_m
DELTAS_ptrs = DELTAS + lse_offset + offs_m
LUT_ptr = LUT + lut_offset
q = tl.load(Q_ptrs, mask=offs_m[:, None] < LQ)
do_s = tl.load(DOS_ptrs, mask=offs_m[:, None] < LQ)
delta_s = tl.load(DELTAS_ptrs, mask=offs_m < LQ)
lse = tl.load(LSE_ptrs, mask=offs_m < LQ, other=float("inf"))
dq = tl.zeros([BLOCK_M, D], dtype=tl.float32)
for block_idx in tl.range(topk, num_stages=2):
idx_n = tl.load(LUT_ptr + block_idx).to(tl.int64)
k_start = idx_n * BLOCK_N
k_mask = (k_start + offs_n) < LK
K_ptrs = K + kv_offset + (k_start + offs_n)[:, None] * HD + offs_d[None, :]
V_ptrs = V + kv_offset + (k_start + offs_n)[:, None] * HD + offs_d[None, :]
k = tl.load(K_ptrs, mask=k_mask[:, None])
v = tl.load(V_ptrs, mask=k_mask[:, None])
qk = tl.dot(q, k.T) * (qk_scale * 1.4426950408889634)
p = tl.math.exp2(qk - lse[:, None])
p = tl.where(k_mask[None, :], p, 0.0)
dp = tl.dot(do_s, v.T).to(tl.float32)
ds = p * (dp - delta_s[:, None])
dq += tl.dot(ds.to(k.dtype), k)
tl.store(DQ_ptrs, dq * qk_scale, mask=offs_m[:, None] < LQ)
@triton.jit
def _attn_bwd_dkdv(
Q, K, V, DOS, DK, DV,
qk_scale, KBID, LSE, DELTAS,
H: tl.constexpr,
LQ: tl.constexpr,
LK: tl.constexpr,
M_BLOCKS: tl.constexpr,
N_BLOCKS: tl.constexpr,
D: tl.constexpr,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
BLOCK_SLICE_FACTOR: tl.constexpr,
):
BLOCK_M2: tl.constexpr = BLOCK_M // BLOCK_SLICE_FACTOR
idx_n = tl.program_id(0).to(tl.int64)
idx_bh = tl.program_id(1).to(tl.int64)
idx_b = idx_bh // H
idx_h = idx_bh % H
HD: tl.constexpr = H * D
offs_n = idx_n * BLOCK_N + tl.arange(0, BLOCK_N)
offs_m = tl.arange(0, BLOCK_M2)
offs_d = tl.arange(0, D)
q_offset = idx_b * LQ * HD + idx_h * D
kv_offset = idx_b * LK * HD + idx_h * D
kbid_offset = idx_bh * M_BLOCKS * N_BLOCKS
lse_offset = idx_bh * LQ
Q_ptrs = Q + q_offset + offs_m[:, None] * HD + offs_d[None, :]
DOS_ptrs = DOS + q_offset + offs_m[:, None] * HD + offs_d[None, :]
LSE_ptrs = LSE + lse_offset + offs_m
DELTAS_ptrs = DELTAS + lse_offset + offs_m
K_ptrs = K + kv_offset + offs_n[:, None] * HD + offs_d[None, :]
V_ptrs = V + kv_offset + offs_n[:, None] * HD + offs_d[None, :]
DK_ptrs = DK + kv_offset + offs_n[:, None] * HD + offs_d[None, :]
DV_ptrs = DV + kv_offset + offs_n[:, None] * HD + offs_d[None, :]
KBID_ptr = KBID + kbid_offset + idx_n
k = tl.load(K_ptrs, mask=offs_n[:, None] < LK)
v = tl.load(V_ptrs, mask=offs_n[:, None] < LK)
dk = tl.zeros([BLOCK_N, D], dtype=tl.float32)
dv = tl.zeros([BLOCK_N, D], dtype=tl.float32)
for idx_m in tl.range(0, LQ, BLOCK_M2):
kbid = tl.load(KBID_ptr)
if kbid == 1:
m_mask = offs_m < (LQ - idx_m)
q = tl.load(Q_ptrs, mask=m_mask[:, None])
lse = tl.load(LSE_ptrs, mask=m_mask, other=float("inf"))
qkT = tl.dot(k, q.T) * (qk_scale * 1.4426950408889634)
pT = tl.math.exp2(qkT - lse[None, :])
pT = tl.where(offs_n[:, None] < LK, pT, 0.0)
do = tl.load(DOS_ptrs, mask=m_mask[:, None])
dv += tl.dot(pT.to(do.dtype), do)
delta = tl.load(DELTAS_ptrs, mask=m_mask)
dpT = tl.dot(v, tl.trans(do))
dsT = pT * (dpT - delta[None, :])
dk += tl.dot(dsT.to(q.dtype), q)
Q_ptrs += BLOCK_M2 * HD
DOS_ptrs += BLOCK_M2 * HD
LSE_ptrs += BLOCK_M2
DELTAS_ptrs += BLOCK_M2
if (idx_m + BLOCK_M2) % BLOCK_M == 0:
KBID_ptr += N_BLOCKS
tl.store(DK_ptrs, dk * qk_scale, mask=offs_n[:, None] < LK)
tl.store(DV_ptrs, dv, mask=offs_n[:, None] < LK)
class _attention(torch.autograd.Function):
@staticmethod
def forward(ctx, q, k, v, k_block_id, lut, topk, BLOCK_M, BLOCK_N, qk_scale=None):
# q, k, v: (B, L, H, D)
B, LQ, H, D = q.shape
_, LK, _, Dk = k.shape
if qk_scale is None:
qk_scale = D**-0.5
M_BLOCKS = triton.cdiv(LQ, BLOCK_M)
o_s = torch.empty_like(q)
lse = torch.empty((B, H, LQ), device=q.device, dtype=torch.float32)
grid = (M_BLOCKS, B * H)
_attn_fwd[grid](
q, k, v, qk_scale, topk,
lut, lse, o_s,
H, LQ, LK, M_BLOCKS,
D, BLOCK_M, BLOCK_N,
num_warps=4, # if D == 64 else 8
num_stages=3
)
ctx.save_for_backward(q, k, v, k_block_id, lut, lse, o_s)
ctx.qk_scale = qk_scale
ctx.topk = topk
ctx.BLOCK_M = BLOCK_M
ctx.BLOCK_N = BLOCK_N
ctx.LQ = LQ
ctx.LK = LK
ctx.H = H
return o_s
@staticmethod
def backward(ctx, do_s):
q, k, v, k_block_id, lut, lse, o_s = ctx.saved_tensors
do_s = do_s.contiguous()
BLOCK_M, BLOCK_N = ctx.BLOCK_M, ctx.BLOCK_N
B, LQ, H, D = q.shape
LK = ctx.LK
M_BLOCKS = triton.cdiv(LQ, BLOCK_M)
N_BLOCKS = triton.cdiv(LK, BLOCK_N)
dq = torch.empty_like(q)
dk = torch.empty_like(k)
dv = torch.empty_like(v)
delta_s = torch.empty_like(lse)
grid = (M_BLOCKS, B * H)
_attn_bwd_preprocess[grid](
o_s, do_s, delta_s,
H, LQ, D, BLOCK_M,
)
grid = (M_BLOCKS, B * H)
_attn_bwd_dq[grid](
q, k, v, lse, delta_s,
do_s, dq, lut,
ctx.qk_scale, ctx.topk,
H, LQ, LK, M_BLOCKS,
D, BLOCK_M, BLOCK_N,
num_warps=4 if D == 64 else 8,
num_stages=4 if D == 64 else 5
)
grid = (N_BLOCKS, B * H)
_attn_bwd_dkdv[grid](
q, k, v, do_s, dk, dv,
ctx.qk_scale, k_block_id, lse, delta_s,
H, LQ, LK, M_BLOCKS, N_BLOCKS,
D, BLOCK_M, BLOCK_N,
BLOCK_SLICE_FACTOR=BLOCK_M // 64,
num_warps=4 if D == 64 else 8,
num_stages=4 if D == 64 else 5
)
return dq, dk, dv, None, None, None, None, None, None