[misc] yapf pass under CI's interpreter (3.12) + pin hook language_version (#1702)
This commit is contained in:
@@ -26,8 +26,7 @@ from fastvideo_kernel.turbodiffusion_ops import (
|
||||
)
|
||||
|
||||
from fastvideo_kernel.block_sparse_attn_varlen import (
|
||||
block_sparse_attn_varlen,
|
||||
)
|
||||
block_sparse_attn_varlen, )
|
||||
|
||||
from fastvideo_kernel.vsa_utils import (
|
||||
VSA_TILE_SIZE,
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import Tuple
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backend selection helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -37,10 +36,8 @@ def _force_triton() -> bool:
|
||||
Honors both the new ``FASTVIDEO_VSA_TRITON`` and the legacy
|
||||
``FASTVIDEO_KERNEL_VSA_FORCE_TRITON`` for backward compatibility.
|
||||
"""
|
||||
return (
|
||||
os.environ.get("FASTVIDEO_VSA_TRITON", "0") == "1"
|
||||
or os.environ.get("FASTVIDEO_KERNEL_VSA_FORCE_TRITON", "0") == "1"
|
||||
)
|
||||
return (os.environ.get("FASTVIDEO_VSA_TRITON", "0") == "1"
|
||||
or os.environ.get("FASTVIDEO_KERNEL_VSA_FORCE_TRITON", "0") == "1")
|
||||
|
||||
|
||||
def _force_tk() -> bool:
|
||||
@@ -62,25 +59,19 @@ def _map_to_index(block_map: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
if block_map.dim() == 3:
|
||||
block_map = block_map.unsqueeze(0)
|
||||
if block_map.dim() != 4:
|
||||
raise ValueError(
|
||||
f"block_map must be [B,H,Q,KV] (or [H,Q,KV]), "
|
||||
f"got shape={tuple(block_map.shape)}"
|
||||
)
|
||||
raise ValueError(f"block_map must be [B,H,Q,KV] (or [H,Q,KV]), "
|
||||
f"got shape={tuple(block_map.shape)}")
|
||||
if block_map.dtype != torch.bool:
|
||||
block_map = block_map.to(torch.bool)
|
||||
if not block_map.is_cuda:
|
||||
raise RuntimeError(
|
||||
"block_map must be a CUDA tensor (Triton map_to_index required)."
|
||||
)
|
||||
raise RuntimeError("block_map must be a CUDA tensor (Triton map_to_index required).")
|
||||
|
||||
try:
|
||||
from fastvideo_kernel.triton_kernels.index import map_to_index as triton_map_to_index
|
||||
except Exception as e: # pragma: no cover - environment issue
|
||||
raise ImportError(
|
||||
"Triton map_to_index is required but not available. "
|
||||
"Ensure Triton is installed and "
|
||||
"fastvideo_kernel.triton_kernels.index is importable."
|
||||
) from e
|
||||
raise ImportError("Triton map_to_index is required but not available. "
|
||||
"Ensure Triton is installed and "
|
||||
"fastvideo_kernel.triton_kernels.index is importable.") from e
|
||||
return triton_map_to_index(block_map)
|
||||
|
||||
|
||||
@@ -123,8 +114,7 @@ def block_sparse_attn_triton(
|
||||
variable_block_sizes: torch.Tensor,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
from fastvideo_kernel.triton_kernels.block_sparse_attn_triton import (
|
||||
triton_block_sparse_attn_forward,
|
||||
)
|
||||
triton_block_sparse_attn_forward, )
|
||||
|
||||
o, M = triton_block_sparse_attn_forward(
|
||||
q.contiguous(),
|
||||
@@ -172,13 +162,10 @@ def block_sparse_attn_backward_triton(
|
||||
variable_block_sizes: torch.Tensor,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
from fastvideo_kernel.triton_kernels.block_sparse_attn_triton import (
|
||||
triton_block_sparse_attn_backward,
|
||||
)
|
||||
triton_block_sparse_attn_backward, )
|
||||
|
||||
num_kv_blocks = int(variable_block_sizes.numel())
|
||||
k2q_idx, k2q_num = _invert_indices_for_backward(
|
||||
q2k_idx, q2k_num, num_kv_blocks
|
||||
)
|
||||
k2q_idx, k2q_num = _invert_indices_for_backward(q2k_idx, q2k_num, num_kv_blocks)
|
||||
# q/k/v are saved from the user-facing inputs and may be non-contiguous;
|
||||
# o/M are kernel outputs so are already contiguous.
|
||||
dq, dk, dv = triton_block_sparse_attn_backward(
|
||||
@@ -223,16 +210,11 @@ def _setup_context_triton(ctx, inputs, output):
|
||||
|
||||
def _backward_triton(ctx, grad_o, grad_M):
|
||||
q, k, v, o, M, q2k_idx, q2k_num, variable_block_sizes = ctx.saved_tensors
|
||||
dq, dk, dv = block_sparse_attn_backward_triton(
|
||||
grad_o, q, k, v, o, M, q2k_idx, q2k_num, variable_block_sizes
|
||||
)
|
||||
dq, dk, dv = block_sparse_attn_backward_triton(grad_o, q, k, v, o, M, q2k_idx, q2k_num, variable_block_sizes)
|
||||
return dq, dk, dv, None, None, None
|
||||
|
||||
|
||||
block_sparse_attn_triton.register_autograd(
|
||||
_backward_triton, setup_context=_setup_context_triton
|
||||
)
|
||||
|
||||
block_sparse_attn_triton.register_autograd(_backward_triton, setup_context=_setup_context_triton)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SM90 backend custom ops (index-native)
|
||||
@@ -306,9 +288,7 @@ def block_sparse_attn_backward_sm90(
|
||||
raise ImportError("fastvideo_kernel_ops.block_sparse_bwd is not available")
|
||||
|
||||
num_kv_blocks = int(variable_block_sizes.numel())
|
||||
k2q_idx, k2q_num = _invert_indices_for_backward(
|
||||
q2k_idx, q2k_num, num_kv_blocks
|
||||
)
|
||||
k2q_idx, k2q_num = _invert_indices_for_backward(q2k_idx, q2k_num, num_kv_blocks)
|
||||
|
||||
# q/k/v are saved from user-facing inputs; o/lse are kernel outputs.
|
||||
dq, dk, dv = block_sparse_bwd(
|
||||
@@ -353,16 +333,11 @@ def _setup_context_sm90(ctx, inputs, output):
|
||||
|
||||
def _backward_sm90(ctx, grad_o, grad_lse):
|
||||
q, k, v, o, lse, q2k_idx, q2k_num, variable_block_sizes = ctx.saved_tensors
|
||||
dq, dk, dv = block_sparse_attn_backward_sm90(
|
||||
grad_o, q, k, v, o, lse, q2k_idx, q2k_num, variable_block_sizes
|
||||
)
|
||||
dq, dk, dv = block_sparse_attn_backward_sm90(grad_o, q, k, v, o, lse, q2k_idx, q2k_num, variable_block_sizes)
|
||||
return dq, dk, dv, None, None, None
|
||||
|
||||
|
||||
block_sparse_attn_sm90.register_autograd(
|
||||
_backward_sm90, setup_context=_setup_context_sm90
|
||||
)
|
||||
|
||||
block_sparse_attn_sm90.register_autograd(_backward_sm90, setup_context=_setup_context_sm90)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API
|
||||
@@ -385,11 +360,7 @@ def block_sparse_attn_from_indices(
|
||||
variable_block_sizes = _as_int32_contig(variable_block_sizes, "variable_block_sizes")
|
||||
|
||||
block_sparse_fwd, block_sparse_bwd = _get_sm90_ops()
|
||||
sm90_available = (
|
||||
_is_sm90()
|
||||
and block_sparse_fwd is not None
|
||||
and block_sparse_bwd is not None
|
||||
)
|
||||
sm90_available = (_is_sm90() and block_sparse_fwd is not None and block_sparse_bwd is not None)
|
||||
|
||||
# Backend resolution:
|
||||
# - FASTVIDEO_VSA_TRITON forces Triton everywhere.
|
||||
@@ -419,6 +390,4 @@ def block_sparse_attn(
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Bool-mask compat wrapper; prefer block_sparse_attn_from_indices."""
|
||||
q2k_idx, q2k_num = _map_to_index(block_map)
|
||||
return block_sparse_attn_from_indices(
|
||||
q, k, v, q2k_idx, q2k_num, variable_block_sizes
|
||||
)
|
||||
return block_sparse_attn_from_indices(q, k, v, q2k_idx, q2k_num, variable_block_sizes)
|
||||
|
||||
@@ -30,7 +30,6 @@ from .block_sparse_attn import block_sparse_attn_triton, _force_triton
|
||||
# FA4 CuTe build (``flash_attn.cute``) and make it a hard dependency of the
|
||||
# default Triton path.
|
||||
|
||||
|
||||
_KV_BLOCK_PHYS = 128 # FA4 CuTe BSA forward uses 128-token KV blocks.
|
||||
_KV_BLOCK_TRITON = 64 # Existing Triton path uses 64-token KV blocks.
|
||||
|
||||
@@ -66,7 +65,7 @@ def _expand_mask_and_sizes_256_to_128(
|
||||
child0 = torch.clamp(sizes_i32, min=0, max=_KV_BLOCK_PHYS)
|
||||
child1 = torch.clamp(sizes_i32 - _KV_BLOCK_PHYS, min=0, max=_KV_BLOCK_PHYS)
|
||||
expanded_sizes = torch.empty(
|
||||
(sizes_i32.numel() * 2,),
|
||||
(sizes_i32.numel() * 2, ),
|
||||
dtype=torch.int32,
|
||||
device=sizes_i32.device,
|
||||
)
|
||||
@@ -108,9 +107,7 @@ def _triton_via_route_a(
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
from .triton_kernels.index import map_to_index as triton_map_to_index
|
||||
|
||||
mask_64, sizes_64 = _expand_mask_and_sizes_256_to_64(
|
||||
logical_mask_256, logical_kv_sizes_256
|
||||
)
|
||||
mask_64, sizes_64 = _expand_mask_and_sizes_256_to_64(logical_mask_256, logical_kv_sizes_256)
|
||||
q2k_idx, q2k_num = triton_map_to_index(mask_64.to(torch.bool))
|
||||
return block_sparse_attn_triton(q, k, v, q2k_idx, q2k_num, sizes_64)
|
||||
|
||||
@@ -127,13 +124,9 @@ def block_sparse_attn_256(
|
||||
logical_block_map_256 = logical_block_map_256.unsqueeze(0)
|
||||
|
||||
if _resolve_backend() == "triton":
|
||||
return _triton_via_route_a(
|
||||
q, k, v, logical_block_map_256, logical_variable_block_sizes_256
|
||||
)
|
||||
return _triton_via_route_a(q, k, v, logical_block_map_256, logical_variable_block_sizes_256)
|
||||
|
||||
mask_128, sizes_128 = _expand_mask_and_sizes_256_to_128(
|
||||
logical_block_map_256, logical_variable_block_sizes_256
|
||||
)
|
||||
mask_128, sizes_128 = _expand_mask_and_sizes_256_to_128(logical_block_map_256, logical_variable_block_sizes_256)
|
||||
from .block_sparse_attn_cute_fwd import block_sparse_attn_cute_fwd
|
||||
return block_sparse_attn_cute_fwd(q, k, v, mask_128, sizes_128)
|
||||
|
||||
@@ -163,8 +156,6 @@ def block_sparse_attn_256_bshd(
|
||||
)
|
||||
return out_bhsd.transpose(1, 2).contiguous(), aux
|
||||
|
||||
mask_128, sizes_128 = _expand_mask_and_sizes_256_to_128(
|
||||
logical_block_map_256, logical_variable_block_sizes_256
|
||||
)
|
||||
mask_128, sizes_128 = _expand_mask_and_sizes_256_to_128(logical_block_map_256, logical_variable_block_sizes_256)
|
||||
from .block_sparse_attn_cute_fwd import block_sparse_attn_cute_fwd_bshd
|
||||
return block_sparse_attn_cute_fwd_bshd(q, k, v, mask_128, sizes_128)
|
||||
|
||||
@@ -22,13 +22,11 @@ from typing import Tuple
|
||||
|
||||
import torch
|
||||
|
||||
_FA4_IMPORT_HINT = (
|
||||
"VSA-256 CuTe fastpath requires a FlashAttention-4 CuTe build that "
|
||||
"provides `flash_attn.cute` with block-sparsity support (plus "
|
||||
"`nvidia-cutlass-dsl` and `quack-kernels`). This is an optional "
|
||||
"dependency; the default VSA-256 path is Triton. Install the FA4 CuTe "
|
||||
"build and set FASTVIDEO_VSA_CUTEDSL=1 to enable the CuTe fastpath."
|
||||
)
|
||||
_FA4_IMPORT_HINT = ("VSA-256 CuTe fastpath requires a FlashAttention-4 CuTe build that "
|
||||
"provides `flash_attn.cute` with block-sparsity support (plus "
|
||||
"`nvidia-cutlass-dsl` and `quack-kernels`). This is an optional "
|
||||
"dependency; the default VSA-256 path is Triton. Install the FA4 CuTe "
|
||||
"build and set FASTVIDEO_VSA_CUTEDSL=1 to enable the CuTe fastpath.")
|
||||
|
||||
|
||||
def _load_fa4_cute():
|
||||
@@ -54,17 +52,14 @@ def _map_to_index(block_map: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
if block_map.dim() == 3:
|
||||
block_map = block_map.unsqueeze(0)
|
||||
if block_map.dim() != 4:
|
||||
raise ValueError(
|
||||
f"block_map must be [B,H,Q,KV] (or [H,Q,KV]), "
|
||||
f"got shape={tuple(block_map.shape)}"
|
||||
)
|
||||
raise ValueError(f"block_map must be [B,H,Q,KV] (or [H,Q,KV]), "
|
||||
f"got shape={tuple(block_map.shape)}")
|
||||
if block_map.dtype != torch.bool:
|
||||
block_map = block_map.to(torch.bool)
|
||||
if not block_map.is_cuda:
|
||||
raise RuntimeError("block_map must be a CUDA tensor.")
|
||||
from fastvideo_kernel.triton_kernels.index import (
|
||||
map_to_index as triton_map_to_index,
|
||||
)
|
||||
map_to_index as triton_map_to_index, )
|
||||
|
||||
return triton_map_to_index(block_map)
|
||||
|
||||
@@ -84,10 +79,8 @@ def _aggregate_q_block_map(
|
||||
) -> torch.Tensor:
|
||||
factor = q_sparse_block_size // q_block_size
|
||||
if factor <= 0 or q_sparse_block_size % q_block_size != 0:
|
||||
raise ValueError(
|
||||
f"q_sparse_block_size must be a positive multiple of "
|
||||
f"q_block_size ({q_block_size}), got {q_sparse_block_size}"
|
||||
)
|
||||
raise ValueError(f"q_sparse_block_size must be a positive multiple of "
|
||||
f"q_block_size ({q_block_size}), got {q_sparse_block_size}")
|
||||
bsz, nhead, q_blocks, kv_blocks = block_map.shape
|
||||
q_blocks_sparse = (q_blocks + factor - 1) // factor
|
||||
pad_q = q_blocks_sparse * factor - q_blocks
|
||||
@@ -164,9 +157,7 @@ def _cute_forward(
|
||||
q_block_size=q_block_size,
|
||||
)
|
||||
kv_full = (variable_block_sizes == kv_block_size).view(1, 1, 1, -1)
|
||||
kv_partial = (
|
||||
(variable_block_sizes > 0) & (variable_block_sizes < kv_block_size)
|
||||
).view(1, 1, 1, -1)
|
||||
kv_partial = ((variable_block_sizes > 0) & (variable_block_sizes < kv_block_size)).view(1, 1, 1, -1)
|
||||
full_map = sparse_map & kv_full
|
||||
mask_map = sparse_map & kv_partial
|
||||
|
||||
|
||||
@@ -41,9 +41,7 @@ def _scatter_to_padded(
|
||||
for actual in sizes:
|
||||
actual = min(actual, src_end - src_pos)
|
||||
if actual > 0:
|
||||
dst[:, :, dst_pos:dst_pos + actual, :] = (
|
||||
src[src_pos:src_pos + actual].transpose(0, 1).unsqueeze(0)
|
||||
)
|
||||
dst[:, :, dst_pos:dst_pos + actual, :] = (src[src_pos:src_pos + actual].transpose(0, 1).unsqueeze(0))
|
||||
src_pos += actual
|
||||
dst_pos += block_size
|
||||
|
||||
@@ -68,9 +66,7 @@ def _gather_from_padded(
|
||||
for actual in sizes:
|
||||
actual = min(actual, dst_end - dst_pos)
|
||||
if actual > 0:
|
||||
dst[dst_pos:dst_pos + actual] = (
|
||||
src[0, :, src_pos:src_pos + actual, :].transpose(0, 1)
|
||||
)
|
||||
dst[dst_pos:dst_pos + actual] = (src[0, :, src_pos:src_pos + actual, :].transpose(0, 1))
|
||||
dst_pos += actual
|
||||
src_pos += block_size
|
||||
|
||||
@@ -131,9 +127,7 @@ def block_sparse_attn_varlen(
|
||||
if q_variable_block_sizes_list is not None:
|
||||
q_vbs_resolved.append(q_variable_block_sizes_list[i])
|
||||
else:
|
||||
q_vbs_resolved.append(
|
||||
torch.full((n_q_blocks,), block_size, dtype=torch.int32)
|
||||
)
|
||||
q_vbs_resolved.append(torch.full((n_q_blocks, ), block_size, dtype=torch.int32))
|
||||
|
||||
total_padded_q = sum(padded_q_lens)
|
||||
total_padded_kv = sum(padded_kv_lens)
|
||||
@@ -146,16 +140,31 @@ def block_sparse_attn_varlen(
|
||||
kv_offset = 0
|
||||
for i in range(num_seqs):
|
||||
_scatter_to_padded(
|
||||
q, q_vbs_resolved[i], block_size,
|
||||
q_packed, q_offset, cu_q[i], cu_q[i + 1],
|
||||
q,
|
||||
q_vbs_resolved[i],
|
||||
block_size,
|
||||
q_packed,
|
||||
q_offset,
|
||||
cu_q[i],
|
||||
cu_q[i + 1],
|
||||
)
|
||||
_scatter_to_padded(
|
||||
k, variable_block_sizes_list[i], block_size,
|
||||
k_packed, kv_offset, cu_kv[i], cu_kv[i + 1],
|
||||
k,
|
||||
variable_block_sizes_list[i],
|
||||
block_size,
|
||||
k_packed,
|
||||
kv_offset,
|
||||
cu_kv[i],
|
||||
cu_kv[i + 1],
|
||||
)
|
||||
_scatter_to_padded(
|
||||
v, variable_block_sizes_list[i], block_size,
|
||||
v_packed, kv_offset, cu_kv[i], cu_kv[i + 1],
|
||||
v,
|
||||
variable_block_sizes_list[i],
|
||||
block_size,
|
||||
v_packed,
|
||||
kv_offset,
|
||||
cu_kv[i],
|
||||
cu_kv[i + 1],
|
||||
)
|
||||
q_offset += padded_q_lens[i]
|
||||
kv_offset += padded_kv_lens[i]
|
||||
@@ -164,12 +173,19 @@ def block_sparse_attn_varlen(
|
||||
max_kv_per_q = max(t.shape[-1] for t in q2k_idx_list)
|
||||
|
||||
global_q2k_idx = torch.zeros(
|
||||
1, num_heads, total_q_blocks, max_kv_per_q,
|
||||
dtype=torch.int32, device=device,
|
||||
1,
|
||||
num_heads,
|
||||
total_q_blocks,
|
||||
max_kv_per_q,
|
||||
dtype=torch.int32,
|
||||
device=device,
|
||||
)
|
||||
global_q2k_num = torch.zeros(
|
||||
1, num_heads, total_q_blocks,
|
||||
dtype=torch.int32, device=device,
|
||||
1,
|
||||
num_heads,
|
||||
total_q_blocks,
|
||||
dtype=torch.int32,
|
||||
device=device,
|
||||
)
|
||||
global_vbs_parts = []
|
||||
|
||||
@@ -191,16 +207,25 @@ def block_sparse_attn_varlen(
|
||||
global_vbs = torch.cat(global_vbs_parts, dim=0).to(torch.int32).contiguous()
|
||||
|
||||
out_packed, _ = block_sparse_attn_from_indices(
|
||||
q_packed, k_packed, v_packed,
|
||||
global_q2k_idx, global_q2k_num, global_vbs,
|
||||
q_packed,
|
||||
k_packed,
|
||||
v_packed,
|
||||
global_q2k_idx,
|
||||
global_q2k_num,
|
||||
global_vbs,
|
||||
)
|
||||
|
||||
out = torch.zeros(cu_q[-1], num_heads, head_dim, device=device, dtype=dtype)
|
||||
q_offset = 0
|
||||
for i in range(num_seqs):
|
||||
_gather_from_padded(
|
||||
out_packed, q_vbs_resolved[i], block_size,
|
||||
out, q_offset, cu_q[i], cu_q[i + 1],
|
||||
out_packed,
|
||||
q_vbs_resolved[i],
|
||||
block_size,
|
||||
out,
|
||||
q_offset,
|
||||
cu_q[i],
|
||||
cu_q[i + 1],
|
||||
)
|
||||
q_offset += padded_q_lens[i]
|
||||
|
||||
|
||||
@@ -27,9 +27,7 @@ def sliding_tile_attention(
|
||||
) -> torch.Tensor:
|
||||
# Check if the specific op is available
|
||||
if sta_fwd is None:
|
||||
return sliding_tile_attention_triton(
|
||||
q, k, v, window_size, text_length, has_text, seq_shape
|
||||
)
|
||||
return sliding_tile_attention_triton(q, k, v, window_size, text_length, has_text, seq_shape)
|
||||
|
||||
seq_length = q.shape[2]
|
||||
shape_map = {"30x48x80": 1, "36x48x48": 2, "18x48x80": 3}
|
||||
@@ -53,11 +51,7 @@ def sliding_tile_attention(
|
||||
k_h = k[:, head_idx:head_idx + 1].contiguous()
|
||||
v_h = v[:, head_idx:head_idx + 1].contiguous()
|
||||
o_h = torch.empty_like(q_h)
|
||||
sta_fwd(
|
||||
q_h, k_h,
|
||||
v_h, o_h,
|
||||
t, h, w, text_length, False, has_text, flag
|
||||
)
|
||||
sta_fwd(q_h, k_h, v_h, o_h, t, h, w, text_length, False, has_text, flag)
|
||||
output[:, head_idx:head_idx + 1] = o_h
|
||||
|
||||
if has_text:
|
||||
@@ -96,35 +90,27 @@ def video_sparse_attn(
|
||||
if k.shape[0] != batch or v.shape[0] != batch or k.shape[1] != heads or v.shape[1] != heads:
|
||||
raise ValueError("Expected q/k/v to have the same batch and head dimensions.")
|
||||
if v.shape[2] != kv_seq_len:
|
||||
raise ValueError(
|
||||
f"Expected k and v to have the same sequence length, got "
|
||||
f"k.shape[2]={kv_seq_len}, v.shape[2]={v.shape[2]}"
|
||||
)
|
||||
raise ValueError(f"Expected k and v to have the same sequence length, got "
|
||||
f"k.shape[2]={kv_seq_len}, v.shape[2]={v.shape[2]}")
|
||||
|
||||
if q_seq_len % block_elements != 0 or kv_seq_len % block_elements != 0:
|
||||
raise ValueError(
|
||||
f"q_seq_len and kv_seq_len must be divisible by block_elements={block_elements}, "
|
||||
f"got q_seq_len={q_seq_len}, kv_seq_len={kv_seq_len}"
|
||||
)
|
||||
raise ValueError(f"q_seq_len and kv_seq_len must be divisible by block_elements={block_elements}, "
|
||||
f"got q_seq_len={q_seq_len}, kv_seq_len={kv_seq_len}")
|
||||
q_num_blocks = q_seq_len // block_elements
|
||||
kv_num_blocks = kv_seq_len // block_elements
|
||||
if variable_block_sizes.numel() != kv_num_blocks:
|
||||
raise ValueError(
|
||||
f"variable_block_sizes must have length kv_num_blocks={kv_num_blocks}, "
|
||||
f"got {variable_block_sizes.numel()}"
|
||||
)
|
||||
raise ValueError(f"variable_block_sizes must have length kv_num_blocks={kv_num_blocks}, "
|
||||
f"got {variable_block_sizes.numel()}")
|
||||
if q_variable_block_sizes.numel() != q_num_blocks:
|
||||
raise ValueError(
|
||||
f"q_variable_block_sizes must have length q_num_blocks={q_num_blocks}, "
|
||||
f"got {q_variable_block_sizes.numel()}"
|
||||
)
|
||||
raise ValueError(f"q_variable_block_sizes must have length q_num_blocks={q_num_blocks}, "
|
||||
f"got {q_variable_block_sizes.numel()}")
|
||||
|
||||
# Compression branch (fused Triton: bf16 read → fp32 accumulate → div → bf16 write)
|
||||
q_c = fused_block_mean(q, q_variable_block_sizes, block_elements)
|
||||
k_c = fused_block_mean(k, variable_block_sizes, block_elements)
|
||||
v_c = fused_block_mean(v, variable_block_sizes, block_elements)
|
||||
|
||||
scores = torch.matmul(q_c, k_c.transpose(-2, -1)) / (dim ** 0.5)
|
||||
scores = torch.matmul(q_c, k_c.transpose(-2, -1)) / (dim**0.5)
|
||||
attn = torch.softmax(scores, dim=-1)
|
||||
out_c = torch.matmul(attn, v_c)
|
||||
out_c = out_c.view(batch, heads, q_num_blocks, 1, dim)
|
||||
@@ -163,61 +149,46 @@ def video_sparse_attn_bshd(
|
||||
block_size = (block_size, block_size, block_size)
|
||||
block_elements = block_size[0] * block_size[1] * block_size[2]
|
||||
if block_elements != 256:
|
||||
raise ValueError(
|
||||
"video_sparse_attn_bshd is only defined for block_elements=256 "
|
||||
f"(got {block_elements}); use video_sparse_attn for the 64-block path."
|
||||
)
|
||||
raise ValueError("video_sparse_attn_bshd is only defined for block_elements=256 "
|
||||
f"(got {block_elements}); use video_sparse_attn for the 64-block path.")
|
||||
|
||||
batch, q_seq_len, heads, dim = q.shape
|
||||
kv_seq_len = k.shape[1]
|
||||
if k.shape[0] != batch or v.shape[0] != batch or k.shape[2] != heads or v.shape[2] != heads:
|
||||
raise ValueError("Expected q/k/v to have the same batch and head dimensions.")
|
||||
if v.shape[1] != kv_seq_len:
|
||||
raise ValueError(
|
||||
f"Expected k and v to have the same sequence length, got "
|
||||
f"k.shape[1]={kv_seq_len}, v.shape[1]={v.shape[1]}"
|
||||
)
|
||||
raise ValueError(f"Expected k and v to have the same sequence length, got "
|
||||
f"k.shape[1]={kv_seq_len}, v.shape[1]={v.shape[1]}")
|
||||
if q_seq_len % block_elements != 0 or kv_seq_len % block_elements != 0:
|
||||
raise ValueError(
|
||||
f"q_seq_len and kv_seq_len must be divisible by block_elements={block_elements}, "
|
||||
f"got q_seq_len={q_seq_len}, kv_seq_len={kv_seq_len}"
|
||||
)
|
||||
raise ValueError(f"q_seq_len and kv_seq_len must be divisible by block_elements={block_elements}, "
|
||||
f"got q_seq_len={q_seq_len}, kv_seq_len={kv_seq_len}")
|
||||
q_num_blocks = q_seq_len // block_elements
|
||||
kv_num_blocks = kv_seq_len // block_elements
|
||||
if variable_block_sizes.numel() != kv_num_blocks:
|
||||
raise ValueError(
|
||||
f"variable_block_sizes must have length kv_num_blocks={kv_num_blocks}, "
|
||||
f"got {variable_block_sizes.numel()}"
|
||||
)
|
||||
raise ValueError(f"variable_block_sizes must have length kv_num_blocks={kv_num_blocks}, "
|
||||
f"got {variable_block_sizes.numel()}")
|
||||
if q_variable_block_sizes.numel() != q_num_blocks:
|
||||
raise ValueError(
|
||||
f"q_variable_block_sizes must have length q_num_blocks={q_num_blocks}, "
|
||||
f"got {q_variable_block_sizes.numel()}"
|
||||
)
|
||||
raise ValueError(f"q_variable_block_sizes must have length q_num_blocks={q_num_blocks}, "
|
||||
f"got {q_variable_block_sizes.numel()}")
|
||||
|
||||
# Compression branch (BSHD-native: mean over the 256-token axis).
|
||||
token_idx = torch.arange(block_elements, device=q.device, dtype=torch.int32)
|
||||
q_token_valid = (token_idx.view(1, -1) < q_variable_block_sizes.view(-1, 1)).view(
|
||||
1, q_num_blocks, block_elements, 1, 1
|
||||
)
|
||||
kv_token_valid = (token_idx.view(1, -1) < variable_block_sizes.view(-1, 1)).view(
|
||||
1, kv_num_blocks, block_elements, 1, 1
|
||||
)
|
||||
q_token_valid = (token_idx.view(1, -1) < q_variable_block_sizes.view(-1,
|
||||
1)).view(1, q_num_blocks, block_elements, 1, 1)
|
||||
kv_token_valid = (token_idx.view(1, -1) < variable_block_sizes.view(-1,
|
||||
1)).view(1, kv_num_blocks, block_elements, 1, 1)
|
||||
|
||||
q_c = q.view(batch, q_num_blocks, block_elements, heads, dim)
|
||||
k_c = k.view(batch, kv_num_blocks, block_elements, heads, dim)
|
||||
v_c = v.view(batch, kv_num_blocks, block_elements, heads, dim)
|
||||
q_c = ((q_c.float() * q_token_valid).sum(dim=2)
|
||||
/ q_variable_block_sizes.view(1, -1, 1, 1)).to(q.dtype)
|
||||
k_c = ((k_c.float() * kv_token_valid).sum(dim=2)
|
||||
/ variable_block_sizes.view(1, -1, 1, 1)).to(k.dtype)
|
||||
v_c = ((v_c.float() * kv_token_valid).sum(dim=2)
|
||||
/ variable_block_sizes.view(1, -1, 1, 1)).to(v.dtype)
|
||||
q_c = ((q_c.float() * q_token_valid).sum(dim=2) / q_variable_block_sizes.view(1, -1, 1, 1)).to(q.dtype)
|
||||
k_c = ((k_c.float() * kv_token_valid).sum(dim=2) / variable_block_sizes.view(1, -1, 1, 1)).to(k.dtype)
|
||||
v_c = ((v_c.float() * kv_token_valid).sum(dim=2) / variable_block_sizes.view(1, -1, 1, 1)).to(v.dtype)
|
||||
q_ch = q_c.permute(0, 2, 1, 3).contiguous()
|
||||
k_ch = k_c.permute(0, 2, 1, 3).contiguous()
|
||||
v_ch = v_c.permute(0, 2, 1, 3).contiguous()
|
||||
|
||||
scores = torch.matmul(q_ch, k_ch.transpose(-2, -1)) / (dim ** 0.5)
|
||||
scores = torch.matmul(q_ch, k_ch.transpose(-2, -1)) / (dim**0.5)
|
||||
attn = torch.softmax(scores, dim=-1)
|
||||
out_c_ch = torch.matmul(attn, v_ch)
|
||||
out_c_blk = out_c_ch.permute(0, 2, 1, 3).contiguous()
|
||||
@@ -229,9 +200,7 @@ def video_sparse_attn_bshd(
|
||||
out = out_s
|
||||
out_view = out.view(batch, q_num_blocks, block_elements, heads, dim)
|
||||
if compress_attn_weight is not None:
|
||||
gate_view = compress_attn_weight.view(
|
||||
batch, q_num_blocks, block_elements, heads, dim
|
||||
)
|
||||
gate_view = compress_attn_weight.view(batch, q_num_blocks, block_elements, heads, dim)
|
||||
out_view.add_(out_c_blk.unsqueeze(2) * gate_view)
|
||||
else:
|
||||
out_view.add_(out_c_blk.unsqueeze(2))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+9
-16
@@ -104,8 +104,7 @@ def _attn_fwd_sparse(
|
||||
block_shape=(HEAD_DIM, BLOCK_N),
|
||||
order=(0, 1))
|
||||
|
||||
v_order: tl.constexpr = (0, 1) if V.dtype.element_ty == tl.float8e5 else (1,
|
||||
0)
|
||||
v_order: tl.constexpr = (0, 1) if V.dtype.element_ty == tl.float8e5 else (1, 0)
|
||||
V_base = tl.make_block_ptr(base=V + v_off,
|
||||
shape=(N_CTX_KV, HEAD_DIM),
|
||||
strides=(stride_vk, stride_vn),
|
||||
@@ -178,10 +177,8 @@ def _attn_bwd_preprocess(
|
||||
off_hz = tl.program_id(1)
|
||||
off_n = tl.arange(0, HEAD_DIM)
|
||||
# load
|
||||
o = tl.load(O + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM +
|
||||
off_n[None, :])
|
||||
do = tl.load(DO + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM +
|
||||
off_n[None, :]).to(tl.float32)
|
||||
o = tl.load(O + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM + off_n[None, :])
|
||||
do = tl.load(DO + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM + off_n[None, :]).to(tl.float32)
|
||||
delta = tl.sum(o * do, axis=1)
|
||||
# write-back
|
||||
tl.store(Delta + off_hz * N_CTX + off_m, delta)
|
||||
@@ -235,8 +232,7 @@ def _attn_bwd_dkdv(
|
||||
block_size = tl.load(variable_block_sizes + kv_blk)
|
||||
|
||||
for blk_idx in range(q_blocks * 2):
|
||||
block_sparse_offset = (tl.load(q_ptr + blk_idx // 2).to(tl.int32) * 2 +
|
||||
blk_idx % 2) * step_m
|
||||
block_sparse_offset = (tl.load(q_ptr + blk_idx // 2).to(tl.int32) * 2 + blk_idx % 2) * step_m
|
||||
qT = tl.load(qT_ptrs + block_sparse_offset * stride_tok)
|
||||
# Load m before computing qk to reduce pipeline stall.
|
||||
offs_m = start_m + block_sparse_offset + tl.arange(0, BLOCK_M1)
|
||||
@@ -497,7 +493,7 @@ def _attn_bwd_dkdv_kernel(
|
||||
stride_tok,
|
||||
stride_d, #
|
||||
# batch/head strides (may differ between Q and KV)
|
||||
stride_qz,
|
||||
stride_qz,
|
||||
stride_qh,
|
||||
stride_kz,
|
||||
stride_kh,
|
||||
@@ -607,7 +603,7 @@ def _attn_bwd_dq_kernel(
|
||||
stride_tok,
|
||||
stride_d, #
|
||||
# batch/head strides (may differ between Q and KV)
|
||||
stride_qz,
|
||||
stride_qz,
|
||||
stride_qh,
|
||||
stride_kz,
|
||||
stride_kh,
|
||||
@@ -689,8 +685,7 @@ def _attn_bwd_dq_kernel(
|
||||
|
||||
|
||||
# ──────────────────────────── SPARSE ADDITION BEGIN ───────────────────────────
|
||||
def triton_block_sparse_attn_forward(q, k, v, q2k_index, q2k_num,
|
||||
variable_block_sizes):
|
||||
def triton_block_sparse_attn_forward(q, k, v, q2k_index, q2k_num, variable_block_sizes):
|
||||
B, H, Tq, D = q.shape
|
||||
Tkv = k.shape[2]
|
||||
sm_scale = 1.0 / math.sqrt(D)
|
||||
@@ -701,8 +696,7 @@ def triton_block_sparse_attn_forward(q, k, v, q2k_index, q2k_num,
|
||||
-1] == Tq // 64, f"shape mismatch, Tq // 64 = {Tq // 64}, q2k_num.shape[-2] = {q2k_num.shape[-2]}"
|
||||
assert variable_block_sizes.numel() == Tkv // 64, (
|
||||
f"shape mismatch, variable_block_sizes must have length {Tkv // 64}, "
|
||||
f"got {variable_block_sizes.numel()}"
|
||||
)
|
||||
f"got {variable_block_sizes.numel()}")
|
||||
o = torch.empty_like(q)
|
||||
M = torch.empty((B, H, Tq), dtype=torch.float32, device=q.device)
|
||||
|
||||
@@ -743,8 +737,7 @@ def triton_block_sparse_attn_forward(q, k, v, q2k_index, q2k_num,
|
||||
return o, M
|
||||
|
||||
|
||||
def triton_block_sparse_attn_backward(do, q, k, v, o, M, q2k_index, q2k_num,
|
||||
k2q_index, k2q_num, variable_block_sizes):
|
||||
def triton_block_sparse_attn_backward(do, q, k, v, o, M, q2k_index, q2k_num, k2q_index, k2q_num, variable_block_sizes):
|
||||
assert do.is_contiguous()
|
||||
|
||||
B, H, Tq, D = q.shape
|
||||
|
||||
@@ -122,9 +122,13 @@ def _fused_block_mean_bwd(
|
||||
grid = (num_blocks, B * H)
|
||||
|
||||
_fused_block_mean_bwd_kernel[grid](
|
||||
go_flat, gx_flat, variable_block_sizes,
|
||||
go_flat.stride(0), go_flat.stride(1),
|
||||
gx_flat.stride(0), gx_flat.stride(1),
|
||||
go_flat,
|
||||
gx_flat,
|
||||
variable_block_sizes,
|
||||
go_flat.stride(0),
|
||||
go_flat.stride(1),
|
||||
gx_flat.stride(0),
|
||||
gx_flat.stride(1),
|
||||
num_blocks,
|
||||
BLOCK_ELEMENTS=block_elements,
|
||||
HEAD_DIM=D,
|
||||
@@ -152,9 +156,13 @@ def _fused_block_mean_fwd(
|
||||
grid = (num_blocks, B * H)
|
||||
|
||||
_fused_block_mean_kernel[grid](
|
||||
x_flat, out_flat, variable_block_sizes,
|
||||
x_flat.stride(0), x_flat.stride(1),
|
||||
out_flat.stride(0), out_flat.stride(1),
|
||||
x_flat,
|
||||
out_flat,
|
||||
variable_block_sizes,
|
||||
x_flat.stride(0),
|
||||
x_flat.stride(1),
|
||||
out_flat.stride(0),
|
||||
out_flat.stride(1),
|
||||
num_blocks,
|
||||
BLOCK_ELEMENTS=block_elements,
|
||||
HEAD_DIM=D,
|
||||
@@ -311,7 +319,8 @@ def fused_topk_mask(
|
||||
logger.debug(
|
||||
"fused_topk_mask: kv_blocks=%d exceeds Triton limit %d, "
|
||||
"falling back to PyTorch topk (slower)",
|
||||
kv_blocks, MAX_KV_BLOCK_SIZE,
|
||||
kv_blocks,
|
||||
MAX_KV_BLOCK_SIZE,
|
||||
)
|
||||
return _pytorch_topk_mask_fallback(scores, topk)
|
||||
|
||||
@@ -323,9 +332,14 @@ def fused_topk_mask(
|
||||
grid = (q_blocks, B * H)
|
||||
|
||||
_fused_topk_mask_kernel[grid](
|
||||
scores_flat, mask_flat,
|
||||
scores_flat.stride(0), scores_flat.stride(1), scores_flat.stride(2),
|
||||
mask_flat.stride(0), mask_flat.stride(1), mask_flat.stride(2),
|
||||
scores_flat,
|
||||
mask_flat,
|
||||
scores_flat.stride(0),
|
||||
scores_flat.stride(1),
|
||||
scores_flat.stride(2),
|
||||
mask_flat.stride(0),
|
||||
mask_flat.stride(1),
|
||||
mask_flat.stride(2),
|
||||
kv_blocks=kv_blocks,
|
||||
topk=topk,
|
||||
KV_BLOCK_SIZE=KV_BLOCK_SIZE,
|
||||
|
||||
@@ -5,6 +5,7 @@ import triton
|
||||
import triton.language as tl
|
||||
import torch
|
||||
|
||||
|
||||
@triton.jit
|
||||
def topk_index_to_map_kernel(
|
||||
map_ptr,
|
||||
@@ -57,14 +58,10 @@ def map_to_index_kernel(
|
||||
tl.store(index_ptr_base + num * index_kv_stride, i)
|
||||
num += 1
|
||||
|
||||
tl.store(
|
||||
index_num_ptr + b * index_num_bs_stride + h * index_num_h_stride +
|
||||
q * index_num_q_stride, num)
|
||||
tl.store(index_num_ptr + b * index_num_bs_stride + h * index_num_h_stride + q * index_num_q_stride, num)
|
||||
|
||||
|
||||
def topk_index_to_map(index: torch.Tensor,
|
||||
num_kv_blocks: int,
|
||||
transpose_map: bool = False):
|
||||
def topk_index_to_map(index: torch.Tensor, num_kv_blocks: int, transpose_map: bool = False):
|
||||
"""
|
||||
Convert topk indices to a map.
|
||||
|
||||
@@ -83,13 +80,9 @@ def topk_index_to_map(index: torch.Tensor,
|
||||
bs, h, num_q_blocks, topk = index.shape
|
||||
|
||||
if transpose_map is False:
|
||||
block_map = torch.zeros((bs, h, num_q_blocks, num_kv_blocks),
|
||||
dtype=torch.bool,
|
||||
device=index.device)
|
||||
block_map = torch.zeros((bs, h, num_q_blocks, num_kv_blocks), dtype=torch.bool, device=index.device)
|
||||
else:
|
||||
block_map = torch.zeros((bs, h, num_kv_blocks, num_q_blocks),
|
||||
dtype=torch.bool,
|
||||
device=index.device)
|
||||
block_map = torch.zeros((bs, h, num_kv_blocks, num_q_blocks), dtype=torch.bool, device=index.device)
|
||||
block_map = block_map.transpose(2, 3)
|
||||
|
||||
grid = (bs, h, num_q_blocks)
|
||||
@@ -126,13 +119,8 @@ def map_to_index(block_map: torch.Tensor):
|
||||
"""
|
||||
bs, h, num_q_blocks, num_kv_blocks = block_map.shape
|
||||
|
||||
index = torch.full((block_map.shape),
|
||||
-1,
|
||||
dtype=torch.int32,
|
||||
device=block_map.device)
|
||||
index_num = torch.empty((bs, h, num_q_blocks),
|
||||
dtype=torch.int32,
|
||||
device=block_map.device)
|
||||
index = torch.full((block_map.shape), -1, dtype=torch.int32, device=block_map.device)
|
||||
index_num = torch.empty((bs, h, num_q_blocks), dtype=torch.int32, device=block_map.device)
|
||||
|
||||
grid = (bs, h, num_q_blocks)
|
||||
map_to_index_kernel[grid](
|
||||
@@ -162,10 +150,20 @@ def _invert_indices_kernel(
|
||||
q2k_num_ptr,
|
||||
k2q_idx_ptr,
|
||||
k2q_num_ptr,
|
||||
q2k_idx_b, q2k_idx_h, q2k_idx_q, q2k_idx_k,
|
||||
q2k_num_b, q2k_num_h, q2k_num_q,
|
||||
k2q_idx_b, k2q_idx_h, k2q_idx_k, k2q_idx_q,
|
||||
k2q_num_b, k2q_num_h, k2q_num_k,
|
||||
q2k_idx_b,
|
||||
q2k_idx_h,
|
||||
q2k_idx_q,
|
||||
q2k_idx_k,
|
||||
q2k_num_b,
|
||||
q2k_num_h,
|
||||
q2k_num_q,
|
||||
k2q_idx_b,
|
||||
k2q_idx_h,
|
||||
k2q_idx_k,
|
||||
k2q_idx_q,
|
||||
k2q_num_b,
|
||||
k2q_num_h,
|
||||
k2q_num_k,
|
||||
MAX_KV_PER_Q: tl.constexpr,
|
||||
):
|
||||
# One program per (b, h, q): reserve a slot in k2q via atomicAdd, write q.
|
||||
@@ -173,36 +171,17 @@ def _invert_indices_kernel(
|
||||
pid_h = tl.program_id(1)
|
||||
pid_q = tl.program_id(2)
|
||||
|
||||
n = tl.load(
|
||||
q2k_num_ptr
|
||||
+ pid_b * q2k_num_b
|
||||
+ pid_h * q2k_num_h
|
||||
+ pid_q * q2k_num_q
|
||||
)
|
||||
n = tl.load(q2k_num_ptr + pid_b * q2k_num_b + pid_h * q2k_num_h + pid_q * q2k_num_q)
|
||||
|
||||
q2k_row = (
|
||||
q2k_idx_ptr
|
||||
+ pid_b * q2k_idx_b
|
||||
+ pid_h * q2k_idx_h
|
||||
+ pid_q * q2k_idx_q
|
||||
)
|
||||
q2k_row = (q2k_idx_ptr + pid_b * q2k_idx_b + pid_h * q2k_idx_h + pid_q * q2k_idx_q)
|
||||
|
||||
for i in tl.range(0, MAX_KV_PER_Q):
|
||||
if i < n:
|
||||
kv = tl.load(q2k_row + i * q2k_idx_k)
|
||||
count_ptr = (
|
||||
k2q_num_ptr
|
||||
+ pid_b * k2q_num_b
|
||||
+ pid_h * k2q_num_h
|
||||
+ kv * k2q_num_k
|
||||
)
|
||||
count_ptr = (k2q_num_ptr + pid_b * k2q_num_b + pid_h * k2q_num_h + kv * k2q_num_k)
|
||||
pos = tl.atomic_add(count_ptr, 1)
|
||||
tl.store(
|
||||
k2q_idx_ptr
|
||||
+ pid_b * k2q_idx_b
|
||||
+ pid_h * k2q_idx_h
|
||||
+ kv * k2q_idx_k
|
||||
+ pos * k2q_idx_q,
|
||||
k2q_idx_ptr + pid_b * k2q_idx_b + pid_h * k2q_idx_h + kv * k2q_idx_k + pos * k2q_idx_q,
|
||||
pid_q,
|
||||
)
|
||||
|
||||
@@ -214,22 +193,16 @@ def invert_indices(
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Transpose a Q->KV index list into a K->Q one via atomic compaction (GPU)."""
|
||||
if q2k_idx.dim() != 4:
|
||||
raise ValueError(
|
||||
f"q2k_idx must be [B, H, Nq, Mk], got shape={tuple(q2k_idx.shape)}"
|
||||
)
|
||||
raise ValueError(f"q2k_idx must be [B, H, Nq, Mk], got shape={tuple(q2k_idx.shape)}")
|
||||
if q2k_num.dim() != 3:
|
||||
raise ValueError(
|
||||
f"q2k_num must be [B, H, Nq], got shape={tuple(q2k_num.shape)}"
|
||||
)
|
||||
raise ValueError(f"q2k_num must be [B, H, Nq], got shape={tuple(q2k_num.shape)}")
|
||||
if not q2k_idx.is_cuda or not q2k_num.is_cuda:
|
||||
raise RuntimeError("invert_indices requires CUDA tensors.")
|
||||
|
||||
B, H, Nq, Mk = q2k_idx.shape
|
||||
if q2k_num.shape != (B, H, Nq):
|
||||
raise ValueError(
|
||||
f"q2k_num shape {tuple(q2k_num.shape)} does not match q2k_idx "
|
||||
f"[B, H, Nq] = {(B, H, Nq)}"
|
||||
)
|
||||
raise ValueError(f"q2k_num shape {tuple(q2k_num.shape)} does not match q2k_idx "
|
||||
f"[B, H, Nq] = {(B, H, Nq)}")
|
||||
|
||||
q2k_idx = q2k_idx.contiguous()
|
||||
q2k_num = q2k_num.contiguous()
|
||||
@@ -257,10 +230,20 @@ def invert_indices(
|
||||
q2k_num,
|
||||
k2q_idx,
|
||||
k2q_num,
|
||||
q2k_idx.stride(0), q2k_idx.stride(1), q2k_idx.stride(2), q2k_idx.stride(3),
|
||||
q2k_num.stride(0), q2k_num.stride(1), q2k_num.stride(2),
|
||||
k2q_idx.stride(0), k2q_idx.stride(1), k2q_idx.stride(2), k2q_idx.stride(3),
|
||||
k2q_num.stride(0), k2q_num.stride(1), k2q_num.stride(2),
|
||||
q2k_idx.stride(0),
|
||||
q2k_idx.stride(1),
|
||||
q2k_idx.stride(2),
|
||||
q2k_idx.stride(3),
|
||||
q2k_num.stride(0),
|
||||
q2k_num.stride(1),
|
||||
q2k_num.stride(2),
|
||||
k2q_idx.stride(0),
|
||||
k2q_idx.stride(1),
|
||||
k2q_idx.stride(2),
|
||||
k2q_idx.stride(3),
|
||||
k2q_num.stride(0),
|
||||
k2q_num.stride(1),
|
||||
k2q_num.stride(2),
|
||||
MAX_KV_PER_Q=Mk,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from triton.language.target_info import cuda_capability_geq
|
||||
|
||||
MXFP_BLOCK_SIZE = tl.constexpr(16)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _compute_quant_and_scale(
|
||||
src_tensor,
|
||||
@@ -22,9 +23,7 @@ def _compute_quant_and_scale(
|
||||
is_fp4: tl.constexpr = mx_tensor_dtype == tl.uint8
|
||||
|
||||
tl.static_assert(
|
||||
is_fp4
|
||||
or mx_tensor_dtype == tl.float8e4nv
|
||||
or mx_tensor_dtype == tl.float8e5,
|
||||
is_fp4 or mx_tensor_dtype == tl.float8e4nv or mx_tensor_dtype == tl.float8e5,
|
||||
"mx_tensor_dtype must be uint8, float8e4nv, or float8e5",
|
||||
)
|
||||
|
||||
@@ -32,7 +31,7 @@ def _compute_quant_and_scale(
|
||||
f32_tensor = src_tensor.to(tl.float32)
|
||||
abs_tensor = tl.abs(f32_tensor)
|
||||
abs_tensor = tl.where(valid_src_mask, abs_tensor, -1.0) # Don't consider padding tensors in scale computation
|
||||
|
||||
|
||||
if two_level_quant_P:
|
||||
# row max from SageAttn3 paper
|
||||
global_max_val = tl.max(f32_tensor, axis=1, keep_dims=True) # (BLOCK_SIZE_OUT_DIM, 1)
|
||||
@@ -51,8 +50,9 @@ def _compute_quant_and_scale(
|
||||
elif not two_level_quant_P and not use_global_sf:
|
||||
s_dec = 1.0
|
||||
s_enc = 1.0
|
||||
|
||||
max_val = tl.max(abs_tensor, axis=2, keep_dims=True) # (BLOCK_SIZE_OUT_DIM, BLOCK_SIZE_QUANT_MX_SCALE, 1) # per block maxima
|
||||
|
||||
max_val = tl.max(abs_tensor, axis=2,
|
||||
keep_dims=True) # (BLOCK_SIZE_OUT_DIM, BLOCK_SIZE_QUANT_MX_SCALE, 1) # per block maxima
|
||||
s_dec_b = max_val / 6 # (BLOCK_SIZE_OUT_DIM, BLOCK_SIZE_QUANT_MX_SCALE, 1)
|
||||
s_dec_b_e4m3 = (s_dec_b * s_enc).to(tl.float8e4nv) # (BLOCK_SIZE_OUT_DIM, BLOCK_SIZE_QUANT_MX_SCALE, 1)
|
||||
s_enc_b = 1 / (s_dec_b_e4m3.to(tl.float32) * s_dec) # (BLOCK_SIZE_OUT_DIM, BLOCK_SIZE_QUANT_MX_SCALE, 1)
|
||||
@@ -132,6 +132,7 @@ def _compute_quant_and_scale(
|
||||
|
||||
return out_tensor, dequant_scale, s_dec
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _compute_dequant(
|
||||
mx_tensor,
|
||||
@@ -141,7 +142,8 @@ def _compute_dequant(
|
||||
BLOCK_SIZE_QUANT_DIM: tl.constexpr,
|
||||
dst_dtype: tl.constexpr,
|
||||
):
|
||||
tl.static_assert(BLOCK_SIZE_QUANT_DIM % MXFP_BLOCK_SIZE == 0, f"Block size along quantization block must be a multiple of {MXFP_BLOCK_SIZE=}")
|
||||
tl.static_assert(BLOCK_SIZE_QUANT_DIM % MXFP_BLOCK_SIZE == 0,
|
||||
f"Block size along quantization block must be a multiple of {MXFP_BLOCK_SIZE=}")
|
||||
# uint8 signifies two fp4 e2m1 values packed into a single byte
|
||||
mx_tensor_dtype: tl.constexpr = mx_tensor.dtype
|
||||
tl.static_assert(dst_dtype == tl.float16 or dst_dtype == tl.bfloat16 or dst_dtype == tl.float32)
|
||||
|
||||
@@ -3,33 +3,44 @@ import triton.language as tl
|
||||
|
||||
from .nvfp4_utils import _compute_quant_and_scale, _compute_dequant
|
||||
|
||||
|
||||
@triton.jit
|
||||
def fake_quantize(src_tensor, valid_src_mask, BLOCK_SIZE_OUT_DIM: tl.constexpr,
|
||||
BLOCK_SIZE_QUANT_DIM: tl.constexpr,
|
||||
dst_dtype: tl.constexpr,
|
||||
mx_tensor_dtype: tl.constexpr = tl.uint8,
|
||||
use_global_sf: tl.constexpr = True,
|
||||
two_level_quant_P: tl.constexpr = False):
|
||||
def fake_quantize(src_tensor,
|
||||
valid_src_mask,
|
||||
BLOCK_SIZE_OUT_DIM: tl.constexpr,
|
||||
BLOCK_SIZE_QUANT_DIM: tl.constexpr,
|
||||
dst_dtype: tl.constexpr,
|
||||
mx_tensor_dtype: tl.constexpr = tl.uint8,
|
||||
use_global_sf: tl.constexpr = True,
|
||||
two_level_quant_P: tl.constexpr = False):
|
||||
high_prec_src_tensor = src_tensor
|
||||
src_tensor, src_scale, src_s_dec = _compute_quant_and_scale(src_tensor=src_tensor,
|
||||
valid_src_mask=valid_src_mask,
|
||||
mx_tensor_dtype=mx_tensor_dtype,
|
||||
use_global_sf=use_global_sf,
|
||||
two_level_quant_P=two_level_quant_P)
|
||||
src_tensor = _compute_dequant(mx_tensor=src_tensor,
|
||||
scale=src_scale,
|
||||
s_dec=src_s_dec,
|
||||
BLOCK_SIZE_OUT_DIM=BLOCK_SIZE_OUT_DIM,
|
||||
BLOCK_SIZE_QUANT_DIM=BLOCK_SIZE_QUANT_DIM,
|
||||
src_tensor = _compute_dequant(mx_tensor=src_tensor,
|
||||
scale=src_scale,
|
||||
s_dec=src_s_dec,
|
||||
BLOCK_SIZE_OUT_DIM=BLOCK_SIZE_OUT_DIM,
|
||||
BLOCK_SIZE_QUANT_DIM=BLOCK_SIZE_QUANT_DIM,
|
||||
dst_dtype=dst_dtype)
|
||||
return src_tensor, high_prec_src_tensor.to(src_tensor.dtype)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def fake_quantize_q(Q, fake_Q, stride_z_q, stride_h_q,
|
||||
stride_tok_q, stride_d_q,
|
||||
fake_stride_z_q, fake_stride_h_q,
|
||||
fake_stride_tok_q, fake_stride_d_q,
|
||||
H, N_CTX_Q,
|
||||
def fake_quantize_q(Q,
|
||||
fake_Q,
|
||||
stride_z_q,
|
||||
stride_h_q,
|
||||
stride_tok_q,
|
||||
stride_d_q,
|
||||
fake_stride_z_q,
|
||||
fake_stride_h_q,
|
||||
fake_stride_tok_q,
|
||||
fake_stride_d_q,
|
||||
H,
|
||||
N_CTX_Q,
|
||||
BLOCK_M: tl.constexpr,
|
||||
HEAD_DIM: tl.constexpr,
|
||||
use_global_sf: tl.constexpr = True):
|
||||
@@ -46,18 +57,33 @@ def fake_quantize_q(Q, fake_Q, stride_z_q, stride_h_q,
|
||||
|
||||
q_valid = offs_m < N_CTX_Q
|
||||
q = tl.load(Q + offs_m[:, None] * stride_tok_q + offs_k[None, :] * stride_d_q, mask=q_valid[:, None], other=0.0)
|
||||
q, _ = fake_quantize(src_tensor=q, valid_src_mask=q_valid[:, None], BLOCK_SIZE_OUT_DIM=BLOCK_M, BLOCK_SIZE_QUANT_DIM=HEAD_DIM, dst_dtype=q.dtype, use_global_sf=use_global_sf)
|
||||
q, _ = fake_quantize(src_tensor=q,
|
||||
valid_src_mask=q_valid[:, None],
|
||||
BLOCK_SIZE_OUT_DIM=BLOCK_M,
|
||||
BLOCK_SIZE_QUANT_DIM=HEAD_DIM,
|
||||
dst_dtype=q.dtype,
|
||||
use_global_sf=use_global_sf)
|
||||
tl.store(fake_Q + offs_m[:, None] * fake_stride_tok_q + offs_k[None, :] * fake_stride_d_q, q, mask=q_valid[:, None])
|
||||
|
||||
|
||||
@triton.jit
|
||||
def fake_quantize_kv(K, V, fake_K, fake_V, stride_z_kv, stride_h_kv,
|
||||
stride_tok_kv, stride_d_kv,
|
||||
fake_stride_z_kv, fake_stride_h_kv,
|
||||
fake_stride_tok_kv, fake_stride_d_kv,
|
||||
H, N_CTX_KV,
|
||||
BLOCK_N: tl.constexpr,
|
||||
HEAD_DIM: tl.constexpr,
|
||||
use_global_sf: tl.constexpr = True):
|
||||
def fake_quantize_kv(K,
|
||||
V,
|
||||
fake_K,
|
||||
fake_V,
|
||||
stride_z_kv,
|
||||
stride_h_kv,
|
||||
stride_tok_kv,
|
||||
stride_d_kv,
|
||||
fake_stride_z_kv,
|
||||
fake_stride_h_kv,
|
||||
fake_stride_tok_kv,
|
||||
fake_stride_d_kv,
|
||||
H,
|
||||
N_CTX_KV,
|
||||
BLOCK_N: tl.constexpr,
|
||||
HEAD_DIM: tl.constexpr,
|
||||
use_global_sf: tl.constexpr = True):
|
||||
bhid = tl.program_id(1)
|
||||
adj_kv = (stride_h_kv * (bhid % H) + stride_z_kv * (bhid // H))
|
||||
fake_adj_kv = (fake_stride_h_kv * (bhid % H) + fake_stride_z_kv * (bhid // H))
|
||||
@@ -70,11 +96,29 @@ def fake_quantize_kv(K, V, fake_K, fake_V, stride_z_kv, stride_h_kv,
|
||||
start_n = pid * BLOCK_N
|
||||
offs_n = start_n + tl.arange(0, BLOCK_N)
|
||||
offs_k = tl.arange(0, HEAD_DIM)
|
||||
|
||||
|
||||
kv_valid = offs_n < N_CTX_KV
|
||||
k_block = tl.load(K + offs_n[:, None] * stride_tok_kv + offs_k[None, :] * stride_d_kv, mask=kv_valid[:, None], other=0.0)
|
||||
v_block = tl.load(V + offs_n[:, None] * stride_tok_kv + offs_k[None, :] * stride_d_kv, mask=kv_valid[:, None], other=0.0)
|
||||
k, _ = fake_quantize(src_tensor=k_block, valid_src_mask=kv_valid[:, None], BLOCK_SIZE_OUT_DIM=BLOCK_N, BLOCK_SIZE_QUANT_DIM=HEAD_DIM, dst_dtype=k_block.dtype, use_global_sf=use_global_sf)
|
||||
v, _ = fake_quantize(src_tensor=v_block, valid_src_mask=kv_valid[:, None], BLOCK_SIZE_OUT_DIM=BLOCK_N, BLOCK_SIZE_QUANT_DIM=HEAD_DIM, dst_dtype=v_block.dtype, use_global_sf=use_global_sf)
|
||||
tl.store(fake_K + offs_n[:, None] * fake_stride_tok_kv + offs_k[None, :] * fake_stride_d_kv, k, mask=kv_valid[:, None])
|
||||
tl.store(fake_V + offs_n[:, None] * fake_stride_tok_kv + offs_k[None, :] * fake_stride_d_kv, v, mask=kv_valid[:, None])
|
||||
k_block = tl.load(K + offs_n[:, None] * stride_tok_kv + offs_k[None, :] * stride_d_kv,
|
||||
mask=kv_valid[:, None],
|
||||
other=0.0)
|
||||
v_block = tl.load(V + offs_n[:, None] * stride_tok_kv + offs_k[None, :] * stride_d_kv,
|
||||
mask=kv_valid[:, None],
|
||||
other=0.0)
|
||||
k, _ = fake_quantize(src_tensor=k_block,
|
||||
valid_src_mask=kv_valid[:, None],
|
||||
BLOCK_SIZE_OUT_DIM=BLOCK_N,
|
||||
BLOCK_SIZE_QUANT_DIM=HEAD_DIM,
|
||||
dst_dtype=k_block.dtype,
|
||||
use_global_sf=use_global_sf)
|
||||
v, _ = fake_quantize(src_tensor=v_block,
|
||||
valid_src_mask=kv_valid[:, None],
|
||||
BLOCK_SIZE_OUT_DIM=BLOCK_N,
|
||||
BLOCK_SIZE_QUANT_DIM=HEAD_DIM,
|
||||
dst_dtype=v_block.dtype,
|
||||
use_global_sf=use_global_sf)
|
||||
tl.store(fake_K + offs_n[:, None] * fake_stride_tok_kv + offs_k[None, :] * fake_stride_d_kv,
|
||||
k,
|
||||
mask=kv_valid[:, None])
|
||||
tl.store(fake_V + offs_n[:, None] * fake_stride_tok_kv + offs_k[None, :] * fake_stride_d_kv,
|
||||
v,
|
||||
mask=kv_valid[:, None])
|
||||
|
||||
@@ -19,10 +19,14 @@ import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def _attn_fwd(
|
||||
Q, K, V,
|
||||
Q,
|
||||
K,
|
||||
V,
|
||||
qk_scale: tl.constexpr,
|
||||
topk: tl.constexpr,
|
||||
LUT, LSE, OS,
|
||||
LUT,
|
||||
LSE,
|
||||
OS,
|
||||
L: tl.constexpr,
|
||||
M_BLOCKS: tl.constexpr,
|
||||
D: tl.constexpr,
|
||||
@@ -45,7 +49,7 @@ def _attn_fwd(
|
||||
OS_ptrs = OS + qkv_offset + offs_m[:, None] * D + 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)
|
||||
@@ -54,7 +58,7 @@ def _attn_fwd(
|
||||
for block_idx in tl.range(topk):
|
||||
idx_n = tl.load(LUT_ptr + block_idx)
|
||||
n_mask = offs_n < L - idx_n * BLOCK_N
|
||||
|
||||
|
||||
k = tl.load(K_ptrs + idx_n * BLOCK_N * D, mask=n_mask[None, :])
|
||||
qk = tl.dot(q, k) * (qk_scale * 1.4426950408889634) # = 1 / ln(2)
|
||||
if L - idx_n * BLOCK_N < BLOCK_N:
|
||||
@@ -76,14 +80,16 @@ def _attn_fwd(
|
||||
|
||||
o_s = o_s / l_i[:, None]
|
||||
tl.store(OS_ptrs, o_s.to(OS.type.element_ty), mask=offs_m[:, None] < L)
|
||||
|
||||
|
||||
m_i += tl.math.log2(l_i)
|
||||
tl.store(LSE_ptrs, m_i, mask=offs_m < L)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _attn_bwd_preprocess(
|
||||
OS, DOS, DELTAS,
|
||||
OS,
|
||||
DOS,
|
||||
DELTAS,
|
||||
L,
|
||||
D: tl.constexpr,
|
||||
BLOCK_M: tl.constexpr,
|
||||
@@ -100,15 +106,21 @@ def _attn_bwd_preprocess(
|
||||
|
||||
o_s = tl.load(OS + offs_m[:, None] * D + offs_d[None, :], mask=offs_m[:, None] < L)
|
||||
do_s = tl.load(DOS + offs_m[:, None] * D + offs_d[None, :], mask=offs_m[:, None] < L)
|
||||
|
||||
|
||||
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 < L)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _attn_bwd_dq(
|
||||
Q, K, V, LSE, DELTAS,
|
||||
DOS, DQ, LUT,
|
||||
Q,
|
||||
K,
|
||||
V,
|
||||
LSE,
|
||||
DELTAS,
|
||||
DOS,
|
||||
DQ,
|
||||
LUT,
|
||||
qk_scale: tl.constexpr,
|
||||
topk: tl.constexpr,
|
||||
L: tl.constexpr,
|
||||
@@ -141,28 +153,36 @@ def _attn_bwd_dq(
|
||||
do_s = tl.load(DOS_ptrs, mask=offs_m[:, None] < L)
|
||||
delta_s = tl.load(DELTAS_ptrs, mask=offs_m < L)
|
||||
lse = tl.load(LSE_ptrs, mask=offs_m < L, 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)
|
||||
n_mask = offs_n < L - idx_n * BLOCK_N
|
||||
|
||||
|
||||
k = tl.load(K_ptrs + idx_n * BLOCK_N * D, mask=n_mask[:, None])
|
||||
v = tl.load(V_ptrs + idx_n * BLOCK_N * D, mask=n_mask[:, None])
|
||||
qk = tl.dot(q, k.T) * (qk_scale * 1.4426950408889634)
|
||||
p = tl.math.exp2(qk - lse[:, None])
|
||||
p = tl.where(n_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] < L)
|
||||
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _attn_bwd_dkdv(
|
||||
Q, K, V, DOS, DK, DV,
|
||||
qk_scale, KBID, LSE, DELTAS,
|
||||
Q,
|
||||
K,
|
||||
V,
|
||||
DOS,
|
||||
DK,
|
||||
DV,
|
||||
qk_scale,
|
||||
KBID,
|
||||
LSE,
|
||||
DELTAS,
|
||||
L: tl.constexpr,
|
||||
M_BLOCKS: tl.constexpr,
|
||||
N_BLOCKS: tl.constexpr,
|
||||
@@ -196,7 +216,7 @@ def _attn_bwd_dkdv(
|
||||
|
||||
k = tl.load(K_ptrs, mask=offs_n[:, None] < L)
|
||||
v = tl.load(V_ptrs, mask=offs_n[:, None] < L)
|
||||
|
||||
|
||||
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, L, BLOCK_M2):
|
||||
@@ -215,7 +235,7 @@ def _attn_bwd_dkdv(
|
||||
dpT = tl.dot(v, tl.trans(do))
|
||||
dsT = pT * (dpT - delta[None, :])
|
||||
dk += tl.dot(dsT.to(q.dtype), q)
|
||||
|
||||
|
||||
Q_ptrs += BLOCK_M2 * D
|
||||
DOS_ptrs += BLOCK_M2 * D
|
||||
LSE_ptrs += BLOCK_M2
|
||||
@@ -225,11 +245,11 @@ def _attn_bwd_dkdv(
|
||||
|
||||
tl.store(DK_ptrs, dk * qk_scale, mask=offs_n[:, None] < L)
|
||||
tl.store(DV_ptrs, dv, mask=offs_n[:, None] < L)
|
||||
|
||||
|
||||
|
||||
class _attention(torch.autograd.Function):
|
||||
"""Sparse attention forward/backward with autograd support."""
|
||||
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, q, k, v, k_block_id, lut, topk, BLOCK_M, BLOCK_N, qk_scale=None):
|
||||
assert q.is_contiguous() and k.is_contiguous() and v.is_contiguous()
|
||||
@@ -248,15 +268,22 @@ class _attention(torch.autograd.Function):
|
||||
lse = torch.empty(q.shape[:-1], device=q.device, dtype=torch.float32)
|
||||
|
||||
grid = (M_BLOCKS, B * H)
|
||||
_attn_fwd[grid](
|
||||
q, k, v, qk_scale, topk,
|
||||
lut, lse, o_s,
|
||||
L, M_BLOCKS,
|
||||
D, BLOCK_M, BLOCK_N,
|
||||
num_warps=4 if q.shape[-1] == 64 else 8,
|
||||
num_stages=3
|
||||
)
|
||||
|
||||
_attn_fwd[grid](q,
|
||||
k,
|
||||
v,
|
||||
qk_scale,
|
||||
topk,
|
||||
lut,
|
||||
lse,
|
||||
o_s,
|
||||
L,
|
||||
M_BLOCKS,
|
||||
D,
|
||||
BLOCK_M,
|
||||
BLOCK_N,
|
||||
num_warps=4 if q.shape[-1] == 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
|
||||
@@ -282,30 +309,52 @@ class _attention(torch.autograd.Function):
|
||||
|
||||
grid = (M_BLOCKS, B * H)
|
||||
_attn_bwd_preprocess[grid](
|
||||
o_s, do_s, delta_s,
|
||||
L, D, BLOCK_M,
|
||||
o_s,
|
||||
do_s,
|
||||
delta_s,
|
||||
L,
|
||||
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,
|
||||
L, M_BLOCKS,
|
||||
D, BLOCK_M, BLOCK_N,
|
||||
num_warps=4 if q.shape[-1] == 64 else 8,
|
||||
num_stages=4 if q.shape[-1] == 64 else 5
|
||||
)
|
||||
_attn_bwd_dq[grid](q,
|
||||
k,
|
||||
v,
|
||||
lse,
|
||||
delta_s,
|
||||
do_s,
|
||||
dq,
|
||||
lut,
|
||||
ctx.qk_scale,
|
||||
ctx.topk,
|
||||
L,
|
||||
M_BLOCKS,
|
||||
D,
|
||||
BLOCK_M,
|
||||
BLOCK_N,
|
||||
num_warps=4 if q.shape[-1] == 64 else 8,
|
||||
num_stages=4 if q.shape[-1] == 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,
|
||||
L, M_BLOCKS, N_BLOCKS,
|
||||
D, BLOCK_M, BLOCK_N,
|
||||
BLOCK_SLICE_FACTOR=BLOCK_M // 64,
|
||||
num_warps=4 if q.shape[-1] == 64 else 8,
|
||||
num_stages=4 if q.shape[-1] == 64 else 5
|
||||
)
|
||||
_attn_bwd_dkdv[grid](q,
|
||||
k,
|
||||
v,
|
||||
do_s,
|
||||
dk,
|
||||
dv,
|
||||
ctx.qk_scale,
|
||||
k_block_id,
|
||||
lse,
|
||||
delta_s,
|
||||
L,
|
||||
M_BLOCKS,
|
||||
N_BLOCKS,
|
||||
D,
|
||||
BLOCK_M,
|
||||
BLOCK_N,
|
||||
BLOCK_SLICE_FACTOR=BLOCK_M // 64,
|
||||
num_warps=4 if q.shape[-1] == 64 else 8,
|
||||
num_stages=4 if q.shape[-1] == 64 else 5)
|
||||
|
||||
return dq, dk, dv, None, None, None, None, None, None
|
||||
|
||||
@@ -3,6 +3,7 @@ import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
def is_cuda():
|
||||
return triton.runtime.driver.active.get_current_target().backend == "cuda"
|
||||
|
||||
@@ -14,7 +15,8 @@ def is_hip():
|
||||
|
||||
def is_cdna3_cdna4():
|
||||
target = triton.runtime.driver.active.get_current_target()
|
||||
return (target.arch == 'gfx1201' or target.arch == 'gfx1101' or target.arch == 'gfx1100' or target.arch == 'gfx1030')
|
||||
return (target.arch == 'gfx1201' or target.arch == 'gfx1101' or target.arch == 'gfx1100'
|
||||
or target.arch == 'gfx1030')
|
||||
|
||||
|
||||
def get_common_autotune_config():
|
||||
@@ -56,10 +58,17 @@ def clamp_int(value, min_val, max_val):
|
||||
|
||||
@triton.jit
|
||||
def _attn_fwd_loop(
|
||||
q, k, v, kv_mask, m, l, acc, sm_scale,
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
kv_mask,
|
||||
m,
|
||||
l,
|
||||
acc,
|
||||
sm_scale,
|
||||
MASK_KV: tl.constexpr,
|
||||
):
|
||||
scores = tl.dot(q, k.T) #[BLOCK_Q, BLOCK_KV]
|
||||
scores = tl.dot(q, k.T) #[BLOCK_Q, BLOCK_KV]
|
||||
scores = scores * sm_scale
|
||||
if MASK_KV:
|
||||
scores = tl.where(kv_mask[None, :], scores, -float('inf'))
|
||||
@@ -86,13 +95,25 @@ def _attn_fwd_loop(
|
||||
)
|
||||
@triton.jit
|
||||
def triton_sta_kernel(
|
||||
Q, K, V, output,
|
||||
batch_size: int, num_heads: int, seq_len: int, head_dim: int,
|
||||
Q,
|
||||
K,
|
||||
V,
|
||||
output,
|
||||
batch_size: int,
|
||||
num_heads: int,
|
||||
seq_len: int,
|
||||
head_dim: int,
|
||||
img_seq_len: int,
|
||||
text_length: int,
|
||||
canvas_t: int, canvas_h: int, canvas_w: int,
|
||||
kernel_t: int, kernel_h: int, kernel_w: int,
|
||||
tile_t: int, tile_h: int, tile_w: int,
|
||||
canvas_t: int,
|
||||
canvas_h: int,
|
||||
canvas_w: int,
|
||||
kernel_t: int,
|
||||
kernel_h: int,
|
||||
kernel_w: int,
|
||||
tile_t: int,
|
||||
tile_h: int,
|
||||
tile_w: int,
|
||||
scale: float,
|
||||
has_text: tl.constexpr,
|
||||
text_q: tl.constexpr,
|
||||
@@ -111,8 +132,8 @@ def triton_sta_kernel(
|
||||
q_tile_flat = tl.program_id(2) // q_block_per_tile
|
||||
q_block_idx = tl.program_id(2) % q_block_per_tile
|
||||
|
||||
m = tl.full((BLOCK_Q,), -float('inf'), dtype=tl.float32)
|
||||
l = tl.zeros((BLOCK_Q,), dtype=tl.float32)
|
||||
m = tl.full((BLOCK_Q, ), -float('inf'), dtype=tl.float32)
|
||||
l = tl.zeros((BLOCK_Q, ), dtype=tl.float32)
|
||||
acc = tl.zeros((BLOCK_Q, BLOCK_DIM), dtype=tl.float32)
|
||||
|
||||
q_offset = (batch_idx * num_heads + head_idx) * seq_len * head_dim
|
||||
@@ -125,11 +146,9 @@ def triton_sta_kernel(
|
||||
q_idx = q_base_idx + q_offset_in_tile
|
||||
q_mask = (q_block_idx * BLOCK_Q + tl.arange(0, BLOCK_Q)) < total_tile_size
|
||||
|
||||
q = tl.load(
|
||||
Q + q_offset + q_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=q_mask[:, None],
|
||||
other=0.0
|
||||
) # [BLOCK_Q, BLOCK_DIM]
|
||||
q = tl.load(Q + q_offset + q_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=q_mask[:, None],
|
||||
other=0.0) # [BLOCK_Q, BLOCK_DIM]
|
||||
|
||||
# Scale sm_scale by log_2(e) and use 2^x instead of exp
|
||||
sm_scale = scale * 1.4426950408889634
|
||||
@@ -175,7 +194,8 @@ def triton_sta_kernel(
|
||||
for kv_tile_t in tl.range(kv_tile_start_t, kv_tile_end_t):
|
||||
for kv_tile_h in tl.range(kv_tile_start_h, kv_tile_end_h):
|
||||
for kv_tile_w in tl.range(kv_tile_start_w, kv_tile_end_w):
|
||||
kv_base_idx = (kv_tile_t * num_tiles_h * num_tiles_w + kv_tile_h * num_tiles_w + kv_tile_w) * total_tile_size
|
||||
kv_base_idx = (kv_tile_t * num_tiles_h * num_tiles_w + kv_tile_h * num_tiles_w +
|
||||
kv_tile_w) * total_tile_size
|
||||
|
||||
for kv_block_idx in tl.range(0, total_tile_size, BLOCK_KV):
|
||||
kv_offset_in_block = tl.arange(0, BLOCK_KV)
|
||||
@@ -184,20 +204,15 @@ def triton_sta_kernel(
|
||||
|
||||
kv_offset = (batch_idx * num_heads + head_idx) * seq_len * head_dim
|
||||
|
||||
k = tl.load(
|
||||
K + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0
|
||||
) # [BLOCK_KV, BLOCK_DIM]
|
||||
v = tl.load(
|
||||
V + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0
|
||||
) # [BLOCK_KV, BLOCK_DIM]
|
||||
k = tl.load(K + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0) # [BLOCK_KV, BLOCK_DIM]
|
||||
v = tl.load(V + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0) # [BLOCK_KV, BLOCK_DIM]
|
||||
|
||||
m, l, acc = _attn_fwd_loop(q, k, v, kv_mask, m, l, acc, sm_scale, False)
|
||||
|
||||
|
||||
# for kv_text
|
||||
if has_text:
|
||||
kv_base_idx = img_seq_len
|
||||
@@ -208,35 +223,32 @@ def triton_sta_kernel(
|
||||
|
||||
kv_offset = (batch_idx * num_heads + head_idx) * seq_len * head_dim
|
||||
|
||||
k = tl.load(
|
||||
K + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0
|
||||
) # [BLOCK_KV, BLOCK_DIM]
|
||||
v = tl.load(
|
||||
V + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0
|
||||
) # [BLOCK_KV, BLOCK_DIM]
|
||||
k = tl.load(K + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0) # [BLOCK_KV, BLOCK_DIM]
|
||||
v = tl.load(V + kv_offset + kv_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
mask=kv_mask[:, None],
|
||||
other=0.0) # [BLOCK_KV, BLOCK_DIM]
|
||||
|
||||
m, l, acc = _attn_fwd_loop(q, k, v, kv_mask, m, l, acc, sm_scale, True)
|
||||
|
||||
|
||||
output_acc = acc / l[:, None]
|
||||
tl.store(
|
||||
output + q_offset + q_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
output_acc,
|
||||
mask=q_mask[:, None]
|
||||
) # [BLOCK_Q, BLOCK_DIM]
|
||||
tl.store(output + q_offset + q_idx[:, None] * head_dim + tl.arange(0, BLOCK_DIM)[None, :],
|
||||
output_acc,
|
||||
mask=q_mask[:, None]) # [BLOCK_Q, BLOCK_DIM]
|
||||
|
||||
|
||||
def sliding_tile_attention_triton(
|
||||
q: torch.Tensor, k: torch.Tensor, v: torch.Tensor,
|
||||
window_size, text_length: int,
|
||||
has_text=True, dit_seq_shape='30x48x80') -> torch.Tensor:
|
||||
def sliding_tile_attention_triton(q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
window_size,
|
||||
text_length: int,
|
||||
has_text=True,
|
||||
dit_seq_shape='30x48x80') -> torch.Tensor:
|
||||
seq_length = q.shape[2]
|
||||
if has_text:
|
||||
assert q.shape[2] >= 115200 and q.shape[2] <= 115456, f"Unsupported {dit_seq_shape}, current shape is {q.shape}, only support '30x48x80' for HunyuanVideo"
|
||||
assert q.shape[2] >= 115200 and q.shape[
|
||||
2] <= 115456, f"Unsupported {dit_seq_shape}, current shape is {q.shape}, only support '30x48x80' for HunyuanVideo"
|
||||
target_size = math.ceil(seq_length / 384) * 384
|
||||
pad_size = target_size - seq_length
|
||||
if pad_size > 0:
|
||||
@@ -244,22 +256,24 @@ def sliding_tile_attention_triton(
|
||||
k = torch.cat([k, k[:, :, -pad_size:]], dim=2)
|
||||
v = torch.cat([v, v[:, :, -pad_size:]], dim=2)
|
||||
else:
|
||||
if dit_seq_shape == '36x48x48': # Stepvideo
|
||||
if dit_seq_shape == '36x48x48': # Stepvideo
|
||||
assert q.shape[2] == 82944
|
||||
elif dit_seq_shape == '18x48x80': # Wan
|
||||
elif dit_seq_shape == '18x48x80': # Wan
|
||||
assert q.shape[2] == 69120
|
||||
else:
|
||||
raise ValueError(f"Unsupported {dit_seq_shape}, current shape is {q.shape}, only support '36x48x48' for Stepvideo and '18x48x80' for Wan")
|
||||
raise ValueError(
|
||||
f"Unsupported {dit_seq_shape}, current shape is {q.shape}, only support '36x48x48' for Stepvideo and '18x48x80' for Wan"
|
||||
)
|
||||
assert q.shape[1] == len(window_size), "Number of heads must match the number of window sizes"
|
||||
|
||||
batch_size, num_heads, seq_len, head_dim = q.shape
|
||||
if dit_seq_shape == '30x48x80': # Hunyuan
|
||||
if dit_seq_shape == '30x48x80': # Hunyuan
|
||||
canvas_t, canvas_h, canvas_w = 30, 48, 80
|
||||
tile_t, tile_h, tile_w = 6, 8, 8
|
||||
elif dit_seq_shape == '36x48x48': # Stepvideo
|
||||
elif dit_seq_shape == '36x48x48': # Stepvideo
|
||||
canvas_t, canvas_h, canvas_w = 36, 48, 48
|
||||
tile_t, tile_h, tile_w = 6, 8, 8
|
||||
elif dit_seq_shape == '18x48x80': # Wan
|
||||
elif dit_seq_shape == '18x48x80': # Wan
|
||||
canvas_t, canvas_h, canvas_w = 18, 48, 80
|
||||
tile_t, tile_h, tile_w = 6, 8, 8
|
||||
|
||||
@@ -283,22 +297,36 @@ def sliding_tile_attention_triton(
|
||||
# This for loop is ugly. but it is actually quite efficient. The sequence dimension alone can already oversubscribe SMs
|
||||
for head_index, (kernel_t, kernel_h, kernel_w) in enumerate(window_size):
|
||||
for batch in range(batch_size):
|
||||
q_head, k_head, v_head, o_head = (q[batch:batch + 1, head_index:head_index + 1],
|
||||
k[batch:batch + 1, head_index:head_index + 1],
|
||||
v[batch:batch + 1, head_index:head_index + 1],
|
||||
output[batch:batch + 1, head_index:head_index + 1])
|
||||
q_head, k_head, v_head, o_head = (q[batch:batch + 1,
|
||||
head_index:head_index + 1], k[batch:batch + 1,
|
||||
head_index:head_index + 1],
|
||||
v[batch:batch + 1,
|
||||
head_index:head_index + 1], output[batch:batch + 1,
|
||||
head_index:head_index + 1])
|
||||
|
||||
# triton_sta_kernel[(1, 1, num_tiles * triton.cdiv(total_tile_size, BLOCK_Q))](
|
||||
grid = lambda META: (1, 1, num_tiles * triton.cdiv(total_tile_size, META['BLOCK_Q']))
|
||||
triton_sta_kernel[grid](
|
||||
q_head, k_head, v_head, o_head,
|
||||
1, 1, seq_len, head_dim,
|
||||
q_head,
|
||||
k_head,
|
||||
v_head,
|
||||
o_head,
|
||||
1,
|
||||
1,
|
||||
seq_len,
|
||||
head_dim,
|
||||
img_seq_len,
|
||||
text_length,
|
||||
canvas_t, canvas_h, canvas_w,
|
||||
kernel_t, kernel_h, kernel_w,
|
||||
tile_t, tile_h, tile_w,
|
||||
scale=1.0 / (head_dim ** 0.5),
|
||||
canvas_t,
|
||||
canvas_h,
|
||||
canvas_w,
|
||||
kernel_t,
|
||||
kernel_h,
|
||||
kernel_w,
|
||||
tile_t,
|
||||
tile_h,
|
||||
tile_w,
|
||||
scale=1.0 / (head_dim**0.5),
|
||||
has_text=has_text,
|
||||
text_q=False,
|
||||
# BLOCK_Q=BLOCK_Q,
|
||||
@@ -312,15 +340,27 @@ def sliding_tile_attention_triton(
|
||||
# triton_sta_kernel[(batch_size, num_heads, triton.cdiv(total_tile_size, BLOCK_Q))](
|
||||
grid = lambda META: (batch_size, num_heads, triton.cdiv(total_tile_size, META['BLOCK_Q']))
|
||||
triton_sta_kernel[grid](
|
||||
q, k, v, output,
|
||||
batch_size, num_heads, seq_len, head_dim,
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
output,
|
||||
batch_size,
|
||||
num_heads,
|
||||
seq_len,
|
||||
head_dim,
|
||||
img_seq_len,
|
||||
text_length,
|
||||
canvas_t, canvas_h, canvas_w,
|
||||
3, 3, 3,
|
||||
canvas_t,
|
||||
canvas_h,
|
||||
canvas_w,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
#kernel_t, kernel_h, kernel_w,
|
||||
tile_t, tile_h, tile_w,
|
||||
scale=1.0 / (head_dim ** 0.5),
|
||||
tile_t,
|
||||
tile_h,
|
||||
tile_w,
|
||||
scale=1.0 / (head_dim**0.5),
|
||||
has_text=has_text,
|
||||
text_q=True,
|
||||
# BLOCK_Q=BLOCK_Q,
|
||||
@@ -332,4 +372,4 @@ def sliding_tile_attention_triton(
|
||||
if pad_size > 0:
|
||||
output = output[:, :, :seq_length]
|
||||
|
||||
return output
|
||||
return output
|
||||
|
||||
@@ -20,6 +20,7 @@ except ImportError:
|
||||
rms_norm_cuda = None
|
||||
layer_norm_cuda = None
|
||||
|
||||
|
||||
def int8_quant(x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Quantize a floating-point tensor to int8 using a custom CUDA kernel.
|
||||
@@ -67,6 +68,7 @@ def int8_linear(
|
||||
gemm_cuda(x_q, x_s, w_q, w_s, y)
|
||||
return y.reshape(*shape[:-1], n)
|
||||
|
||||
|
||||
def flatten_if_batched(*tensors):
|
||||
"""
|
||||
Flattens all input tensors from (B, N, D_i) to (B * N, D_i) if they are batched (3D).
|
||||
@@ -92,9 +94,8 @@ def flatten_if_batched(*tensors):
|
||||
batched = True
|
||||
batch_size = first.shape[0]
|
||||
assert all(t.shape[0] == batch_size for t in tensors), "All input tensors must have the same batch size"
|
||||
assert all(
|
||||
t.shape[1] == first.shape[1] for t in tensors
|
||||
), "All input tensors must have the same sequence length"
|
||||
assert all(t.shape[1] == first.shape[1]
|
||||
for t in tensors), "All input tensors must have the same sequence length"
|
||||
flat_tensors = [t.reshape(-1, t.shape[-1]) for t in tensors]
|
||||
else:
|
||||
batched = False
|
||||
@@ -167,21 +168,21 @@ def rmsnorm(x, w, eps):
|
||||
# allocate output
|
||||
M, N = x.shape
|
||||
y = torch.empty_like(x, dtype=x.dtype)
|
||||
rstd = torch.empty((M,), dtype=torch.float32, device=x.device)
|
||||
rstd = torch.empty((M, ), dtype=torch.float32, device=x.device)
|
||||
|
||||
# heuristics for number of warps
|
||||
num_warps = 8
|
||||
|
||||
|
||||
# Avoid illegal memory access
|
||||
N2 = triton.next_power_of_2(N)
|
||||
|
||||
|
||||
if N <= 512:
|
||||
BLOCK_M = 32
|
||||
else:
|
||||
BLOCK_M = 1
|
||||
|
||||
# Call the triton kernel
|
||||
_rms_norm_fwd_fused[(triton.cdiv(M, BLOCK_M),)]( #
|
||||
_rms_norm_fwd_fused[(triton.cdiv(M, BLOCK_M), )]( #
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
@@ -201,6 +202,7 @@ def rmsnorm(x, w, eps):
|
||||
|
||||
return y, rstd
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _layer_norm_param_fwd_fused(
|
||||
X, # pointer to the input
|
||||
@@ -245,7 +247,7 @@ def _layer_norm_param_fwd_fused(
|
||||
|
||||
w = tl.load(W + cols)
|
||||
b = tl.load(B + cols)
|
||||
|
||||
|
||||
x_hat = x_hat * w + b
|
||||
|
||||
# Write output
|
||||
@@ -260,20 +262,20 @@ def layernorm_param(x, w, b, eps):
|
||||
# allocate output
|
||||
M, N = x.shape
|
||||
y = torch.empty_like(x, dtype=torch.float32)
|
||||
mean = torch.empty((M,), dtype=torch.float32, device=x.device)
|
||||
rstd = torch.empty((M,), dtype=torch.float32, device=x.device)
|
||||
mean = torch.empty((M, ), dtype=torch.float32, device=x.device)
|
||||
rstd = torch.empty((M, ), dtype=torch.float32, device=x.device)
|
||||
# heuristics for number of warps
|
||||
num_warps = 8
|
||||
|
||||
N2 = triton.next_power_of_2(N)
|
||||
|
||||
|
||||
if N <= 512:
|
||||
BLOCK_M = 32
|
||||
else:
|
||||
BLOCK_M = 1
|
||||
|
||||
# enqueue kernel
|
||||
_layer_norm_param_fwd_fused[(triton.cdiv(M, BLOCK_M),)]( #
|
||||
_layer_norm_param_fwd_fused[(triton.cdiv(M, BLOCK_M), )]( #
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
@@ -355,20 +357,20 @@ def layernorm_noparam(x, eps):
|
||||
# allocate output
|
||||
M, N = x.shape
|
||||
y = torch.empty_like(x, dtype=torch.float32)
|
||||
mean = torch.empty((M,), dtype=torch.float32, device=x.device)
|
||||
rstd = torch.empty((M,), dtype=torch.float32, device=x.device)
|
||||
mean = torch.empty((M, ), dtype=torch.float32, device=x.device)
|
||||
rstd = torch.empty((M, ), dtype=torch.float32, device=x.device)
|
||||
# heuristics for number of warps
|
||||
num_warps = 8
|
||||
|
||||
N2 = triton.next_power_of_2(N)
|
||||
|
||||
|
||||
if N <= 512:
|
||||
BLOCK_M = 32
|
||||
else:
|
||||
BLOCK_M = 1
|
||||
|
||||
# enqueue kernel
|
||||
_layer_norm_noparam_fwd_fused[(triton.cdiv(M, BLOCK_M),)]( #
|
||||
_layer_norm_noparam_fwd_fused[(triton.cdiv(M, BLOCK_M), )]( #
|
||||
x,
|
||||
y,
|
||||
mean,
|
||||
@@ -388,6 +390,7 @@ def layernorm_noparam(x, eps):
|
||||
|
||||
return y, mean, rstd
|
||||
|
||||
|
||||
def layernorm(x, w, b, eps, elementwise_affine=True):
|
||||
if elementwise_affine:
|
||||
assert w is not None and b is not None
|
||||
@@ -396,9 +399,11 @@ def layernorm(x, w, b, eps, elementwise_affine=True):
|
||||
assert w is None and b is None
|
||||
return layernorm_noparam(x, eps)
|
||||
|
||||
|
||||
def cdiv(a: int, b: int):
|
||||
return (a + b - 1) // b
|
||||
|
||||
|
||||
def dequantize_weight(w_q, w_s, dtype):
|
||||
# w_q: (N, K) int8
|
||||
# w_s: (NB, KB) float32
|
||||
@@ -412,12 +417,14 @@ def dequantize_weight(w_q, w_s, dtype):
|
||||
w_s_exp = w_s_exp[:N, :K]
|
||||
return w_q.to(dtype) * w_s_exp.to(dtype)
|
||||
|
||||
|
||||
class Int8LinearFunction(torch.autograd.Function):
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, x, w_q, w_s, bias=None):
|
||||
ctx.save_for_backward(x, w_q, w_s, bias)
|
||||
ctx.bias_requires_grad = bias.requires_grad if bias is not None else False
|
||||
|
||||
|
||||
out = int8_linear(x, w_q, w_s)
|
||||
if bias is not None:
|
||||
out = out + bias
|
||||
@@ -426,24 +433,26 @@ class Int8LinearFunction(torch.autograd.Function):
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
x, w_q, w_s, bias = ctx.saved_tensors
|
||||
|
||||
|
||||
grad_input = None
|
||||
grad_weight = None
|
||||
grad_weight = None
|
||||
grad_scale = None
|
||||
grad_bias = None
|
||||
|
||||
if ctx.needs_input_grad[0]:
|
||||
|
||||
if ctx.needs_input_grad[0]:
|
||||
# grad_input = grad_output @ W
|
||||
w_float = dequantize_weight(w_q, w_s, grad_output.dtype)
|
||||
grad_input = torch.matmul(grad_output, w_float)
|
||||
|
||||
|
||||
if ctx.bias_requires_grad:
|
||||
dim_to_sum = list(range(grad_output.dim() - 1))
|
||||
grad_bias = grad_output.sum(dim=dim_to_sum)
|
||||
|
||||
|
||||
return grad_input, grad_weight, grad_scale, grad_bias
|
||||
|
||||
|
||||
class RMSNormFunction(torch.autograd.Function):
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, x, w, eps):
|
||||
y, rstd = rmsnorm(x, w, eps)
|
||||
@@ -459,14 +468,14 @@ class RMSNormFunction(torch.autograd.Function):
|
||||
x, w, rstd = ctx.saved_tensors
|
||||
eps = ctx.eps
|
||||
N = x.shape[-1]
|
||||
|
||||
|
||||
# dL/dy * w
|
||||
dx = grad_output * w
|
||||
|
||||
|
||||
# Expand rstd
|
||||
rstd = rstd.unsqueeze(-1) # (B, 1) or (M, 1)
|
||||
rstd = rstd.unsqueeze(-1) # (B, 1) or (M, 1)
|
||||
if x.dim() == 3:
|
||||
# Flatten if necessary or handle dimensions.
|
||||
# Flatten if necessary or handle dimensions.
|
||||
# forward flattens x to (M, N) internally but saved x might be 3D?
|
||||
# rmsnorm takes x, flattens it. But does it modify x in place? No.
|
||||
# But ctx.save_for_backward saves the original x (3D if input was 3D).
|
||||
@@ -479,36 +488,36 @@ class RMSNormFunction(torch.autograd.Function):
|
||||
x_flat = x
|
||||
grad_output_flat = grad_output
|
||||
dx = dx
|
||||
|
||||
|
||||
# Standard RMSNorm backward
|
||||
# dy = grad_output_flat
|
||||
# x_hat = x * rstd
|
||||
# w * dy
|
||||
# c1 = mean(dy * w * x) * rstd^2
|
||||
# dx = (dy * w - c1 * x) * rstd
|
||||
|
||||
|
||||
# More precise:
|
||||
# y = x * rstd * w
|
||||
# dy = grad_output
|
||||
# dw = sum(dy * x * rstd, dim=0)
|
||||
|
||||
|
||||
grad_w = None
|
||||
if ctx.needs_input_grad[1]: # w
|
||||
# grad_w = (grad_output_flat * x_flat * rstd).sum(dim=0)
|
||||
# More accurate gradient for weight when considering rstd was computed from x
|
||||
# Actually, for Affine part (w), it is just sum(dL/dy * x_hat).
|
||||
# y = x_hat * w
|
||||
# dL/dw = sum(dL/dy * x_hat)
|
||||
x_hat = x_flat * rstd
|
||||
grad_w = (grad_output_flat * x_hat).sum(dim=0)
|
||||
if ctx.needs_input_grad[1]: # w
|
||||
# grad_w = (grad_output_flat * x_flat * rstd).sum(dim=0)
|
||||
# More accurate gradient for weight when considering rstd was computed from x
|
||||
# Actually, for Affine part (w), it is just sum(dL/dy * x_hat).
|
||||
# y = x_hat * w
|
||||
# dL/dw = sum(dL/dy * x_hat)
|
||||
x_hat = x_flat * rstd
|
||||
grad_w = (grad_output_flat * x_hat).sum(dim=0)
|
||||
|
||||
grad_input = None
|
||||
if ctx.needs_input_grad[0]: # x
|
||||
if ctx.needs_input_grad[0]: # x
|
||||
# x_hat = x * rstd
|
||||
# dx = rstd * (w * dy - mean(w * dy * x_hat) * x_hat)
|
||||
# dx = rstd * (w * dy - mean(w * dy * x_hat) * x_hat)
|
||||
# but for RMSNorm:
|
||||
# dx = rstd * (w * dy - (x * rstd^2) * mean(w * dy * x)) -> check formula
|
||||
|
||||
|
||||
# Using PyTorch autograd for reference logic:
|
||||
# y = x / sqrt(mean(x^2) + eps) * w
|
||||
# Let sigma = sqrt(...)
|
||||
@@ -518,12 +527,12 @@ class RMSNormFunction(torch.autograd.Function):
|
||||
# dL/dx = (dL/dy * w)/sigma - sum(dL/dy * w * x) * x / (N * sigma^3)
|
||||
# = (1/sigma) * [ (dL/dy * w) - x * sum(dL/dy * w * x) / (N * sigma^2) ]
|
||||
# = rstd * [ (dL/dy * w) - x * rstd^2 * mean(dL/dy * w * x) ] # Wait, sum/N is mean
|
||||
|
||||
|
||||
# Let's compute:
|
||||
dy_w = grad_output_flat * w
|
||||
# term2 = (dy_w * x_flat).sum(dim=-1, keepdim=True) / N # mean(dy*w*x)
|
||||
# grad_input = rstd * (dy_w - x_flat * (rstd ** 2) * term2)
|
||||
|
||||
|
||||
# Re-derivation:
|
||||
# x_hat = x * rstd
|
||||
# y = x_hat * w
|
||||
@@ -531,20 +540,22 @@ class RMSNormFunction(torch.autograd.Function):
|
||||
# dy/dx = w * dx_hat/dx
|
||||
# dx_hat/dx = rstd * (I - x * x^T * rstd^2 / N) ?? No.
|
||||
# dx_hat_i / dx_j = rstd * (delta_ij - x_i * x_j * rstd^2 / N)
|
||||
|
||||
|
||||
# dL/dx_hat = dL/dy * w
|
||||
# dL/dx = rstd * (dL/dx_hat - x * rstd^2 * mean(dL/dx_hat * x))
|
||||
|
||||
|
||||
dx_hat = grad_output_flat * w
|
||||
dx_hat_x_mean = (dx_hat * x_flat).mean(dim=-1, keepdim=True)
|
||||
grad_input = rstd * (dx_hat - x_flat * (rstd ** 2) * dx_hat_x_mean)
|
||||
|
||||
grad_input = rstd * (dx_hat - x_flat * (rstd**2) * dx_hat_x_mean)
|
||||
|
||||
if x.dim() == 3:
|
||||
grad_input = grad_input.reshape(x.shape)
|
||||
|
||||
|
||||
return grad_input, grad_w, None
|
||||
|
||||
|
||||
class LayerNormFunction(torch.autograd.Function):
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, x, w, b, eps, elementwise_affine):
|
||||
if elementwise_affine:
|
||||
@@ -553,7 +564,7 @@ class LayerNormFunction(torch.autograd.Function):
|
||||
else:
|
||||
y, mean, rstd = layernorm_noparam(x, eps)
|
||||
ctx.save_for_backward(x, None, None, mean, rstd)
|
||||
|
||||
|
||||
ctx.eps = eps
|
||||
ctx.elementwise_affine = elementwise_affine
|
||||
return y
|
||||
@@ -562,60 +573,62 @@ class LayerNormFunction(torch.autograd.Function):
|
||||
def backward(ctx, grad_output):
|
||||
x, w, b, mean, rstd = ctx.saved_tensors
|
||||
N = x.shape[-1]
|
||||
|
||||
|
||||
if x.dim() == 3:
|
||||
x_flat = x.reshape(-1, N)
|
||||
grad_output_flat = grad_output.reshape(-1, N)
|
||||
else:
|
||||
x_flat = x
|
||||
grad_output_flat = grad_output
|
||||
|
||||
|
||||
if w is None:
|
||||
w_eff = 1.0
|
||||
w_eff = 1.0
|
||||
else:
|
||||
w_eff = w
|
||||
|
||||
w_eff = w
|
||||
|
||||
# dx calculation
|
||||
# x_hat = (x - mean) * rstd
|
||||
# y = x_hat * w + b
|
||||
# dL/dx_hat = dL/dy * w
|
||||
|
||||
|
||||
dy = grad_output_flat
|
||||
dx_hat = dy * w_eff
|
||||
|
||||
|
||||
# dL/dvar = sum(dL/dx_hat * (x-mean) * (-0.5) * (var+eps)^(-1.5))
|
||||
# = -0.5 * rstd^3 * sum(dx_hat * (x-mean))
|
||||
# dL/dmean = sum(dL/dx_hat * (-rstd)) + dL/dvar * (-2/N) * sum(x-mean)
|
||||
# term sum(x-mean) is 0. So second part vanishes.
|
||||
# dL/dmean = -rstd * sum(dx_hat)
|
||||
|
||||
|
||||
# dL/dx = dL/dx_hat * rstd + dL/dvar * 2(x-mean)/N + dL/dmean * 1/N
|
||||
# = dx_hat * rstd + (-0.5 * rstd^3 * sum(dx_hat * (x-mean))) * 2(x-mean)/N + (-rstd * sum(dx_hat))/N
|
||||
# = rstd * [ dx_hat - mean(dx_hat) - (x-mean)*rstd^2 * mean(dx_hat * (x-mean)) ]
|
||||
|
||||
|
||||
x_centered = x_flat - mean.unsqueeze(-1)
|
||||
dx_hat_mean = dx_hat.mean(dim=-1, keepdim=True)
|
||||
dx_hat_x_centered_mean = (dx_hat * x_centered).mean(dim=-1, keepdim=True)
|
||||
|
||||
grad_input = rstd.unsqueeze(-1) * (dx_hat - dx_hat_mean - x_centered * (rstd.unsqueeze(-1)**2) * dx_hat_x_centered_mean)
|
||||
|
||||
|
||||
grad_input = rstd.unsqueeze(-1) * (dx_hat - dx_hat_mean - x_centered *
|
||||
(rstd.unsqueeze(-1)**2) * dx_hat_x_centered_mean)
|
||||
|
||||
if x.dim() == 3:
|
||||
grad_input = grad_input.reshape(x.shape)
|
||||
|
||||
grad_w = None
|
||||
grad_b = None
|
||||
|
||||
|
||||
if ctx.elementwise_affine:
|
||||
if ctx.needs_input_grad[1]:
|
||||
x_hat = x_centered * rstd.unsqueeze(-1)
|
||||
grad_w = (dy * x_hat).sum(dim=0)
|
||||
x_hat = x_centered * rstd.unsqueeze(-1)
|
||||
grad_w = (dy * x_hat).sum(dim=0)
|
||||
if ctx.needs_input_grad[2]:
|
||||
grad_b = dy.sum(dim=0)
|
||||
|
||||
grad_b = dy.sum(dim=0)
|
||||
|
||||
return grad_input, grad_w, grad_b, None, None
|
||||
|
||||
|
||||
class Int8Linear(nn.Module):
|
||||
|
||||
def __init__(self, in_features, out_features, bias=True, dtype=torch.bfloat16):
|
||||
super().__init__()
|
||||
self.in_features = in_features
|
||||
@@ -623,27 +636,24 @@ class Int8Linear(nn.Module):
|
||||
|
||||
row_blocks = cdiv(out_features, b=128)
|
||||
col_blocks = cdiv(in_features, b=128)
|
||||
|
||||
|
||||
self.register_buffer("int8_weight", torch.empty((out_features, in_features), dtype=torch.int8))
|
||||
self.register_buffer("scale", torch.empty((row_blocks, col_blocks), dtype=torch.float32))
|
||||
if bias:
|
||||
self.register_buffer("bias", torch.empty(out_features, dtype=dtype))
|
||||
else:
|
||||
self.bias = None
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
return Int8LinearFunction.apply(x, self.int8_weight, self.scale, self.bias)
|
||||
|
||||
@classmethod
|
||||
def from_linear(cls, original_linear: nn.Linear, quantize: bool = True):
|
||||
|
||||
int8_layer = cls(
|
||||
original_linear.in_features,
|
||||
original_linear.out_features,
|
||||
bias=original_linear.bias is not None,
|
||||
dtype=original_linear.weight.dtype
|
||||
)
|
||||
|
||||
int8_layer = cls(original_linear.in_features,
|
||||
original_linear.out_features,
|
||||
bias=original_linear.bias is not None,
|
||||
dtype=original_linear.weight.dtype)
|
||||
if quantize:
|
||||
w_data = original_linear.weight.data.cuda()
|
||||
int8_w, scale = int8_quant(w_data)
|
||||
@@ -652,10 +662,12 @@ class Int8Linear(nn.Module):
|
||||
int8_layer.scale.copy_(scale)
|
||||
if original_linear.bias is not None:
|
||||
int8_layer.bias.data.copy_(original_linear.bias.data.cuda())
|
||||
|
||||
|
||||
return int8_layer
|
||||
|
||||
|
||||
|
||||
class FastRMSNorm(nn.Module):
|
||||
|
||||
def __init__(self, dim: int, eps: float = 1e-5):
|
||||
super().__init__()
|
||||
self.dim = dim
|
||||
@@ -667,23 +679,15 @@ class FastRMSNorm(nn.Module):
|
||||
|
||||
@classmethod
|
||||
def from_rmsnorm(cls, original_rmsnorm):
|
||||
rmsnorm_layer = cls(
|
||||
dim=original_rmsnorm.dim,
|
||||
eps=original_rmsnorm.eps
|
||||
)
|
||||
rmsnorm_layer = cls(dim=original_rmsnorm.dim, eps=original_rmsnorm.eps)
|
||||
if original_rmsnorm.weight.device != torch.device('meta'):
|
||||
rmsnorm_layer.weight.data.copy_(original_rmsnorm.weight.float().data)
|
||||
return rmsnorm_layer
|
||||
|
||||
|
||||
|
||||
class FastLayerNorm(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
eps: float = 1e-5,
|
||||
elementwise_affine: bool = False,
|
||||
bias: bool = True
|
||||
) :
|
||||
def __init__(self, dim: int, eps: float = 1e-5, elementwise_affine: bool = False, bias: bool = True):
|
||||
super().__init__()
|
||||
self.dim = dim # type: ignore[arg-type]
|
||||
self.eps = eps
|
||||
@@ -700,15 +704,13 @@ class FastLayerNorm(nn.Module):
|
||||
|
||||
def forward(self, x):
|
||||
return LayerNormFunction.apply(x.float(), self.weight, self.bias, self.eps, self.elementwise_affine).to(x.dtype)
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_layernorm(cls, original_layernorm):
|
||||
layernorm_layer = cls(
|
||||
dim=original_layernorm.normalized_shape[0],
|
||||
eps=original_layernorm.eps,
|
||||
elementwise_affine=False if original_layernorm.weight is None else True,
|
||||
bias=original_layernorm.bias is not None
|
||||
)
|
||||
layernorm_layer = cls(dim=original_layernorm.normalized_shape[0],
|
||||
eps=original_layernorm.eps,
|
||||
elementwise_affine=False if original_layernorm.weight is None else True,
|
||||
bias=original_layernorm.bias is not None)
|
||||
if original_layernorm.weight is not None and original_layernorm.weight.device != torch.device('meta'):
|
||||
layernorm_layer.weight.data.copy_(original_layernorm.weight.data)
|
||||
if original_layernorm.bias is not None and original_layernorm.bias.device != torch.device('meta'):
|
||||
|
||||
@@ -10,9 +10,7 @@ try:
|
||||
except ImportError:
|
||||
|
||||
def _unsupported(*args, **kwargs):
|
||||
raise ImportError(
|
||||
"flash-attn is not installed. Please install it, e.g., `uv pip install flash-attn`."
|
||||
)
|
||||
raise ImportError("flash-attn is not installed. Please install it, e.g., `uv pip install flash-attn`.")
|
||||
|
||||
_flash_attn_varlen_forward = _unsupported
|
||||
_flash_attn_varlen_backward = _unsupported
|
||||
@@ -39,25 +37,17 @@ def calc_chunks(cu_seqlen, moba_chunk_size):
|
||||
)
|
||||
cu_num_chunk[1:] = batch_num_chunk.cumsum(dim=0)
|
||||
num_chunk = cu_num_chunk[-1]
|
||||
chunk_sizes = torch.full((num_chunk + 1, ),
|
||||
moba_chunk_size,
|
||||
dtype=torch.int32,
|
||||
device=cu_seqlen.device)
|
||||
chunk_sizes = torch.full((num_chunk + 1, ), moba_chunk_size, dtype=torch.int32, device=cu_seqlen.device)
|
||||
chunk_sizes[0] = 0
|
||||
batch_last_chunk_size = batch_sizes - (batch_num_chunk -
|
||||
1) * moba_chunk_size
|
||||
batch_last_chunk_size = batch_sizes - (batch_num_chunk - 1) * moba_chunk_size
|
||||
chunk_sizes[cu_num_chunk[1:]] = batch_last_chunk_size
|
||||
cu_chunk = chunk_sizes.cumsum(dim=-1, dtype=torch.int32)
|
||||
chunk_to_batch = torch.zeros((num_chunk, ),
|
||||
dtype=torch.int32,
|
||||
device=cu_seqlen.device)
|
||||
chunk_to_batch = torch.zeros((num_chunk, ), dtype=torch.int32, device=cu_seqlen.device)
|
||||
chunk_to_batch[cu_num_chunk[1:-1]] = 1
|
||||
chunk_to_batch = chunk_to_batch.cumsum(dim=0, dtype=torch.int32)
|
||||
|
||||
# Do not filter out any chunk
|
||||
filtered_chunk_indices = torch.arange(num_chunk,
|
||||
device=cu_seqlen.device,
|
||||
dtype=torch.int32)
|
||||
filtered_chunk_indices = torch.arange(num_chunk, device=cu_seqlen.device, dtype=torch.int32)
|
||||
num_filtered_chunk = num_chunk
|
||||
|
||||
return cu_chunk, filtered_chunk_indices, num_filtered_chunk, chunk_to_batch
|
||||
@@ -66,9 +56,7 @@ def calc_chunks(cu_seqlen, moba_chunk_size):
|
||||
# --- Threshold Selection Helper Functions ---
|
||||
|
||||
|
||||
def _select_threshold_query_head(gate: torch.Tensor,
|
||||
valid_gate_mask: torch.Tensor,
|
||||
gate_self_chunk_mask: torch.Tensor,
|
||||
def _select_threshold_query_head(gate: torch.Tensor, valid_gate_mask: torch.Tensor, gate_self_chunk_mask: torch.Tensor,
|
||||
simsum_threshold: float) -> torch.Tensor:
|
||||
"""
|
||||
Selects chunks for each <query, head> pair based on threshold.
|
||||
@@ -78,16 +66,13 @@ def _select_threshold_query_head(gate: torch.Tensor,
|
||||
eps = 1e-6
|
||||
|
||||
# LSE‐style normalization per <head, query> (across chunks)
|
||||
gate_masked = torch.where(valid_gate_mask, gate,
|
||||
-torch.inf) # Use -inf for max
|
||||
gate_min_val = torch.where(valid_gate_mask, gate,
|
||||
torch.inf) # Use +inf for min
|
||||
gate_masked = torch.where(valid_gate_mask, gate, -torch.inf) # Use -inf for max
|
||||
gate_min_val = torch.where(valid_gate_mask, gate, torch.inf) # Use +inf for min
|
||||
|
||||
row_min = gate_min_val.amin(dim=0) # (H, S)
|
||||
row_max = gate_masked.amax(dim=0) # (H, S)
|
||||
denom = row_max - row_min
|
||||
denom = torch.where(denom <= eps, torch.ones_like(denom),
|
||||
denom) # avoid divide‑by‑zero
|
||||
denom = torch.where(denom <= eps, torch.ones_like(denom), denom) # avoid divide‑by‑zero
|
||||
|
||||
gate_norm = (gate - row_min.unsqueeze(0)) / denom.unsqueeze(0)
|
||||
gate_norm = torch.where(valid_gate_mask, gate_norm, 0.0) # (C, H, S)
|
||||
@@ -97,18 +82,15 @@ def _select_threshold_query_head(gate: torch.Tensor,
|
||||
|
||||
# 2) compute how much more normalized weight we need beyond self
|
||||
total_norm_sum = gate_norm.sum(dim=0) # (H, S)
|
||||
remain_ratio = simsum_threshold - self_norm / (total_norm_sum + eps
|
||||
) # (H, S)
|
||||
remain_ratio = torch.clamp(remain_ratio,
|
||||
min=0.0) # if already ≥ thresh, no extra needed
|
||||
remain_ratio = simsum_threshold - self_norm / (total_norm_sum + eps) # (H, S)
|
||||
remain_ratio = torch.clamp(remain_ratio, min=0.0) # if already ≥ thresh, no extra needed
|
||||
|
||||
# 3) zero out the self‐chunk in a copy, so we only sort “others”
|
||||
others_norm = gate_norm.clone()
|
||||
others_norm[gate_self_chunk_mask] = 0.0
|
||||
|
||||
# 4) sort the other chunks by descending norm, per <head,seq>
|
||||
sorted_norm, sorted_idx = torch.sort(others_norm, descending=True,
|
||||
dim=0) # (C, H, S)
|
||||
sorted_norm, sorted_idx = torch.sort(others_norm, descending=True, dim=0) # (C, H, S)
|
||||
|
||||
# 5) cumulative‑sum the sorted norms per <head,seq>
|
||||
cumsum_others = sorted_norm.cumsum(dim=0) # (C, H, S)
|
||||
@@ -118,9 +100,7 @@ def _select_threshold_query_head(gate: torch.Tensor,
|
||||
cond = ratio >= remain_ratio.unsqueeze(0) # (C, H, S) boolean mask
|
||||
any_cond = cond.any(dim=0) # (H, S)
|
||||
# Find the index of the first True value along dim 0. If none, use C-1.
|
||||
cutoff = torch.where(any_cond,
|
||||
cond.float().argmax(dim=0),
|
||||
torch.full_like(any_cond, fill_value=C - 1)) # (H, S)
|
||||
cutoff = torch.where(any_cond, cond.float().argmax(dim=0), torch.full_like(any_cond, fill_value=C - 1)) # (H, S)
|
||||
|
||||
# 7) build a mask in sorted order up to that cutoff
|
||||
idx_range = torch.arange(C, device=gate.device).view(-1, 1, 1) # (C, 1, 1)
|
||||
@@ -136,8 +116,7 @@ def _select_threshold_query_head(gate: torch.Tensor,
|
||||
return final_gate_mask
|
||||
|
||||
|
||||
def _select_threshold_block(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
gate_self_chunk_mask: torch.Tensor,
|
||||
def _select_threshold_block(gate: torch.Tensor, valid_gate_mask: torch.Tensor, gate_self_chunk_mask: torch.Tensor,
|
||||
simsum_threshold: float) -> torch.Tensor:
|
||||
"""
|
||||
Selects <query, head> pairs for each block based on threshold.
|
||||
@@ -148,16 +127,13 @@ def _select_threshold_block(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
eps = 1e-6
|
||||
|
||||
# LSE‐style normalization per block (across heads and queries)
|
||||
gate_masked = torch.where(valid_gate_mask, gate,
|
||||
-torch.inf) # Use -inf for max
|
||||
gate_min_val = torch.where(valid_gate_mask, gate,
|
||||
torch.inf) # Use +inf for min
|
||||
gate_masked = torch.where(valid_gate_mask, gate, -torch.inf) # Use -inf for max
|
||||
gate_min_val = torch.where(valid_gate_mask, gate, torch.inf) # Use +inf for min
|
||||
|
||||
block_max = gate_masked.amax(dim=(1, 2), keepdim=True) # (C, 1, 1)
|
||||
block_min = gate_min_val.amin(dim=(1, 2), keepdim=True) # (C, 1, 1)
|
||||
block_denom = block_max - block_min
|
||||
block_denom = torch.where(block_denom <= eps, torch.ones_like(block_denom),
|
||||
block_denom) # (C, 1, 1)
|
||||
block_denom = torch.where(block_denom <= eps, torch.ones_like(block_denom), block_denom) # (C, 1, 1)
|
||||
|
||||
gate_norm = (gate - block_min) / block_denom # (C, H, S)
|
||||
gate_norm = torch.where(valid_gate_mask, gate_norm, 0.0) # (C, H, S)
|
||||
@@ -169,8 +145,7 @@ def _select_threshold_block(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
|
||||
# 2) compute how much more normalized weight each block needs beyond its self-chunk contributions
|
||||
total_norm_sum_per_block = gate_norm.sum(dim=(1, 2)) # (C,)
|
||||
remain_ratio = simsum_threshold - self_norm_sum_per_block / (
|
||||
total_norm_sum_per_block + eps) # (C,)
|
||||
remain_ratio = simsum_threshold - self_norm_sum_per_block / (total_norm_sum_per_block + eps) # (C,)
|
||||
remain_ratio = torch.clamp(remain_ratio, min=0.0) # (C,)
|
||||
|
||||
# 3) zero out the self‐chunk entries in a copy, so we only sort “others”
|
||||
@@ -179,31 +154,25 @@ def _select_threshold_block(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
|
||||
# 4) sort the other <head, seq> pairs by descending norm, per block
|
||||
others_flat = others_norm.contiguous().view(C, HS) # (C, H*S)
|
||||
sorted_others_flat, sorted_indices_flat = torch.sort(
|
||||
others_flat, dim=1, descending=True) # (C, H*S)
|
||||
sorted_others_flat, sorted_indices_flat = torch.sort(others_flat, dim=1, descending=True) # (C, H*S)
|
||||
|
||||
# 5) cumulative‑sum the sorted norms per block
|
||||
cumsum_others_flat = sorted_others_flat.cumsum(dim=1) # (C, H*S)
|
||||
|
||||
# 6) for each block, find the smallest k where cumsum_ratio ≥ remain_ratio
|
||||
ratio_flat = cumsum_others_flat / (
|
||||
total_norm_sum_per_block.unsqueeze(1) + eps) # (C, H*S)
|
||||
ratio_flat = cumsum_others_flat / (total_norm_sum_per_block.unsqueeze(1) + eps) # (C, H*S)
|
||||
cond_flat = ratio_flat >= remain_ratio.unsqueeze(1) # (C, H*S) boolean mask
|
||||
any_cond = cond_flat.any(dim=1) # (C,)
|
||||
# Find the index of the first True value along dim 1. If none, use HS-1.
|
||||
cutoff_flat = torch.where(any_cond,
|
||||
cond_flat.float().argmax(dim=1),
|
||||
torch.full_like(any_cond,
|
||||
fill_value=HS - 1)) # (C,)
|
||||
cond_flat.float().argmax(dim=1), torch.full_like(any_cond, fill_value=HS - 1)) # (C,)
|
||||
|
||||
# 7) build a mask in sorted order up to that cutoff per block
|
||||
idx_range_flat = torch.arange(HS,
|
||||
device=gate.device).unsqueeze(0) # (1, H*S)
|
||||
idx_range_flat = torch.arange(HS, device=gate.device).unsqueeze(0) # (1, H*S)
|
||||
sorted_mask_flat = idx_range_flat <= cutoff_flat.unsqueeze(1) # (C, H*S)
|
||||
|
||||
# 8) scatter it back to original <head, seq> order per block
|
||||
others_mask_flat = torch.zeros_like(others_flat,
|
||||
dtype=torch.bool) # (C, H*S)
|
||||
others_mask_flat = torch.zeros_like(others_flat, dtype=torch.bool) # (C, H*S)
|
||||
others_mask_flat.scatter_(1, sorted_indices_flat, sorted_mask_flat)
|
||||
others_mask = others_mask_flat.view(C, H, S) # (C, H, S)
|
||||
|
||||
@@ -213,8 +182,7 @@ def _select_threshold_block(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
return final_gate_mask
|
||||
|
||||
|
||||
def _select_threshold_overall(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
gate_self_chunk_mask: torch.Tensor,
|
||||
def _select_threshold_overall(gate: torch.Tensor, valid_gate_mask: torch.Tensor, gate_self_chunk_mask: torch.Tensor,
|
||||
simsum_threshold: float) -> torch.Tensor:
|
||||
"""
|
||||
Selects <chunk, query, head> triplets globally based on threshold.
|
||||
@@ -225,17 +193,14 @@ def _select_threshold_overall(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
eps = 1e-6
|
||||
|
||||
# LSE‐style normalization globally across all valid entries
|
||||
gate_masked = torch.where(valid_gate_mask, gate,
|
||||
-torch.inf) # Use -inf for max
|
||||
gate_min_val = torch.where(valid_gate_mask, gate,
|
||||
torch.inf) # Use +inf for min
|
||||
gate_masked = torch.where(valid_gate_mask, gate, -torch.inf) # Use -inf for max
|
||||
gate_min_val = torch.where(valid_gate_mask, gate, torch.inf) # Use +inf for min
|
||||
|
||||
overall_max = gate_masked.max() # scalar
|
||||
overall_min = gate_min_val.min() # scalar
|
||||
overall_denom = overall_max - overall_min
|
||||
overall_denom = torch.where(
|
||||
overall_denom <= eps,
|
||||
torch.tensor(1.0, device=gate.device, dtype=gate.dtype), overall_denom)
|
||||
overall_denom = torch.where(overall_denom <= eps, torch.tensor(1.0, device=gate.device, dtype=gate.dtype),
|
||||
overall_denom)
|
||||
|
||||
gate_norm = (gate - overall_min) / overall_denom # (C, H, S)
|
||||
gate_norm = torch.where(valid_gate_mask, gate_norm, 0.0) # (C, H, S)
|
||||
@@ -247,8 +212,7 @@ def _select_threshold_overall(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
|
||||
# 2) compute how much more normalized weight is needed globally beyond self-chunk contributions
|
||||
total_norm_sum_overall = gate_norm.sum() # scalar
|
||||
remain_ratio = simsum_threshold - self_norm_sum_overall / (
|
||||
total_norm_sum_overall + eps) # scalar
|
||||
remain_ratio = simsum_threshold - self_norm_sum_overall / (total_norm_sum_overall + eps) # scalar
|
||||
remain_ratio = torch.clamp(remain_ratio, min=0.0) # scalar
|
||||
|
||||
# 3) zero out the self‐chunk entries in a copy, so we only sort “others”
|
||||
@@ -264,18 +228,14 @@ def _select_threshold_overall(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
valid_others_indices = torch.where(valid_others_mask_flat)[0]
|
||||
valid_others_values = others_flat[valid_others_indices]
|
||||
|
||||
sorted_others_values, sort_perm = torch.sort(
|
||||
valid_others_values, descending=True) # (N_valid_others,)
|
||||
sorted_original_indices = valid_others_indices[
|
||||
sort_perm] # Original indices in C*H*S space, sorted by value
|
||||
sorted_others_values, sort_perm = torch.sort(valid_others_values, descending=True) # (N_valid_others,)
|
||||
sorted_original_indices = valid_others_indices[sort_perm] # Original indices in C*H*S space, sorted by value
|
||||
|
||||
# 5) cumulative‑sum the sorted valid 'other' norms globally
|
||||
cumsum_others_values = sorted_others_values.cumsum(
|
||||
dim=0) # (N_valid_others,)
|
||||
cumsum_others_values = sorted_others_values.cumsum(dim=0) # (N_valid_others,)
|
||||
|
||||
# 6) find the smallest k where cumsum_ratio ≥ remain_ratio globally
|
||||
ratio_values = cumsum_others_values / (total_norm_sum_overall + eps
|
||||
) # (N_valid_others,)
|
||||
ratio_values = cumsum_others_values / (total_norm_sum_overall + eps) # (N_valid_others,)
|
||||
cond_values = ratio_values >= remain_ratio # (N_valid_others,) boolean mask
|
||||
any_cond = cond_values.any() # scalar
|
||||
|
||||
@@ -283,19 +243,15 @@ def _select_threshold_overall(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
cutoff_idx_in_sorted = torch.where(
|
||||
any_cond,
|
||||
cond_values.float().argmax(dim=0),
|
||||
torch.tensor(len(sorted_others_values) - 1,
|
||||
device=gate.device,
|
||||
dtype=torch.long))
|
||||
torch.tensor(len(sorted_others_values) - 1, device=gate.device, dtype=torch.long))
|
||||
|
||||
# 7) build a mask selecting the top-k others based on the cutoff
|
||||
# Select the original indices corresponding to the top entries in the sorted list
|
||||
selected_other_indices = sorted_original_indices[:cutoff_idx_in_sorted + 1]
|
||||
|
||||
# 8) create the mask in the original flat shape
|
||||
others_mask_flat = torch.zeros_like(others_flat,
|
||||
dtype=torch.bool) # (C*H*S,)
|
||||
if selected_other_indices.numel(
|
||||
) > 0: # Check if any 'other' indices were selected
|
||||
others_mask_flat = torch.zeros_like(others_flat, dtype=torch.bool) # (C*H*S,)
|
||||
if selected_other_indices.numel() > 0: # Check if any 'other' indices were selected
|
||||
others_mask_flat[selected_other_indices] = True
|
||||
others_mask = others_mask_flat.view(C, H, S) # (C, H, S)
|
||||
|
||||
@@ -305,9 +261,7 @@ def _select_threshold_overall(gate: torch.Tensor, valid_gate_mask: torch.Tensor,
|
||||
return final_gate_mask
|
||||
|
||||
|
||||
def _select_threshold_head_global(gate: torch.Tensor,
|
||||
valid_gate_mask: torch.Tensor,
|
||||
gate_self_chunk_mask: torch.Tensor,
|
||||
def _select_threshold_head_global(gate: torch.Tensor, valid_gate_mask: torch.Tensor, gate_self_chunk_mask: torch.Tensor,
|
||||
simsum_threshold: float) -> torch.Tensor:
|
||||
"""
|
||||
Selects <chunk, query> globally for each head based on threshold.
|
||||
@@ -334,8 +288,7 @@ def _select_threshold_head_global(gate: torch.Tensor,
|
||||
total_norm_sum = gate_norm.sum(dim=(0, 2)) # (H,)
|
||||
|
||||
# 4) how much more normalized weight needed per head
|
||||
remain_ratio = simsum_threshold - self_norm_sum / (total_norm_sum + eps
|
||||
) # (H,)
|
||||
remain_ratio = simsum_threshold - self_norm_sum / (total_norm_sum + eps) # (H,)
|
||||
remain_ratio = torch.clamp(remain_ratio, min=0.0)
|
||||
|
||||
# 5) zero out self‐chunk entries to focus on "others"
|
||||
@@ -349,10 +302,8 @@ def _select_threshold_head_global(gate: torch.Tensor,
|
||||
.permute(1, 0, 2).reshape(H, CS) # (H, C*S)
|
||||
|
||||
# 7) vectorized selection of “others” per head
|
||||
masked_flat = torch.where(valid_flat, others_flat,
|
||||
torch.zeros_like(others_flat))
|
||||
sorted_vals, sorted_idx = torch.sort(masked_flat, dim=1,
|
||||
descending=True) # (H, C*S)
|
||||
masked_flat = torch.where(valid_flat, others_flat, torch.zeros_like(others_flat))
|
||||
sorted_vals, sorted_idx = torch.sort(masked_flat, dim=1, descending=True) # (H, C*S)
|
||||
|
||||
cumsum_vals = sorted_vals.cumsum(dim=1) # (H, C*S)
|
||||
ratio_vals = cumsum_vals / (total_norm_sum.unsqueeze(1) + eps) # (H, C*S)
|
||||
@@ -360,8 +311,7 @@ def _select_threshold_head_global(gate: torch.Tensor,
|
||||
|
||||
has_cutoff = cond.any(dim=1) # (H,)
|
||||
default = torch.full((H, ), CS - 1, device=gate.device, dtype=torch.long)
|
||||
cutoff = torch.where(has_cutoff,
|
||||
cond.float().argmax(dim=1), default) # (H,)
|
||||
cutoff = torch.where(has_cutoff, cond.float().argmax(dim=1), default) # (H,)
|
||||
|
||||
idx_range = torch.arange(CS, device=gate.device).unsqueeze(0) # (1, C*S)
|
||||
sorted_mask = idx_range <= cutoff.unsqueeze(1) # (H, C*S)
|
||||
@@ -430,25 +380,19 @@ class MixedAttention(torch.autograd.Function):
|
||||
self_attn_lse_sh = self_attn_lse_hs.t().contiguous()
|
||||
moba_attn_lse = moba_attn_lse_hs.t().contiguous()
|
||||
|
||||
output = torch.zeros((q.shape[0], q.shape[1], q.shape[2]),
|
||||
device=q.device,
|
||||
dtype=torch.float32)
|
||||
output = torch.zeros((q.shape[0], q.shape[1], q.shape[2]), device=q.device, dtype=torch.float32)
|
||||
output_2d = output.view(-1, q.shape[2])
|
||||
|
||||
max_lse_1d = self_attn_lse_sh.view(-1)
|
||||
max_lse_1d = max_lse_1d.index_reduce(0, moba_q_sh_indices,
|
||||
moba_attn_lse.view(-1), "amax")
|
||||
self_attn_lse_sh = self_attn_lse_sh - max_lse_1d.view_as(
|
||||
self_attn_lse_sh)
|
||||
moba_attn_lse = (moba_attn_lse.view(-1).sub(
|
||||
max_lse_1d.index_select(
|
||||
0, moba_q_sh_indices)).reshape_as(moba_attn_lse))
|
||||
max_lse_1d = max_lse_1d.index_reduce(0, moba_q_sh_indices, moba_attn_lse.view(-1), "amax")
|
||||
self_attn_lse_sh = self_attn_lse_sh - max_lse_1d.view_as(self_attn_lse_sh)
|
||||
moba_attn_lse = (moba_attn_lse.view(-1).sub(max_lse_1d.index_select(
|
||||
0, moba_q_sh_indices)).reshape_as(moba_attn_lse))
|
||||
|
||||
mixed_attn_se_sh = self_attn_lse_sh.exp()
|
||||
moba_attn_se = moba_attn_lse.exp()
|
||||
|
||||
mixed_attn_se_sh.view(-1).index_add_(0, moba_q_sh_indices,
|
||||
moba_attn_se.view(-1))
|
||||
mixed_attn_se_sh.view(-1).index_add_(0, moba_q_sh_indices, moba_attn_se.view(-1))
|
||||
mixed_attn_lse_sh = mixed_attn_se_sh.log()
|
||||
|
||||
# Combine self-attention output
|
||||
@@ -457,15 +401,13 @@ class MixedAttention(torch.autograd.Function):
|
||||
output_2d += self_attn_out_sh.reshape_as(output_2d)
|
||||
|
||||
# Combine MOBA attention output
|
||||
mixed_attn_lse = (mixed_attn_lse_sh.view(-1).index_select(
|
||||
0, moba_q_sh_indices).view_as(moba_attn_lse))
|
||||
mixed_attn_lse = (mixed_attn_lse_sh.view(-1).index_select(0, moba_q_sh_indices).view_as(moba_attn_lse))
|
||||
factor = (moba_attn_lse - mixed_attn_lse).exp() # [S, H]
|
||||
moba_attn_out = moba_attn_out * factor.unsqueeze(-1)
|
||||
raw_attn_out = moba_attn_out.view(-1, moba_attn_out.shape[-1])
|
||||
output_2d.index_add_(0, moba_q_sh_indices, raw_attn_out)
|
||||
output = output.to(q.dtype)
|
||||
mixed_attn_lse_sh = mixed_attn_lse_sh + max_lse_1d.view_as(
|
||||
mixed_attn_se_sh)
|
||||
mixed_attn_lse_sh = mixed_attn_lse_sh + max_lse_1d.view_as(mixed_attn_se_sh)
|
||||
ctx.save_for_backward(
|
||||
output,
|
||||
mixed_attn_lse_sh,
|
||||
@@ -508,37 +450,33 @@ class MixedAttention(torch.autograd.Function):
|
||||
dq = torch.empty_like(q)
|
||||
dk = torch.empty_like(k)
|
||||
dv = torch.empty_like(v)
|
||||
_ = _flash_attn_varlen_backward(
|
||||
dout=d_output,
|
||||
q=q,
|
||||
k=k,
|
||||
v=v,
|
||||
out=output,
|
||||
softmax_lse=mixed_attn_vlse_sh.t().contiguous(),
|
||||
dq=dq,
|
||||
dk=dk,
|
||||
dv=dv,
|
||||
cu_seqlens_q=self_attn_cu_seqlen,
|
||||
cu_seqlens_k=self_attn_cu_seqlen,
|
||||
max_seqlen_q=max_seqlen,
|
||||
max_seqlen_k=max_seqlen,
|
||||
softmax_scale=softmax_scale,
|
||||
causal=False,
|
||||
dropout_p=0.0,
|
||||
softcap=0.0,
|
||||
alibi_slopes=None,
|
||||
deterministic=True,
|
||||
window_size_left=-1,
|
||||
window_size_right=-1)
|
||||
_ = _flash_attn_varlen_backward(dout=d_output,
|
||||
q=q,
|
||||
k=k,
|
||||
v=v,
|
||||
out=output,
|
||||
softmax_lse=mixed_attn_vlse_sh.t().contiguous(),
|
||||
dq=dq,
|
||||
dk=dk,
|
||||
dv=dv,
|
||||
cu_seqlens_q=self_attn_cu_seqlen,
|
||||
cu_seqlens_k=self_attn_cu_seqlen,
|
||||
max_seqlen_q=max_seqlen,
|
||||
max_seqlen_k=max_seqlen,
|
||||
softmax_scale=softmax_scale,
|
||||
causal=False,
|
||||
dropout_p=0.0,
|
||||
softcap=0.0,
|
||||
alibi_slopes=None,
|
||||
deterministic=True,
|
||||
window_size_left=-1,
|
||||
window_size_right=-1)
|
||||
|
||||
headdim = q.shape[-1]
|
||||
d_moba_output = (d_output.view(-1, headdim).index_select(
|
||||
0, moba_q_sh_indices).unsqueeze(1))
|
||||
moba_output = (output.view(-1, headdim).index_select(
|
||||
0, moba_q_sh_indices).unsqueeze(1))
|
||||
d_moba_output = (d_output.view(-1, headdim).index_select(0, moba_q_sh_indices).unsqueeze(1))
|
||||
moba_output = (output.view(-1, headdim).index_select(0, moba_q_sh_indices).unsqueeze(1))
|
||||
|
||||
mixed_attn_vlse = (mixed_attn_vlse_sh.view(-1).index_select(
|
||||
0, moba_q_sh_indices).view(1, -1))
|
||||
mixed_attn_vlse = (mixed_attn_vlse_sh.view(-1).index_select(0, moba_q_sh_indices).view(1, -1))
|
||||
|
||||
dmq = torch.empty_like(moba_q)
|
||||
dmkv = torch.empty_like(moba_kv)
|
||||
@@ -595,8 +533,7 @@ def moba_attn_varlen(
|
||||
seqlen, num_head, head_dim = q.shape
|
||||
|
||||
# Compute chunk boundaries.
|
||||
cu_chunk, filtered_chunk_indices, num_filtered_chunk, chunk_to_batch = calc_chunks(
|
||||
cu_seqlens, moba_chunk_size)
|
||||
cu_chunk, filtered_chunk_indices, num_filtered_chunk, chunk_to_batch = calc_chunks(cu_seqlens, moba_chunk_size)
|
||||
|
||||
self_attn_cu_seqlen = cu_chunk
|
||||
|
||||
@@ -609,20 +546,15 @@ def moba_attn_varlen(
|
||||
chunk_lengths = chunk_ends - chunk_starts # [num_filtered_chunk]
|
||||
max_chunk_len = int(chunk_lengths.max().item())
|
||||
|
||||
range_tensor = torch.arange(max_chunk_len,
|
||||
device=kv.device,
|
||||
dtype=chunk_starts.dtype).unsqueeze(0)
|
||||
range_tensor = torch.arange(max_chunk_len, device=kv.device, dtype=chunk_starts.dtype).unsqueeze(0)
|
||||
indices = chunk_starts.unsqueeze(1) + range_tensor
|
||||
indices = torch.clamp(indices, max=kv.shape[0] - 1)
|
||||
valid_mask = range_tensor < chunk_lengths.unsqueeze(1)
|
||||
gathered = kv[indices.view(-1)].view(num_filtered_chunk, max_chunk_len,
|
||||
*kv.shape[1:])
|
||||
gathered = gathered * valid_mask.unsqueeze(-1).unsqueeze(-1).unsqueeze(
|
||||
-1).type_as(gathered)
|
||||
gathered = kv[indices.view(-1)].view(num_filtered_chunk, max_chunk_len, *kv.shape[1:])
|
||||
gathered = gathered * valid_mask.unsqueeze(-1).unsqueeze(-1).unsqueeze(-1).type_as(gathered)
|
||||
|
||||
# Compute key_gate_weight over valid tokens.
|
||||
key_values = gathered[:, :, 0].float(
|
||||
) # [num_filtered_chunk, max_chunk_len, num_head, head_dim]
|
||||
key_values = gathered[:, :, 0].float() # [num_filtered_chunk, max_chunk_len, num_head, head_dim]
|
||||
valid_mask_exp = valid_mask.unsqueeze(-1).unsqueeze(-1)
|
||||
key_sum = (key_values * valid_mask_exp).sum(dim=1)
|
||||
divisor = valid_mask.sum(dim=1).unsqueeze(-1).unsqueeze(-1)
|
||||
@@ -631,19 +563,15 @@ def moba_attn_varlen(
|
||||
# Compute gate logits between key_gate_weight and queries.
|
||||
q_float = q.float()
|
||||
# gate = torch.einsum("nhd,shd->nhs", key_gate_weight, q_float) # [num_filtered_chunk, num_head, seqlen]
|
||||
gate = torch.bmm(key_gate_weight.permute(1, 0, 2),
|
||||
q_float.permute(1, 0, 2).transpose(1, 2)).permute(1, 0, 2)
|
||||
gate = torch.bmm(key_gate_weight.permute(1, 0, 2), q_float.permute(1, 0, 2).transpose(1, 2)).permute(1, 0, 2)
|
||||
|
||||
# Amplify the diagonal (self chunk) contributions.
|
||||
gate_seq_idx = torch.arange(seqlen, device=q.device,
|
||||
dtype=torch.int32).unsqueeze(0).expand(
|
||||
num_filtered_chunk, seqlen)
|
||||
dtype=torch.int32).unsqueeze(0).expand(num_filtered_chunk, seqlen)
|
||||
chunk_start = cu_chunk[filtered_chunk_indices] # [num_filtered_chunk]
|
||||
chunk_end = cu_chunk[filtered_chunk_indices + 1] # [num_filtered_chunk]
|
||||
gate_self_chunk_mask = (
|
||||
(gate_seq_idx >= chunk_start.unsqueeze(1)) &
|
||||
(gate_seq_idx < chunk_end.unsqueeze(1))).unsqueeze(1).expand(
|
||||
-1, num_head, -1)
|
||||
gate_self_chunk_mask = ((gate_seq_idx >= chunk_start.unsqueeze(1)) &
|
||||
(gate_seq_idx < chunk_end.unsqueeze(1))).unsqueeze(1).expand(-1, num_head, -1)
|
||||
amplification_factor = 1e9 # Example factor; adjust as needed.
|
||||
origin_gate = gate.clone()
|
||||
gate = gate.clone()
|
||||
@@ -664,11 +592,7 @@ def moba_attn_varlen(
|
||||
if threshold_type == 'query_head':
|
||||
# === per‐<head,seq> top-k across chunks (original behavior) ===
|
||||
# gate: (C, H, S)
|
||||
_, gate_topk_idx = torch.topk(gate,
|
||||
k=moba_topk,
|
||||
dim=0,
|
||||
largest=True,
|
||||
sorted=False)
|
||||
_, gate_topk_idx = torch.topk(gate, k=moba_topk, dim=0, largest=True, sorted=False)
|
||||
gate_idx_mask = torch.zeros_like(gate, dtype=torch.bool)
|
||||
gate_idx_mask.scatter_(0, gate_topk_idx, True)
|
||||
gate_mask = valid_gate_mask & gate_idx_mask
|
||||
@@ -679,85 +603,52 @@ def moba_attn_varlen(
|
||||
flat_mask = valid_gate_mask.flatten()
|
||||
flat_gate_masked = torch.where(flat_mask, flat_gate, -float("inf"))
|
||||
# pick topk global entries
|
||||
vals, idx = torch.topk(flat_gate_masked,
|
||||
k=moba_topk * H * S,
|
||||
largest=True,
|
||||
sorted=False)
|
||||
vals, idx = torch.topk(flat_gate_masked, k=moba_topk * H * S, largest=True, sorted=False)
|
||||
others_mask_flat = torch.zeros_like(flat_mask, dtype=torch.bool)
|
||||
others_mask_flat[idx] = True
|
||||
gate_mask = (valid_gate_mask.flatten() & others_mask_flat).view(
|
||||
gate.shape)
|
||||
gate_mask = (valid_gate_mask.flatten() & others_mask_flat).view(gate.shape)
|
||||
elif threshold_type == 'head_global':
|
||||
# per-head top-k across all chunks and sequence positions
|
||||
C, H, S = gate.shape
|
||||
CS = C * S
|
||||
flat_gate = gate.permute(1, 0, 2).reshape(H, CS)
|
||||
flat_valid = valid_gate_mask.permute(1, 0, 2).reshape(H, CS)
|
||||
flat_gate_masked = torch.where(
|
||||
flat_valid, flat_gate, torch.full_like(flat_gate,
|
||||
-float('inf')))
|
||||
flat_gate_masked = torch.where(flat_valid, flat_gate, torch.full_like(flat_gate, -float('inf')))
|
||||
# pick top-k indices per head
|
||||
_, topk_idx = torch.topk(flat_gate_masked,
|
||||
k=moba_topk * S,
|
||||
dim=1,
|
||||
largest=True,
|
||||
sorted=False)
|
||||
_, topk_idx = torch.topk(flat_gate_masked, k=moba_topk * S, dim=1, largest=True, sorted=False)
|
||||
gate_idx_flat = torch.zeros_like(flat_valid, dtype=torch.bool)
|
||||
gate_idx_flat.scatter_(1, topk_idx, True)
|
||||
gate_mask = gate_idx_flat.reshape(H, C, S).permute(1, 0, 2)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid threshold_type for topk: {threshold_type}. "
|
||||
"Choose 'query_head', 'block', or 'overall'.")
|
||||
raise ValueError(f"Invalid threshold_type for topk: {threshold_type}. "
|
||||
"Choose 'query_head', 'block', or 'overall'.")
|
||||
elif select_mode == 'threshold':
|
||||
# Delegate to the specific thresholding function
|
||||
valid_gate_mask = gate != -float("inf") # (num_chunk, num_head, seqlen)
|
||||
if threshold_type == 'query_head':
|
||||
gate_mask = _select_threshold_query_head(gate, valid_gate_mask,
|
||||
gate_self_chunk_mask,
|
||||
simsum_threshold)
|
||||
gate_mask = _select_threshold_query_head(gate, valid_gate_mask, gate_self_chunk_mask, simsum_threshold)
|
||||
elif threshold_type == 'block':
|
||||
gate_mask = _select_threshold_block(gate, valid_gate_mask,
|
||||
gate_self_chunk_mask,
|
||||
simsum_threshold)
|
||||
gate_mask = _select_threshold_block(gate, valid_gate_mask, gate_self_chunk_mask, simsum_threshold)
|
||||
elif threshold_type == 'overall':
|
||||
gate_mask = _select_threshold_overall(gate, valid_gate_mask,
|
||||
gate_self_chunk_mask,
|
||||
simsum_threshold)
|
||||
gate_mask = _select_threshold_overall(gate, valid_gate_mask, gate_self_chunk_mask, simsum_threshold)
|
||||
elif threshold_type == 'head_global':
|
||||
gate_mask = _select_threshold_head_global(gate, valid_gate_mask,
|
||||
gate_self_chunk_mask,
|
||||
simsum_threshold)
|
||||
gate_mask = _select_threshold_head_global(gate, valid_gate_mask, gate_self_chunk_mask, simsum_threshold)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid threshold_type: {threshold_type}. Choose 'query_head', 'block', or 'overall'."
|
||||
)
|
||||
raise ValueError(f"Invalid threshold_type: {threshold_type}. Choose 'query_head', 'block', or 'overall'.")
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid select_mode: {select_mode}. Choose 'topk' or 'threshold'."
|
||||
)
|
||||
raise ValueError(f"Invalid select_mode: {select_mode}. Choose 'topk' or 'threshold'.")
|
||||
|
||||
# eliminate self_chunk in MoBA branch
|
||||
gate_mask = gate_mask & ~gate_self_chunk_mask
|
||||
# if gate_mask is all false, perform flash_attn instead
|
||||
if gate_mask.sum() == 0:
|
||||
return flash_attn_varlen_func(q,
|
||||
k,
|
||||
v,
|
||||
cu_seqlens,
|
||||
cu_seqlens,
|
||||
max_seqlen,
|
||||
max_seqlen,
|
||||
causal=False)
|
||||
return flash_attn_varlen_func(q, k, v, cu_seqlens, cu_seqlens, max_seqlen, max_seqlen, causal=False)
|
||||
|
||||
# Determine which query positions are selected.
|
||||
# nonzero_indices has shape [N, 3] where each row is [chunk_index, head_index, seq_index].
|
||||
moba_q_indices = gate_mask.reshape(
|
||||
gate_mask.shape[0], -1).nonzero(as_tuple=True)[-1] # [(h s k)]
|
||||
moba_q_sh_indices = (moba_q_indices % seqlen) * num_head + (
|
||||
moba_q_indices // seqlen)
|
||||
moba_q = rearrange(q, "s h d -> (h s) d").index_select(
|
||||
0, moba_q_indices).unsqueeze(1)
|
||||
moba_q_indices = gate_mask.reshape(gate_mask.shape[0], -1).nonzero(as_tuple=True)[-1] # [(h s k)]
|
||||
moba_q_sh_indices = (moba_q_indices % seqlen) * num_head + (moba_q_indices // seqlen)
|
||||
moba_q = rearrange(q, "s h d -> (h s) d").index_select(0, moba_q_indices).unsqueeze(1)
|
||||
|
||||
# Build cumulative sequence lengths for the selected queries.
|
||||
moba_seqlen_q = gate_mask.sum(dim=-1).flatten()
|
||||
@@ -775,23 +666,19 @@ def moba_attn_varlen(
|
||||
|
||||
# Rearrange gathered KV for the MOBA branch.
|
||||
experts_tensor = rearrange(gathered, "nc cl two h d -> (nc h) cl two d")
|
||||
valid_expert_lengths = chunk_lengths.unsqueeze(1).expand(
|
||||
num_filtered_chunk, num_head).reshape(-1).to(torch.int32)
|
||||
valid_expert_lengths = chunk_lengths.unsqueeze(1).expand(num_filtered_chunk, num_head).reshape(-1).to(torch.int32)
|
||||
if q_zero_mask.sum() > 0:
|
||||
experts_tensor = experts_tensor[valid_expert_mask]
|
||||
valid_expert_lengths = valid_expert_lengths[valid_expert_mask]
|
||||
|
||||
seq_range = torch.arange(experts_tensor.shape[1],
|
||||
device=experts_tensor.device).unsqueeze(0)
|
||||
seq_range = torch.arange(experts_tensor.shape[1], device=experts_tensor.device).unsqueeze(0)
|
||||
mask = seq_range < valid_expert_lengths.unsqueeze(1)
|
||||
moba_kv = experts_tensor[mask] # Shape: ((nc h cl_valid) two d)
|
||||
moba_kv = moba_kv.unsqueeze(2) # Shape: ((nc h cl_valid) two 1 d)
|
||||
|
||||
moba_cu_seqlen_kv = torch.cat(
|
||||
[
|
||||
torch.zeros(1, device=experts_tensor.device, dtype=torch.int32),
|
||||
valid_expert_lengths.cumsum(dim=0)
|
||||
],
|
||||
[torch.zeros(1, device=experts_tensor.device, dtype=torch.int32),
|
||||
valid_expert_lengths.cumsum(dim=0)],
|
||||
dim=0,
|
||||
).to(torch.int32)
|
||||
|
||||
@@ -831,18 +718,14 @@ def process_moba_input(
|
||||
torch.Tensor: Processed input tensor.
|
||||
"""
|
||||
if isinstance(chunk_size, float) or isinstance(chunk_size, int):
|
||||
moba_chunk_size = int(chunk_size * patch_resolution[1] *
|
||||
patch_resolution[2])
|
||||
moba_chunk_size = int(chunk_size * patch_resolution[1] * patch_resolution[2])
|
||||
else:
|
||||
assert isinstance(
|
||||
chunk_size, (tuple, list)
|
||||
), f"chunk_size should be a tuple, list, or int, now it is: {type(chunk_size)}"
|
||||
assert isinstance(chunk_size,
|
||||
(tuple, list)), f"chunk_size should be a tuple, list, or int, now it is: {type(chunk_size)}"
|
||||
if len(chunk_size) == 2:
|
||||
assert patch_resolution[1] % chunk_size[
|
||||
0] == 0 and patch_resolution[2] % chunk_size[
|
||||
1] == 0, f"spatial patch_resolution {patch_resolution[1:]} should be divisible by 2d chunk_size {chunk_size}"
|
||||
nch, ncw = patch_resolution[1] // chunk_size[0], patch_resolution[
|
||||
2] // chunk_size[1]
|
||||
assert patch_resolution[1] % chunk_size[0] == 0 and patch_resolution[2] % chunk_size[
|
||||
1] == 0, f"spatial patch_resolution {patch_resolution[1:]} should be divisible by 2d chunk_size {chunk_size}"
|
||||
nch, ncw = patch_resolution[1] // chunk_size[0], patch_resolution[2] // chunk_size[1]
|
||||
x = rearrange(x,
|
||||
"b (t nch ch ncw cw) n d -> b (nch ncw t ch cw) n d",
|
||||
t=patch_resolution[0],
|
||||
@@ -852,26 +735,22 @@ def process_moba_input(
|
||||
cw=chunk_size[1])
|
||||
moba_chunk_size = patch_resolution[0] * chunk_size[0] * chunk_size[1]
|
||||
elif len(chunk_size) == 3:
|
||||
assert patch_resolution[0] % chunk_size[0] == 0 and patch_resolution[
|
||||
1] % chunk_size[1] == 0 and patch_resolution[2] % chunk_size[
|
||||
assert patch_resolution[0] % chunk_size[0] == 0 and patch_resolution[1] % chunk_size[
|
||||
1] == 0 and patch_resolution[2] % chunk_size[
|
||||
2] == 0, f"patch_resolution {patch_resolution} should be divisible by 3d chunk_size {chunk_size}"
|
||||
nct, nch, ncw = patch_resolution[0] // chunk_size[
|
||||
0], patch_resolution[1] // chunk_size[1], patch_resolution[
|
||||
2] // chunk_size[2]
|
||||
x = rearrange(
|
||||
x,
|
||||
"b (nct ct nch ch ncw cw) n d -> b (nct nch ncw ct ch cw) n d",
|
||||
nct=nct,
|
||||
nch=nch,
|
||||
ncw=ncw,
|
||||
ct=chunk_size[0],
|
||||
ch=chunk_size[1],
|
||||
cw=chunk_size[2])
|
||||
nct, nch, ncw = patch_resolution[0] // chunk_size[0], patch_resolution[1] // chunk_size[
|
||||
1], patch_resolution[2] // chunk_size[2]
|
||||
x = rearrange(x,
|
||||
"b (nct ct nch ch ncw cw) n d -> b (nct nch ncw ct ch cw) n d",
|
||||
nct=nct,
|
||||
nch=nch,
|
||||
ncw=ncw,
|
||||
ct=chunk_size[0],
|
||||
ch=chunk_size[1],
|
||||
cw=chunk_size[2])
|
||||
moba_chunk_size = chunk_size[0] * chunk_size[1] * chunk_size[2]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"chunk_size should be a int, or a tuple of length 2 or 3, now it is: {len(chunk_size)}"
|
||||
)
|
||||
raise ValueError(f"chunk_size should be a int, or a tuple of length 2 or 3, now it is: {len(chunk_size)}")
|
||||
|
||||
return x, moba_chunk_size
|
||||
|
||||
@@ -892,15 +771,14 @@ def process_moba_output(
|
||||
ch=chunk_size[0],
|
||||
cw=chunk_size[1])
|
||||
elif len(chunk_size) == 3:
|
||||
x = rearrange(
|
||||
x,
|
||||
"b (nct nch ncw ct ch cw) n d -> b (nct ct nch ch ncw cw) n d",
|
||||
nct=patch_resolution[0] // chunk_size[0],
|
||||
nch=patch_resolution[1] // chunk_size[1],
|
||||
ncw=patch_resolution[2] // chunk_size[2],
|
||||
ct=chunk_size[0],
|
||||
ch=chunk_size[1],
|
||||
cw=chunk_size[2])
|
||||
x = rearrange(x,
|
||||
"b (nct nch ncw ct ch cw) n d -> b (nct ct nch ch ncw cw) n d",
|
||||
nct=patch_resolution[0] // chunk_size[0],
|
||||
nch=patch_resolution[1] // chunk_size[1],
|
||||
ncw=patch_resolution[2] // chunk_size[2],
|
||||
ct=chunk_size[0],
|
||||
ch=chunk_size[1],
|
||||
cw=chunk_size[2])
|
||||
|
||||
return x
|
||||
|
||||
@@ -912,18 +790,11 @@ def generate_data(batch_size, seqlen, num_head, head_dim, dtype):
|
||||
torch.cuda.manual_seed(0)
|
||||
device = torch.cuda.current_device()
|
||||
|
||||
q = torch.randn((batch_size, seqlen, num_head, head_dim),
|
||||
requires_grad=True).to(dtype=dtype, device='cuda')
|
||||
k = torch.randn((batch_size, seqlen, num_head, head_dim),
|
||||
requires_grad=True).to(dtype=dtype, device='cuda')
|
||||
v = torch.randn((batch_size, seqlen, num_head, head_dim),
|
||||
requires_grad=True).to(dtype=dtype, device='cuda')
|
||||
q = torch.randn((batch_size, seqlen, num_head, head_dim), requires_grad=True).to(dtype=dtype, device='cuda')
|
||||
k = torch.randn((batch_size, seqlen, num_head, head_dim), requires_grad=True).to(dtype=dtype, device='cuda')
|
||||
v = torch.randn((batch_size, seqlen, num_head, head_dim), requires_grad=True).to(dtype=dtype, device='cuda')
|
||||
print(f"q.shape: {q.shape}, k.shape: {k.shape}, v.shape: {v.shape}")
|
||||
cu_seqlens = torch.arange(0,
|
||||
q.shape[0] * q.shape[1] + 1,
|
||||
q.shape[1],
|
||||
dtype=torch.int32,
|
||||
device='cuda')
|
||||
cu_seqlens = torch.arange(0, q.shape[0] * q.shape[1] + 1, q.shape[1], dtype=torch.int32, device='cuda')
|
||||
max_seqlen = q.shape[1]
|
||||
q = rearrange(q, "b s ... -> (b s) ...")
|
||||
k = rearrange(k, "b s ... -> (b s) ...")
|
||||
@@ -944,8 +815,7 @@ def test_attn_varlen_moba_speed(batch,
|
||||
threshold_type='query_head'):
|
||||
"""Speed test comparing flash_attn vs moba_attention"""
|
||||
# Get data
|
||||
q, k, v, cu_seqlen, max_seqlen = generate_data(batch, seqlen, head,
|
||||
head_dim, dtype)
|
||||
q, k, v, cu_seqlen, max_seqlen = generate_data(batch, seqlen, head, head_dim, dtype)
|
||||
print(
|
||||
f"batch:{batch} head:{head} seqlen:{seqlen} chunk:{moba_chunk_size} topk:{moba_topk} select_mode: {select_mode} simsum_threshold:{simsum_threshold}"
|
||||
)
|
||||
@@ -957,27 +827,13 @@ def test_attn_varlen_moba_speed(batch,
|
||||
|
||||
# Warmup
|
||||
for _ in range(warmup_iters):
|
||||
o = flash_attn_varlen_func(q,
|
||||
k,
|
||||
v,
|
||||
cu_seqlen,
|
||||
cu_seqlen,
|
||||
max_seqlen,
|
||||
max_seqlen,
|
||||
causal=False)
|
||||
o = flash_attn_varlen_func(q, k, v, cu_seqlen, cu_seqlen, max_seqlen, max_seqlen, causal=False)
|
||||
torch.autograd.backward(o, vo_grad)
|
||||
|
||||
torch.cuda.synchronize()
|
||||
start_flash = time.perf_counter()
|
||||
for _ in range(perf_test_iters):
|
||||
o = flash_attn_varlen_func(q,
|
||||
k,
|
||||
v,
|
||||
cu_seqlen,
|
||||
cu_seqlen,
|
||||
max_seqlen,
|
||||
max_seqlen,
|
||||
causal=False)
|
||||
o = flash_attn_varlen_func(q, k, v, cu_seqlen, cu_seqlen, max_seqlen, max_seqlen, causal=False)
|
||||
torch.autograd.backward(o, vo_grad)
|
||||
|
||||
torch.cuda.synchronize()
|
||||
|
||||
@@ -78,7 +78,7 @@ def construct_variable_block_sizes(
|
||||
n_t, n_h, n_w = num_tiles
|
||||
|
||||
def _sizes(dim_len: int, tile: int, n: int) -> torch.LongTensor:
|
||||
sizes = torch.full((n,), tile, dtype=torch.int, device=device)
|
||||
sizes = torch.full((n, ), tile, dtype=torch.int, device=device)
|
||||
remainder = dim_len - (n - 1) * tile
|
||||
sizes[-1] = remainder if remainder > 0 else tile
|
||||
return sizes
|
||||
@@ -87,11 +87,7 @@ def construct_variable_block_sizes(
|
||||
h_sizes = _sizes(h, ts_h, n_h)
|
||||
w_sizes = _sizes(w, ts_w, n_w)
|
||||
|
||||
return (
|
||||
t_sizes[:, None, None]
|
||||
* h_sizes[None, :, None]
|
||||
* w_sizes[None, None, :]
|
||||
).reshape(-1)
|
||||
return (t_sizes[:, None, None] * h_sizes[None, :, None] * w_sizes[None, None, :]).reshape(-1)
|
||||
|
||||
|
||||
def get_non_pad_index(
|
||||
@@ -134,10 +130,8 @@ def build_vsa_metadata(
|
||||
ts_t, ts_h, ts_w = tile_size
|
||||
max_block_size = math.prod(tile_size)
|
||||
if max_block_size not in _SUPPORTED_VSA_BLOCK_VOLUMES:
|
||||
raise ValueError(
|
||||
f"Unsupported VSA tile volume {max_block_size} for tile_size={tile_size}; "
|
||||
f"supported volumes are {_SUPPORTED_VSA_BLOCK_VOLUMES}."
|
||||
)
|
||||
raise ValueError(f"Unsupported VSA tile volume {max_block_size} for tile_size={tile_size}; "
|
||||
f"supported volumes are {_SUPPORTED_VSA_BLOCK_VOLUMES}.")
|
||||
|
||||
num_tiles = (
|
||||
math.ceil(T / ts_t),
|
||||
|
||||
Reference in New Issue
Block a user