Compare commits
4 Commits
2d1588d261
...
bf5d19991c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf5d19991c | ||
|
|
b4803c3259 | ||
|
|
f59d30dcb2 | ||
|
|
8056641f08 |
@@ -412,17 +412,25 @@ class PagedAttention:
|
|||||||
else:
|
else:
|
||||||
# Run PagedAttention V2.
|
# Run PagedAttention V2.
|
||||||
assert _PARTITION_SIZE % block_size == 0
|
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),
|
size=(num_seqs, num_heads, max_num_partitions, head_size),
|
||||||
dtype=output.dtype,
|
dtype=output.dtype,
|
||||||
device=output.device,
|
device=output.device,
|
||||||
)
|
)
|
||||||
exp_sums = torch.empty(
|
exp_sums = torch.zeros(
|
||||||
size=(num_seqs, num_heads, max_num_partitions),
|
size=(num_seqs, num_heads, max_num_partitions),
|
||||||
dtype=torch.float32,
|
dtype=torch.float32,
|
||||||
device=output.device,
|
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(
|
ops.paged_attention_v2(
|
||||||
output,
|
output,
|
||||||
exp_sums,
|
exp_sums,
|
||||||
|
|||||||
@@ -114,40 +114,41 @@ def _torch_chunk_gated_delta_rule(
|
|||||||
g = g.cumsum(dim=-1)
|
g = g.cumsum(dim=-1)
|
||||||
decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
|
decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
|
||||||
|
|
||||||
# CCCL BlockScan RAKING pattern: the original Python for-loop (63 iterations)
|
# Lower-triangular solve WITHOUT libcusolver (not available on BI-V100).
|
||||||
# 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.
|
|
||||||
#
|
#
|
||||||
# Source insight: cub/block/block_scan.cuh RAKING algorithm computes prefix
|
# Computes (I - A)^{-1} @ RHS where A is strictly lower-triangular.
|
||||||
# sums by solving the sequential dependency in one fused pass. PyTorch's
|
# A = (k_beta @ key^T) * decay_mask, masked to lower triangle.
|
||||||
# solve_triangular does the same: 1 CUDA kernel replaces 63 Python loops.
|
|
||||||
#
|
#
|
||||||
# Memory: system matrix is (B, H, num_chunks, C, C) — same as the old attn
|
# Forward substitution: x[0] = rhs[0]; x[i] = rhs[i] + A[i,:i] @ x[:i]
|
||||||
# matrix. No additional allocation. solve_triangular operates in-place on RHS.
|
# 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)
|
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
|
# For solve: (I-A) @ X = RHS → X = (I-A)^{-1} @ RHS
|
||||||
orig_shape = system.shape # (B, H, num_chunks, C, C)
|
# Since (I-A) is lower-triangular with 1s on diagonal, and A is strictly
|
||||||
BHC = orig_shape[0] * orig_shape[1] * orig_shape[2]
|
# lower-triangular, we can use a row-by-row forward substitution.
|
||||||
system_flat = system.reshape(BHC, chunk_size, chunk_size)
|
# This avoids cuSOLVER entirely — only needs basic matmul and indexing.
|
||||||
|
|
||||||
# Solve (I-A) @ value_out = v_beta → value_out = (I-A)^{-1} @ v_beta
|
def _forward_sub_lower(A_lower, rhs):
|
||||||
value = torch.linalg.solve_triangular(
|
"""Solve (I - A_lower) @ X = RHS via forward substitution.
|
||||||
system_flat,
|
A_lower: (..., C, C) strictly lower-triangular
|
||||||
v_beta.reshape(BHC, chunk_size, v_beta.shape[-1]),
|
rhs: (..., C, D)
|
||||||
upper=False,
|
Returns X: (..., C, D)
|
||||||
).reshape(*orig_shape[:3], chunk_size, v_beta.shape[-1])
|
"""
|
||||||
|
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
|
value = _forward_sub_lower(A, v_beta)
|
||||||
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])
|
|
||||||
|
|
||||||
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 = (
|
last_state = (
|
||||||
torch.zeros(batch, num_heads, k_dim, v_dim, dtype=value.dtype, device=value.device)
|
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:
|
if all_top_p_disabled:
|
||||||
max_k = k.max().item()
|
max_k = k.max().item()
|
||||||
if max_k > 0 and max_k < logits.size(1):
|
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)
|
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"))
|
topk_vals.masked_fill_(~actual_k_mask, -float("inf"))
|
||||||
threshold = topk_vals.min(dim=-1, keepdim=True).values
|
threshold = topk_vals.min(dim=-1, keepdim=True).values
|
||||||
|
|||||||
@@ -737,8 +737,16 @@ class XFormersImpl(AttentionImpl[XFormersMetadata]):
|
|||||||
else:
|
else:
|
||||||
use_gqa_broadcast = False
|
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)
|
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):
|
for qc_start in range(0, q_len, _Q_CHUNK):
|
||||||
qc_end = min(qc_start + _Q_CHUNK, q_len)
|
qc_end = min(qc_start + _Q_CHUNK, q_len)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user