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