Skip to content

Commit 8271e9d

Browse files
committed
add backward support
1 parent c95ac1a commit 8271e9d

2 files changed

Lines changed: 157 additions & 14 deletions

File tree

fastvideo-kernel/benchmarks/bench_fused_compress_topk.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
1414
Reports per-kernel latency (ms), speedup, and numerical accuracy (max abs error,
1515
cosine similarity for compress; mask match rate for topk).
16+
17+
Also benchmarks backward pass of compress (fused Triton bwd kernel vs. PyTorch autograd).
1618
"""
1719

1820
from __future__ import annotations
@@ -116,14 +118,13 @@ def accuracy_topk(ref_mask: torch.Tensor, test_mask: torch.Tensor) -> dict:
116118
# Benchmark runner
117119
# ---------------------------------------------------------------------------
118120

119-
def bench_compress(
121+
def bench_compress_fwd(
120122
B: int, H: int, seq_len: int, D: int, block_elements: int,
121123
dtype: torch.dtype, warmup: int, rep: int,
122124
) -> None:
123125
num_blocks = seq_len // block_elements
124126
x = torch.randn(B, H, seq_len, D, dtype=dtype, device="cuda")
125127
vbs = torch.full((num_blocks,), block_elements, dtype=torch.int32, device="cuda")
126-
# Make a few blocks partially filled to exercise variable block sizes
127128
if num_blocks > 4:
128129
vbs[1] = block_elements - 2
129130
vbs[-2] = block_elements - 5
@@ -138,7 +139,58 @@ def bench_compress(
138139
new_ms = do_bench(lambda: fused_block_mean(x, vbs, block_elements), warmup=warmup, rep=rep)
139140

140141
speedup = old_ms / new_ms if new_ms > 0 else float("inf")
141-
print(f" compress | old: {old_ms:8.3f} ms | new: {new_ms:8.3f} ms | speedup: {speedup:5.2f}x "
142+
print(f" compress fwd | old: {old_ms:8.3f} ms | new: {new_ms:8.3f} ms | speedup: {speedup:5.2f}x "
143+
f"| max_abs_err: {acc['max_abs_err']:.2e} | cos_sim: {acc['cosine_sim']:.8f}")
144+
145+
146+
def bench_compress_bwd(
147+
B: int, H: int, seq_len: int, D: int, block_elements: int,
148+
dtype: torch.dtype, warmup: int, rep: int,
149+
) -> None:
150+
num_blocks = seq_len // block_elements
151+
vbs = torch.full((num_blocks,), block_elements, dtype=torch.int32, device="cuda")
152+
if num_blocks > 4:
153+
vbs[1] = block_elements - 2
154+
vbs[-2] = block_elements - 5
155+
156+
# --- Accuracy: compare gradients ---
157+
x_old = torch.randn(B, H, seq_len, D, dtype=dtype, device="cuda", requires_grad=True)
158+
grad_out = torch.randn(B, H, num_blocks, D, dtype=dtype, device="cuda")
159+
160+
out_old = pytorch_block_mean(x_old, vbs, block_elements)
161+
out_old.backward(grad_out)
162+
grad_ref = x_old.grad.clone()
163+
164+
x_new = x_old.detach().clone().requires_grad_(True)
165+
out_new = fused_block_mean(x_new, vbs, block_elements)
166+
out_new.backward(grad_out)
167+
grad_fused = x_new.grad.clone()
168+
169+
acc = accuracy_compress(grad_ref, grad_fused)
170+
171+
# --- Latency: isolate backward-only via retain_graph ---
172+
x_o = x_old.detach().clone().requires_grad_(True)
173+
out_o = pytorch_block_mean(x_o, vbs, block_elements)
174+
loss_o = (out_o * grad_out).sum()
175+
for _ in range(warmup):
176+
torch.autograd.grad(loss_o, x_o, retain_graph=True)
177+
old_ms = do_bench(
178+
lambda: torch.autograd.grad(loss_o, x_o, retain_graph=True),
179+
warmup=0, rep=rep,
180+
)
181+
182+
x_n = x_old.detach().clone().requires_grad_(True)
183+
out_n = fused_block_mean(x_n, vbs, block_elements)
184+
loss_n = (out_n * grad_out).sum()
185+
for _ in range(warmup):
186+
torch.autograd.grad(loss_n, x_n, retain_graph=True)
187+
new_ms = do_bench(
188+
lambda: torch.autograd.grad(loss_n, x_n, retain_graph=True),
189+
warmup=0, rep=rep,
190+
)
191+
192+
speedup = old_ms / new_ms if new_ms > 0 else float("inf")
193+
print(f" compress bwd | old: {old_ms:8.3f} ms | new: {new_ms:8.3f} ms | speedup: {speedup:5.2f}x "
142194
f"| max_abs_err: {acc['max_abs_err']:.2e} | cos_sim: {acc['cosine_sim']:.8f}")
143195

144196

@@ -188,7 +240,8 @@ def main() -> None:
188240
print(f"seq_len={seq_len}, num_blocks={num_blocks}, topk={topk}")
189241
print("-" * 100)
190242

191-
bench_compress(B, H, seq_len, D, block_elements, dtype, args.warmup, args.rep)
243+
bench_compress_fwd(B, H, seq_len, D, block_elements, dtype, args.warmup, args.rep)
244+
bench_compress_bwd(B, H, seq_len, D, block_elements, dtype, args.warmup, args.rep)
192245
bench_topk(B, H, num_blocks, topk, dtype, args.warmup, args.rep)
193246

194247

fastvideo-kernel/python/fastvideo_kernel/triton_kernels/fused_compress_topk.py

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,75 @@ def _fused_block_mean_kernel(
5858
tl.store(out_base, acc.to(tl.bfloat16))
5959

6060

61-
def fused_block_mean(
62-
x: torch.Tensor,
61+
@triton.jit
62+
def _fused_block_mean_bwd_kernel(
63+
GradOut_ptr,
64+
GradX_ptr,
65+
VBS_ptr,
66+
stride_go_bh,
67+
stride_go_blk,
68+
stride_gx_bh,
69+
stride_gx_seq,
70+
num_blocks,
71+
BLOCK_ELEMENTS: tl.constexpr,
72+
HEAD_DIM: tl.constexpr,
73+
):
74+
"""Backward of block mean: broadcast grad_out / vbs to each token in the block.
75+
76+
Mirrors the forward kernel: one program per (block, bh).
77+
GradOut is [B*H, num_blocks, HEAD_DIM].
78+
GradX is [B*H, num_blocks*BLOCK_ELEMENTS, HEAD_DIM].
79+
"""
80+
block_idx = tl.program_id(0)
81+
bh_idx = tl.program_id(1)
82+
83+
if block_idx >= num_blocks:
84+
return
85+
86+
vbs = tl.load(VBS_ptr + block_idx).to(tl.float32)
87+
88+
go_base = GradOut_ptr + bh_idx * stride_go_bh + block_idx * stride_go_blk
89+
dim_offsets = tl.arange(0, HEAD_DIM)
90+
grad_val = tl.load(go_base + dim_offsets).to(tl.float32) / vbs
91+
92+
gx_base = GradX_ptr + bh_idx * stride_gx_bh + block_idx * BLOCK_ELEMENTS * stride_gx_seq
93+
grad_out_cast = grad_val.to(tl.bfloat16)
94+
for i in range(BLOCK_ELEMENTS):
95+
tl.store(gx_base + i * stride_gx_seq + dim_offsets, grad_out_cast)
96+
97+
98+
def _fused_block_mean_bwd(
99+
grad_output: torch.Tensor,
63100
variable_block_sizes: torch.Tensor,
64101
block_elements: int,
65102
) -> torch.Tensor:
66-
"""Compute block-wise mean with fp32 accumulation, fused in one kernel.
103+
B, H, num_blocks, D = grad_output.shape
104+
seq_len = num_blocks * block_elements
67105

68-
Args:
69-
x: [B, H, seq_len, D] in bf16
70-
variable_block_sizes: [num_blocks] number of valid tokens per block
71-
block_elements: tokens per block (e.g. 64)
106+
grad_x = torch.empty(B, H, seq_len, D, dtype=grad_output.dtype, device=grad_output.device)
72107

73-
Returns:
74-
[B, H, num_blocks, D] in bf16
75-
"""
108+
go_flat = grad_output.contiguous().view(B * H, num_blocks, D)
109+
gx_flat = grad_x.view(B * H, seq_len, D)
110+
111+
grid = (num_blocks, B * H)
112+
113+
_fused_block_mean_bwd_kernel[grid](
114+
go_flat, gx_flat, variable_block_sizes,
115+
go_flat.stride(0), go_flat.stride(1),
116+
gx_flat.stride(0), gx_flat.stride(1),
117+
num_blocks,
118+
BLOCK_ELEMENTS=block_elements,
119+
HEAD_DIM=D,
120+
)
121+
122+
return grad_x
123+
124+
125+
def _fused_block_mean_fwd(
126+
x: torch.Tensor,
127+
variable_block_sizes: torch.Tensor,
128+
block_elements: int,
129+
) -> torch.Tensor:
76130
B, H, seq_len, D = x.shape
77131
num_blocks = seq_len // block_elements
78132
assert seq_len % block_elements == 0
@@ -97,6 +151,42 @@ def fused_block_mean(
97151
return out
98152

99153

154+
class _FusedBlockMeanAutograd(torch.autograd.Function):
155+
156+
@staticmethod
157+
def forward(ctx, x, variable_block_sizes, block_elements):
158+
ctx.save_for_backward(variable_block_sizes)
159+
ctx.block_elements = block_elements
160+
return _fused_block_mean_fwd(x, variable_block_sizes, block_elements)
161+
162+
@staticmethod
163+
def backward(ctx, grad_output):
164+
variable_block_sizes, = ctx.saved_tensors
165+
block_elements = ctx.block_elements
166+
return _fused_block_mean_bwd(grad_output, variable_block_sizes, block_elements), None, None
167+
168+
169+
def fused_block_mean(
170+
x: torch.Tensor,
171+
variable_block_sizes: torch.Tensor,
172+
block_elements: int,
173+
) -> torch.Tensor:
174+
"""Compute block-wise mean with fp32 accumulation, fused in one kernel.
175+
176+
Forward: fused Triton kernel (bf16 read → fp32 accumulate → div → bf16 write).
177+
Backward: broadcasts grad_output / vbs back to each token position.
178+
179+
Args:
180+
x: [B, H, seq_len, D] in bf16
181+
variable_block_sizes: [num_blocks] number of valid tokens per block
182+
block_elements: tokens per block (e.g. 64)
183+
184+
Returns:
185+
[B, H, num_blocks, D] in bf16
186+
"""
187+
return _FusedBlockMeanAutograd.apply(x, variable_block_sizes, block_elements)
188+
189+
100190
@triton.jit
101191
def _fused_topk_mask_kernel(
102192
Scores_ptr,

0 commit comments

Comments
 (0)