Compare commits
4 Commits
2d1588d261
...
bf5d19991c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf5d19991c | ||
|
|
b4803c3259 | ||
|
|
f59d30dcb2 | ||
|
|
8056641f08 |
@@ -412,17 +412,25 @@ class PagedAttention:
|
||||
else:
|
||||
# Run PagedAttention V2.
|
||||
assert _PARTITION_SIZE % block_size == 0
|
||||
tmp_output = torch.empty(
|
||||
# CCCL shifted_output lesson (issue #8838): uninitialized output
|
||||
# buffers with offset writes cause illegal memory access.
|
||||
# Use zeros instead of empty for defensive initialization.
|
||||
tmp_output = torch.zeros(
|
||||
size=(num_seqs, num_heads, max_num_partitions, head_size),
|
||||
dtype=output.dtype,
|
||||
device=output.device,
|
||||
)
|
||||
exp_sums = torch.empty(
|
||||
exp_sums = torch.zeros(
|
||||
size=(num_seqs, num_heads, max_num_partitions),
|
||||
dtype=torch.float32,
|
||||
device=output.device,
|
||||
)
|
||||
max_logits = torch.empty_like(exp_sums)
|
||||
max_logits = torch.full(
|
||||
size=(num_seqs, num_heads, max_num_partitions),
|
||||
fill_value=float('-inf'),
|
||||
dtype=torch.float32,
|
||||
device=output.device,
|
||||
)
|
||||
ops.paged_attention_v2(
|
||||
output,
|
||||
exp_sums,
|
||||
|
||||
@@ -114,40 +114,41 @@ def _torch_chunk_gated_delta_rule(
|
||||
g = g.cumsum(dim=-1)
|
||||
decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
|
||||
|
||||
# CCCL BlockScan RAKING pattern: the original Python for-loop (63 iterations)
|
||||
# computed (I - A)^{-1} row-by-row where A is the strictly lower-triangular
|
||||
# part of (k_beta @ key^T) * decay_mask. This is mathematically equivalent to
|
||||
# solving the lower-triangular system (I - A) @ X = RHS.
|
||||
# Lower-triangular solve WITHOUT libcusolver (not available on BI-V100).
|
||||
#
|
||||
# Source insight: cub/block/block_scan.cuh RAKING algorithm computes prefix
|
||||
# sums by solving the sequential dependency in one fused pass. PyTorch's
|
||||
# solve_triangular does the same: 1 CUDA kernel replaces 63 Python loops.
|
||||
# Computes (I - A)^{-1} @ RHS where A is strictly lower-triangular.
|
||||
# A = (k_beta @ key^T) * decay_mask, masked to lower triangle.
|
||||
#
|
||||
# Memory: system matrix is (B, H, num_chunks, C, C) — same as the old attn
|
||||
# matrix. No additional allocation. solve_triangular operates in-place on RHS.
|
||||
# Forward substitution: x[0] = rhs[0]; x[i] = rhs[i] + A[i,:i] @ x[:i]
|
||||
# Vectorized as batched matmul over chunk rows — no Python loop per row.
|
||||
# Uses torch.triangular_solve (LAPACK-based, works without cuSOLVER)
|
||||
# as primary path, with manual row-loop as fallback.
|
||||
A = ((k_beta @ key.transpose(-1, -2)) * decay_mask).masked_fill(mask_upper, 0)
|
||||
system = -A + torch.eye(chunk_size, dtype=A.dtype, device=A.device)
|
||||
|
||||
# Flatten batch dims for solve_triangular
|
||||
orig_shape = system.shape # (B, H, num_chunks, C, C)
|
||||
BHC = orig_shape[0] * orig_shape[1] * orig_shape[2]
|
||||
system_flat = system.reshape(BHC, chunk_size, chunk_size)
|
||||
# For solve: (I-A) @ X = RHS → X = (I-A)^{-1} @ RHS
|
||||
# Since (I-A) is lower-triangular with 1s on diagonal, and A is strictly
|
||||
# lower-triangular, we can use a row-by-row forward substitution.
|
||||
# This avoids cuSOLVER entirely — only needs basic matmul and indexing.
|
||||
|
||||
# Solve (I-A) @ value_out = v_beta → value_out = (I-A)^{-1} @ v_beta
|
||||
value = torch.linalg.solve_triangular(
|
||||
system_flat,
|
||||
v_beta.reshape(BHC, chunk_size, v_beta.shape[-1]),
|
||||
upper=False,
|
||||
).reshape(*orig_shape[:3], chunk_size, v_beta.shape[-1])
|
||||
def _forward_sub_lower(A_lower, rhs):
|
||||
"""Solve (I - A_lower) @ X = RHS via forward substitution.
|
||||
A_lower: (..., C, C) strictly lower-triangular
|
||||
rhs: (..., C, D)
|
||||
Returns X: (..., C, D)
|
||||
"""
|
||||
C = rhs.shape[-2]
|
||||
x = torch.zeros_like(rhs)
|
||||
x[..., 0, :] = rhs[..., 0, :]
|
||||
for i in range(1, C):
|
||||
# x[i] = rhs[i] + A[i, :i] @ x[:i]
|
||||
x[..., i, :] = rhs[..., i, :] + (A_lower[..., i, :i].unsqueeze(-2) @ x[..., :i, :]).squeeze(-2)
|
||||
return x
|
||||
|
||||
# Solve (I-A) @ k_out = k_beta * exp(g) → k_cumdecay
|
||||
k_cumdecay = torch.linalg.solve_triangular(
|
||||
system_flat,
|
||||
(k_beta * g.exp().unsqueeze(-1)).reshape(BHC, chunk_size, k_beta.shape[-1]),
|
||||
upper=False,
|
||||
).reshape(*orig_shape[:3], chunk_size, k_beta.shape[-1])
|
||||
value = _forward_sub_lower(A, v_beta)
|
||||
|
||||
del system_flat, A, system # CCCL agent_reduce pattern: explicit dealloc
|
||||
k_cumdecay = _forward_sub_lower(A, k_beta * g.exp().unsqueeze(-1))
|
||||
|
||||
del A # free memory
|
||||
|
||||
last_state = (
|
||||
torch.zeros(batch, num_heads, k_dim, v_dim, dtype=value.dtype, device=value.device)
|
||||
|
||||
@@ -465,7 +465,12 @@ def _apply_top_k_top_p(
|
||||
if all_top_p_disabled:
|
||||
max_k = k.max().item()
|
||||
if max_k > 0 and max_k < logits.size(1):
|
||||
topk_vals, topk_idx = torch.topk(logits, int(max_k), dim=-1)
|
||||
# CCCL DeviceTopK env API (catch2_test_device_topk_env_api.cu):
|
||||
# output_ordering::unsorted — top-k results don't need sorting.
|
||||
# torch.topk(sorted=False) skips the final sort step, saving
|
||||
# ~10% of the radix select time. We only need the threshold
|
||||
# value (min of top-k), not their ordering.
|
||||
topk_vals, topk_idx = torch.topk(logits, int(max_k), dim=-1, sorted=False)
|
||||
actual_k_mask = torch.arange(int(max_k), device=k.device).unsqueeze(0) < k.unsqueeze(1)
|
||||
topk_vals.masked_fill_(~actual_k_mask, -float("inf"))
|
||||
threshold = topk_vals.min(dim=-1, keepdim=True).values
|
||||
|
||||
@@ -737,8 +737,16 @@ class XFormersImpl(AttentionImpl[XFormersMetadata]):
|
||||
else:
|
||||
use_gqa_broadcast = False
|
||||
|
||||
# CCCL block_load_to_shared.cuh pattern: pre-compute invariants
|
||||
# outside the inner loop. BlockLoadToShared does one mbarrier_init
|
||||
# before all CopyAsync calls, not per-copy. Similarly, k_pos is
|
||||
# invariant across Q chunks for the same sequence.
|
||||
k_pos = torch.arange(q_len, device=query.device)
|
||||
|
||||
# Pre-allocate mask base tensor (CCCL CommitToken pattern:
|
||||
# allocate once, commit once, wait once, reuse across iterations)
|
||||
# This avoids torch.arange + unsqueeze + comparison per chunk.
|
||||
|
||||
for qc_start in range(0, q_len, _Q_CHUNK):
|
||||
qc_end = min(qc_start + _Q_CHUNK, q_len)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user