Download upstreams/flm/utils/jvp.py from jyliuAI/diffusion_baseline: direct link, hf CLI and curl.
- Browser
- Download file 29.1 kB
-
https://huggingface.co/jyliuAI/diffusion_baseline/resolve/main/upstreams/flm/utils/jvp.py
- Command line
-
hf download hf://jyliuAI/diffusion_baseline/upstreams/flm/utils/jvp.py
-
curl -L -o jvp.py https://huggingface.co/jyliuAI/diffusion_baseline/resolve/main/upstreams/flm/utils/jvp.py
29.1 kB
| # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """ | |
| Flash Attention v2 with JVP | |
| =============== | |
| This is a Triton implementation of the Flash Attention v2 algorithm from Tri Dao (https://tridao.me/publications/flash2/flash2.pdf) | |
| Taken from https://github.com/triton-lang/triton/blob/main/python/tutorials/06-fused-attention.py | |
| Modified 2025/03; Author: Kaiwen Zheng (zkwthu@gmail.com) | |
| (1) Simplified version, combining Triton forward and official backward | |
| (2) Support Jacobian-vector-product (JVP) computation in the forward pass | |
| Credits: OpenAI kernel team | |
| Extra Credits: | |
| * Original flash attention paper (https://arxiv.org/abs/2205.14135) | |
| * Rabe and Staats (https://arxiv.org/pdf/2112.05682v2.pdf) | |
| """ | |
| import torch | |
| import triton | |
| import triton.language as tl | |
| from einops import rearrange | |
| from flash_attn.flash_attn_interface import _flash_attn_backward, _flash_attn_varlen_backward | |
| DEVICE = "cuda" | |
| def _attn_fwd_inner( | |
| acc, | |
| acc_A, | |
| acc_B, | |
| l_i, | |
| m_i, | |
| r_i, | |
| q, | |
| tq, # | |
| K_block_ptr, | |
| V_block_ptr, | |
| tK_block_ptr, | |
| tV_block_ptr, # | |
| start_m, | |
| sm_scale, # | |
| BLOCK_M: tl.constexpr, | |
| BLOCK_N: tl.constexpr, # | |
| STAGE: tl.constexpr, | |
| offs_m: tl.constexpr, | |
| offs_n: tl.constexpr, # | |
| SEQ_LEN_KV: tl.constexpr, | |
| HEAD_DIM_V: tl.constexpr, | |
| bf16_v: tl.constexpr, | |
| ): | |
| # range of values handled by this stage | |
| if STAGE == 1: | |
| lo, hi = 0, min(start_m * BLOCK_M, SEQ_LEN_KV) | |
| elif STAGE == 2: | |
| lo, hi = start_m * BLOCK_M, min((start_m + 1) * BLOCK_M, SEQ_LEN_KV) | |
| lo = tl.multiple_of(lo, BLOCK_M) | |
| # causal = False | |
| else: | |
| lo, hi = 0, SEQ_LEN_KV | |
| qk_scale = sm_scale * 1.44269504 | |
| K_block_ptr = tl.advance(K_block_ptr, (0, lo)) | |
| V_block_ptr = tl.advance(V_block_ptr, (lo, 0)) | |
| tK_block_ptr = tl.advance(tK_block_ptr, (0, lo)) | |
| tV_block_ptr = tl.advance(tV_block_ptr, (lo, 0)) | |
| # loop over k, v and update accumulator | |
| for start_n in range(lo, hi, BLOCK_N): | |
| start_n = tl.multiple_of(start_n, BLOCK_N) | |
| # -- compute qk ---- | |
| k, tk = tl.load(K_block_ptr, boundary_check=(0, 1), padding_option="zero"), tl.load( | |
| tK_block_ptr, boundary_check=(0, 1), padding_option="zero" | |
| ) | |
| qk = tl.dot(q, k) | |
| tS_ij = tl.dot(tq, k) | |
| tS_ij = tl.dot(q, tk, tS_ij) | |
| tS_ij *= sm_scale | |
| if STAGE == 2: | |
| causal_mask = offs_m[:, None] >= (start_n + offs_n[None, :]) | |
| qk = qk * qk_scale + tl.where(causal_mask, 0, -1.0e6) | |
| m_ij = tl.maximum(m_i, tl.max(qk, 1)) | |
| qk -= m_ij[:, None] | |
| else: | |
| m_ij = tl.maximum(m_i, tl.max(qk, 1) * qk_scale) | |
| qk = qk * qk_scale - m_ij[:, None] | |
| # mask if SEQ_LEN_KV % BLOCK_N != 0 | |
| boundary_m = tl.full([BLOCK_M], hi, dtype=tl.int32) | |
| size_n = start_n + offs_n[None, :] | |
| mask = size_n < boundary_m[:, None] | |
| qk = tl.where(mask, qk, float("-inf")) | |
| p = tl.math.exp2(qk) | |
| l_ij = tl.sum(p, 1) | |
| tS_ij = tl.where(mask, tS_ij, float("0")) | |
| H_ij = p * tS_ij | |
| r_ij = tl.sum(H_ij, 1) | |
| # -- update m_i and l_i | |
| alpha = tl.math.exp2(m_i - m_ij) | |
| l_i = l_i * alpha + l_ij | |
| r_i = r_i * alpha + r_ij | |
| # -- update output accumulator -- | |
| acc = acc * alpha[:, None] | |
| acc_A = acc_A * alpha[:, None] | |
| acc_B = acc_B * alpha[:, None] | |
| # update acc | |
| v, tv = tl.load(V_block_ptr, boundary_check=(0, 1), padding_option="zero"), tl.load( | |
| tV_block_ptr, boundary_check=(0, 1), padding_option="zero" | |
| ) | |
| # boundary_v = tl.full([HEAD_DIM_V], hi, dtype=tl.int32) | |
| # size_n = start_n + offs_n | |
| # mask_v = size_n[:, None] < boundary_v[None, :] | |
| # v = tl.where(mask_v, v, float("0")) | |
| # tv = tl.where(mask_v, tv, float("0")) | |
| if bf16_v: | |
| p = p.to(tl.bfloat16) | |
| H_ij = H_ij.to(tl.bfloat16) | |
| v = v.to(tl.bfloat16) | |
| tv = tv.to(tl.bfloat16) | |
| else: | |
| p = p.to(tl.float16) | |
| H_ij = H_ij.to(tl.float16) | |
| v = v.to(tl.float16) | |
| tv = tv.to(tl.float16) | |
| acc = tl.dot(p, v, acc) | |
| acc_A = tl.dot(p, tv, acc_A) | |
| acc_B = tl.dot(H_ij, v, acc_B) | |
| # update m_i and l_i | |
| m_i = m_ij | |
| V_block_ptr = tl.advance(V_block_ptr, (BLOCK_N, 0)) | |
| K_block_ptr = tl.advance(K_block_ptr, (0, BLOCK_N)) | |
| tV_block_ptr = tl.advance(tV_block_ptr, (BLOCK_N, 0)) | |
| tK_block_ptr = tl.advance(tK_block_ptr, (0, BLOCK_N)) | |
| return acc, acc_A, acc_B, l_i, m_i, r_i | |
| # We don't run auto-tuning every time to keep the tutorial fast. Keeping | |
| # the code below and commenting out the equivalent parameters is convenient for | |
| # re-tuning. | |
| configs = [ | |
| triton.Config({"BLOCK_M": BM, "BLOCK_N": BN}, num_stages=s, num_warps=w) | |
| for BM in [64, 128] | |
| for BN in [16, 32, 64] | |
| for s in [3, 4, 7] | |
| for w in [4, 8] | |
| ] | |
| def _attn_fwd( | |
| Q, | |
| K, | |
| V, | |
| tQ, | |
| tK, | |
| tV, | |
| sm_scale, | |
| M, | |
| Out, | |
| tOut, # | |
| stride_qz, | |
| stride_qh, | |
| stride_qm, | |
| stride_qd, # | |
| stride_kz, | |
| stride_kh, | |
| stride_kn, | |
| stride_kd, # | |
| stride_vz, | |
| stride_vh, | |
| stride_vn, | |
| stride_vd, # | |
| stride_oz, | |
| stride_oh, | |
| stride_om, | |
| stride_od, # | |
| Z, | |
| H, # | |
| SEQ_LEN_Q, | |
| SEQ_LEN_KV, # | |
| HEAD_DIM_QK: tl.constexpr, | |
| HEAD_DIM_V: tl.constexpr, # | |
| BLOCK_M: tl.constexpr, # | |
| BLOCK_N: tl.constexpr, # | |
| STAGE: tl.constexpr, # | |
| ): | |
| start_m = tl.program_id(0) | |
| off_hz = tl.program_id(1) | |
| off_z = off_hz // H | |
| off_h = off_hz % H | |
| q_offset = off_z.to(tl.int64) * stride_qz + off_h.to(tl.int64) * stride_qh | |
| k_offset = off_z.to(tl.int64) * stride_kz + off_h.to(tl.int64) * stride_kh | |
| v_offset = off_z.to(tl.int64) * stride_vz + off_h.to(tl.int64) * stride_vh | |
| o_offset = off_z.to(tl.int64) * stride_oz + off_h.to(tl.int64) * stride_oh | |
| start_m_idx = start_m * BLOCK_M | |
| # end_m_idx = (start_m + 1) * BLOCK_M | |
| # block pointers | |
| Q_block_ptr = tl.make_block_ptr( | |
| base=Q + q_offset, | |
| shape=(SEQ_LEN_Q, HEAD_DIM_QK), | |
| strides=(stride_qm, stride_qd), | |
| offsets=(start_m_idx, 0), | |
| block_shape=(BLOCK_M, HEAD_DIM_QK), | |
| order=(1, 0), | |
| ) | |
| V_block_ptr = tl.make_block_ptr( | |
| base=V + v_offset, | |
| shape=(SEQ_LEN_KV, HEAD_DIM_V), | |
| strides=(stride_vn, stride_vd), | |
| offsets=(0, 0), | |
| block_shape=(BLOCK_N, HEAD_DIM_V), | |
| order=(1, 0), | |
| ) | |
| # load transposed K | |
| K_block_ptr = tl.make_block_ptr( | |
| base=K + k_offset, | |
| shape=(HEAD_DIM_QK, SEQ_LEN_KV), | |
| strides=(stride_kd, stride_kn), | |
| offsets=(0, 0), | |
| block_shape=(HEAD_DIM_QK, BLOCK_N), | |
| order=(0, 1), | |
| ) | |
| O_block_ptr = tl.make_block_ptr( | |
| base=Out + o_offset, | |
| shape=(SEQ_LEN_Q, HEAD_DIM_V), | |
| strides=(stride_om, stride_od), | |
| offsets=(start_m_idx, 0), | |
| block_shape=(BLOCK_M, HEAD_DIM_V), | |
| order=(1, 0), | |
| ) | |
| tQ_block_ptr = tl.make_block_ptr( | |
| base=tQ + q_offset, | |
| shape=(SEQ_LEN_Q, HEAD_DIM_QK), | |
| strides=(stride_qm, stride_qd), | |
| offsets=(start_m_idx, 0), | |
| block_shape=(BLOCK_M, HEAD_DIM_QK), | |
| order=(1, 0), | |
| ) | |
| tV_block_ptr = tl.make_block_ptr( | |
| base=tV + v_offset, | |
| shape=(SEQ_LEN_KV, HEAD_DIM_V), | |
| strides=(stride_vn, stride_vd), | |
| offsets=(0, 0), | |
| block_shape=(BLOCK_N, HEAD_DIM_V), | |
| order=(1, 0), | |
| ) | |
| # load transposed K | |
| tK_block_ptr = tl.make_block_ptr( | |
| base=tK + k_offset, | |
| shape=(HEAD_DIM_QK, SEQ_LEN_KV), | |
| strides=(stride_kd, stride_kn), | |
| offsets=(0, 0), | |
| block_shape=(HEAD_DIM_QK, BLOCK_N), | |
| order=(0, 1), | |
| ) | |
| tO_block_ptr = tl.make_block_ptr( | |
| base=tOut + o_offset, | |
| shape=(SEQ_LEN_Q, HEAD_DIM_V), | |
| strides=(stride_om, stride_od), | |
| offsets=(start_m_idx, 0), | |
| block_shape=(BLOCK_M, HEAD_DIM_V), | |
| order=(1, 0), | |
| ) | |
| # initialize offsets | |
| offs_m = start_m_idx + tl.arange(0, BLOCK_M) | |
| offs_n = tl.arange(0, BLOCK_N) | |
| offs_d_qk, offs_d_v = tl.arange(0, HEAD_DIM_QK), tl.arange(0, HEAD_DIM_V) | |
| # initialize pointer to m and l | |
| m_i = tl.zeros([BLOCK_M], dtype=tl.float32) - float("inf") | |
| l_i = tl.zeros([BLOCK_M], dtype=tl.float32) # + 1.0 | |
| acc = tl.zeros([BLOCK_M, HEAD_DIM_V], dtype=tl.float32) | |
| r_i = tl.zeros([BLOCK_M], dtype=tl.float32) | |
| acc_A = tl.zeros([BLOCK_M, HEAD_DIM_V], dtype=tl.float32) | |
| acc_B = tl.zeros([BLOCK_M, HEAD_DIM_V], dtype=tl.float32) | |
| # load q: it will stay in SRAM throughout | |
| q, tq = tl.load(Q_block_ptr, boundary_check=(0, 1), padding_option="zero"), tl.load(tQ_block_ptr, boundary_check=(0, 1), padding_option="zero") | |
| # stage 1: off-band | |
| # For causal = True, STAGE = 3 and _attn_fwd_inner gets 1 as its STAGE | |
| # For causal = False, STAGE = 1, and _attn_fwd_inner gets 3 as its STAGE | |
| if STAGE & 1: | |
| acc, acc_A, acc_B, l_i, m_i, r_i = _attn_fwd_inner( | |
| acc, | |
| acc_A, | |
| acc_B, | |
| l_i, | |
| m_i, | |
| r_i, | |
| q, | |
| tq, # | |
| K_block_ptr, | |
| V_block_ptr, | |
| tK_block_ptr, | |
| tV_block_ptr, # | |
| start_m, | |
| sm_scale, # | |
| BLOCK_M, | |
| BLOCK_N, # | |
| 4 - STAGE, | |
| offs_m, | |
| offs_n, | |
| SEQ_LEN_KV, | |
| HEAD_DIM_V, | |
| V.dtype.element_ty == tl.bfloat16, # | |
| ) | |
| # stage 2: on-band | |
| if STAGE & 2: | |
| # barrier makes it easier for compielr to schedule the | |
| # two loops independently | |
| acc, acc_A, acc_B, l_i, m_i, r_i = _attn_fwd_inner( | |
| acc, | |
| acc_A, | |
| acc_B, | |
| l_i, | |
| m_i, | |
| r_i, | |
| q, | |
| tq, # | |
| K_block_ptr, | |
| V_block_ptr, | |
| tK_block_ptr, | |
| tV_block_ptr, # | |
| start_m, | |
| sm_scale, # | |
| BLOCK_M, | |
| BLOCK_N, # | |
| 2, | |
| offs_m, | |
| offs_n, | |
| SEQ_LEN_KV, | |
| HEAD_DIM_V, | |
| V.dtype.element_ty == tl.bfloat16, # | |
| ) | |
| # epilogue | |
| # m_i += tl.math.log2(l_i) | |
| empty_mask = l_i == 0.0 | |
| # NOTE: This happens if the entire block is masked out. | |
| l_i = tl.where(empty_mask, 1.0, l_i) | |
| # NOTE: This is needed to compute the logsumexp for the backward pass. | |
| m_i = m_i + tl.where( | |
| empty_mask, | |
| 0.0, | |
| tl.math.log2(l_i), | |
| ) | |
| acc = acc / l_i[:, None] | |
| tO_i = (acc_A + acc_B - (r_i[:, None] * acc)) / l_i[:, None] | |
| m_ptrs = M + off_hz * SEQ_LEN_Q + offs_m | |
| O_block_ptr = Out + o_offset + offs_m[:, None] * stride_om + offs_d_v[None, :] * stride_od | |
| tO_block_ptr = tOut + o_offset + offs_m[:, None] * stride_om + offs_d_v[None, :] * stride_od | |
| # mask if SEQ_LEN_Q % BLOCK_M != 0 | |
| mask_lse = offs_m < SEQ_LEN_Q | |
| mask = offs_m[:, None] < SEQ_LEN_Q | |
| tl.store(m_ptrs, m_i * 0.69314718, mask=mask_lse) | |
| tl.store(O_block_ptr, acc.to(Out.type.element_ty), mask=mask) | |
| tl.store(tO_block_ptr, tO_i.to(tOut.type.element_ty), mask=mask) | |
| def generate_qkv(q, k, v): | |
| """ | |
| Arguments: | |
| q: (batch_size, nheads, seqlen_q, d) | |
| k: (batch_size, nheads_k, seqlen_k, d) | |
| v: (batch_size, nheads_k, seqlen_k, d) | |
| """ | |
| batch_size, _, seqlen_q, d = q.shape | |
| _, nheads_k, seqlen_k, _ = k.shape | |
| assert k.shape == (batch_size, nheads_k, seqlen_k, d) | |
| assert v.shape == (batch_size, nheads_k, seqlen_k, d) | |
| def unpad_fn(x): | |
| return rearrange(x, "b h s d -> (b s) h d") | |
| def lse_unpad_fn(x): | |
| return rearrange(x, "b h s -> (b s) h") | |
| def pad_fn(x): | |
| return rearrange(x, "(b s) h d -> b h s d", b=batch_size) | |
| # unpad_fn = lambda x: rearrange(x, "b h s d -> (b s) h d") | |
| # lse_unpad_fn = lambda x: rearrange(x, "b h s -> (b s) h") | |
| # pad_fn = lambda x: rearrange(x, "(b s) h d -> b h s d", b=batch_size) | |
| cu_seqlens_q = torch.arange(0, (batch_size + 1) * seqlen_q, step=seqlen_q, dtype=torch.int32, device=q.device) | |
| max_seqlen_q = seqlen_q | |
| cu_seqlens_k = torch.arange(0, (batch_size + 1) * seqlen_k, step=seqlen_k, dtype=torch.int32, device=q.device) | |
| max_seqlen_k = seqlen_k | |
| return ( | |
| unpad_fn, | |
| lse_unpad_fn, | |
| pad_fn, | |
| cu_seqlens_q, | |
| cu_seqlens_k, | |
| max_seqlen_q, | |
| max_seqlen_k, | |
| ) | |
| class _attention(torch.autograd.Function): | |
| """ | |
| Arguments: | |
| q, tq: (batch_size, nheads, seqlen_q, d_qk) | |
| k, tk: (batch_size, nheads, seqlen_kv, d_qk) | |
| v, tv: (batch_size, nheads, seqlen_kv, d_v) | |
| Returns: | |
| o, to: (batch_size, nheads, seqlen_q, d_v) | |
| Backward is only supported when d_qk=d_v. | |
| """ | |
| def forward(ctx, q, k, v, tq, tk, tv, causal=False, sm_scale=None): | |
| is_grad = any(x.requires_grad for x in [q, k, v]) | |
| # shape constraints | |
| assert q.shape[:-2] == k.shape[:-2] and k.shape[:-2] == v.shape[:-2] | |
| assert k.shape[-2] == v.shape[-2] and q.shape[-1] == k.shape[-1] | |
| Z, H = q.shape[:-2] | |
| SEQ_LEN_Q, SEQ_LEN_KV = q.shape[-2], k.shape[-2] | |
| HEAD_DIM_QK, HEAD_DIM_V = q.shape[-1], v.shape[-1] | |
| assert HEAD_DIM_QK in {16, 32, 64, 128, 256} | |
| assert HEAD_DIM_V in {16, 32, 64, 128, 256} | |
| assert (SEQ_LEN_Q == SEQ_LEN_KV) or (not causal), "Causal cross-attention is currently not supported." | |
| assert tq.shape == q.shape and tk.shape == k.shape and tv.shape == v.shape | |
| assert tq.stride() == q.stride() and tk.stride() == k.stride() and tv.stride() == v.stride() | |
| if sm_scale is None: | |
| sm_scale = HEAD_DIM_QK ** (-0.5) | |
| o = torch.empty((Z, H, SEQ_LEN_Q, HEAD_DIM_V), device=q.device, dtype=q.dtype) | |
| to = torch.empty_like(o) | |
| stage = 3 if causal else 1 | |
| M = torch.empty((Z, H, SEQ_LEN_Q), device=q.device, dtype=torch.float32) | |
| def grid(args): | |
| return (triton.cdiv(SEQ_LEN_Q, args["BLOCK_M"]), Z * H, 1) | |
| # grid = lambda args: (triton.cdiv(SEQ_LEN_Q, args["BLOCK_M"]), Z * H, 1) | |
| ctx.grid = grid | |
| _attn_fwd[grid]( | |
| q, | |
| k, | |
| v, | |
| tq, | |
| tk, | |
| tv, | |
| sm_scale, | |
| M, | |
| o, | |
| to, # | |
| q.stride(0), | |
| q.stride(1), | |
| q.stride(2), | |
| q.stride(3), # | |
| k.stride(0), | |
| k.stride(1), | |
| k.stride(2), | |
| k.stride(3), # | |
| v.stride(0), | |
| v.stride(1), | |
| v.stride(2), | |
| v.stride(3), # | |
| o.stride(0), | |
| o.stride(1), | |
| o.stride(2), | |
| o.stride(3), # | |
| Z, | |
| H, # | |
| SEQ_LEN_Q, | |
| SEQ_LEN_KV, # | |
| HEAD_DIM_QK, | |
| HEAD_DIM_V, # | |
| STAGE=stage, | |
| ) | |
| if is_grad: | |
| ctx.save_for_backward(q, k, v, o, M) | |
| ctx.sm_scale = sm_scale | |
| ctx.causal = causal | |
| return o, to | |
| def backward(ctx, dout, *args): | |
| q, k, v, out, softmax_lse = ctx.saved_tensors | |
| assert q.shape[-1] == k.shape[-1] and k.shape[-1] == v.shape[-1], "Backward not supported with different headdim." | |
| # flash_attn uses the shape (batch_size, seqlen, nheads, headdim) | |
| # torch.nn.functional.scaled_dot_product_attention and this implementation use (batch_size, nheads, seqlen, headdim) | |
| if q.shape[-2] == k.shape[-2]: | |
| dq, dk, dv = torch.empty_like(q), torch.empty_like(k), torch.empty_like(v) | |
| _flash_attn_backward( | |
| dout.transpose(1, 2), | |
| q.transpose(1, 2), | |
| k.transpose(1, 2), | |
| v.transpose(1, 2), | |
| out.transpose(1, 2), | |
| softmax_lse, | |
| dq.transpose(1, 2), | |
| dk.transpose(1, 2), | |
| dv.transpose(1, 2), | |
| dropout_p=0.0, | |
| softmax_scale=ctx.sm_scale, | |
| causal=ctx.causal, | |
| window_size=(-1, -1), | |
| # softcap=0, | |
| alibi_slopes=None, | |
| deterministic=False, | |
| ) | |
| else: | |
| unpad_fn, lse_unpad_fn, pad_fn, cu_seqlens_q, cu_seqlens_k, max_seqlen_q, max_seqlen_k = generate_qkv(q, k, v) | |
| q_unpad, k_unpad, v_unpad = unpad_fn(q), unpad_fn(k), unpad_fn(v) | |
| dq, dk, dv = torch.empty_like(q_unpad), torch.empty_like(k_unpad), torch.empty_like(v_unpad) | |
| _flash_attn_varlen_backward( | |
| unpad_fn(dout), | |
| q_unpad, | |
| k_unpad, | |
| v_unpad, | |
| unpad_fn(out), | |
| lse_unpad_fn(softmax_lse), | |
| dq, | |
| dk, | |
| dv, | |
| cu_seqlens_q, | |
| cu_seqlens_k, | |
| max_seqlen_q, | |
| max_seqlen_k, | |
| dropout_p=0.0, | |
| softmax_scale=ctx.sm_scale, | |
| causal=ctx.causal, | |
| window_size=(-1, -1), | |
| # softcap=0, | |
| alibi_slopes=None, | |
| deterministic=False, | |
| ) | |
| dq, dk, dv = pad_fn(dq), pad_fn(dk), pad_fn(dv) | |
| return dq, dk, dv, None, None, None, None, None | |
| attention = _attention.apply | |
| def _test_fwd_bwd(Z, H, SEQ_LEN, HEAD_DIM, causal, dtype=torch.float16): | |
| torch.manual_seed(20) | |
| q = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| k = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| v = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| tq = torch.zeros_like(q) | |
| tk = torch.zeros_like(k) | |
| tv = torch.zeros_like(v) | |
| sm_scale = 0.5 | |
| dout = torch.randn_like(q) | |
| # reference implementation | |
| M = torch.tril(torch.ones((SEQ_LEN, SEQ_LEN), device=DEVICE)) | |
| p = torch.matmul(q, k.transpose(2, 3)) * sm_scale | |
| if causal: | |
| p[:, :, M == 0] = float("-inf") | |
| p = torch.softmax(p.float(), dim=-1).to(dtype) | |
| ref_out = torch.matmul(p, v) | |
| ref_out.backward(dout) | |
| ref_dv, v.grad = v.grad.clone(), None | |
| ref_dk, k.grad = k.grad.clone(), None | |
| ref_dq, q.grad = q.grad.clone(), None | |
| # triton implementation | |
| tri_out = attention(q, k, v, tq, tk, tv, causal, sm_scale)[0].to(dtype) | |
| tri_out.backward(dout) | |
| tri_dv, v.grad = v.grad.clone(), None | |
| tri_dk, k.grad = k.grad.clone(), None | |
| tri_dq, q.grad = q.grad.clone(), None | |
| # compare | |
| rtol = 2e-2 if dtype == torch.bfloat16 else 0 | |
| torch.testing.assert_close(ref_out, tri_out, atol=1e-2, rtol=0) | |
| torch.testing.assert_close(ref_dq, tri_dq, atol=1e-2, rtol=rtol / 2) | |
| torch.testing.assert_close(ref_dk, tri_dk, atol=1e-2, rtol=rtol / 2) | |
| torch.testing.assert_close(ref_dv, tri_dv, atol=1e-2, rtol=rtol) | |
| def test_fwd_bwd(): | |
| for shape in [(1, 2, 1024, 64), (1, 2, 999, 64)]: | |
| for causal in [True, False]: | |
| for dtype in [torch.float16, torch.bfloat16]: | |
| _test_fwd_bwd(*shape, causal, dtype) | |
| print(f"Shape={shape}, Causal={causal}, Dtype={dtype} Passed (SA fwd/bwd).") | |
| def _test_jvp(Z, H, SEQ_LEN, HEAD_DIM, causal, dtype=torch.float16): | |
| torch.manual_seed(20) | |
| q = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| k = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| v = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| tq = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5) | |
| tk = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5) | |
| tv = torch.empty((Z, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5) | |
| sm_scale = 0.5 | |
| def naive_attention(q, k, v): | |
| # reference implementation | |
| M = torch.tril(torch.ones((SEQ_LEN, SEQ_LEN), device=DEVICE)) | |
| p = torch.matmul(q, k.transpose(2, 3)) * sm_scale | |
| if causal: | |
| p[:, :, M == 0] = float("-inf") | |
| p = torch.softmax(p.float(), dim=-1).to(dtype) | |
| ref_out = torch.matmul(p, v) | |
| return ref_out | |
| _, ref_tout = torch.func.jvp(naive_attention, (q, k, v), (tq, tk, tv)) | |
| # triton implementation | |
| tri_tout = attention(q, k, v, tq, tk, tv, causal, sm_scale)[1].to(dtype) | |
| # compare | |
| torch.testing.assert_close(ref_tout, tri_tout, atol=1e-2, rtol=1e-2) | |
| def test_jvp(): | |
| for shape in [(1, 2, 1024, 64), (1, 2, 999, 64)]: | |
| for causal in [True, False]: | |
| for dtype in [torch.float16, torch.bfloat16]: | |
| _test_jvp(*shape, causal, dtype) | |
| print(f"Shape={shape}, Causal={causal}, Dtype={dtype} Passed (SA JVP).") | |
| BATCH, N_HEADS, HEAD_DIM = 4, 32, 64 | |
| # vary seq length for fixed head and batch=4 | |
| configs = [] | |
| for mode in ["fwd", "bwd"]: | |
| for causal in [True, False]: | |
| if mode == "bwd" and not causal: | |
| continue | |
| configs.append( | |
| triton.testing.Benchmark( | |
| x_names=["SEQ_LEN"], | |
| x_vals=[2**i for i in range(10, 15)], | |
| line_arg="provider", | |
| line_vals=["triton-fp16", "flash"], | |
| line_names=["Triton [FP16]", "FlashAttn-2"], | |
| styles=[("red", "-"), ("blue", "-"), ("green", "-")], | |
| ylabel="TFLOPS", | |
| plot_name=f"fused-attention-batch{BATCH}-head{N_HEADS}-d{HEAD_DIM}-{mode}-causal={causal}", | |
| args={ | |
| "H": N_HEADS, | |
| "BATCH": BATCH, | |
| "HEAD_DIM": HEAD_DIM, | |
| "mode": mode, | |
| "causal": causal, | |
| }, | |
| ) | |
| ) | |
| def bench_flash_attention(BATCH, H, SEQ_LEN, HEAD_DIM, causal, mode, provider, device=DEVICE): | |
| assert mode in ["fwd", "bwd"] | |
| dtype = torch.float16 | |
| if "triton" in provider: | |
| q = torch.randn((BATCH, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=device, requires_grad=True) | |
| k = torch.randn((BATCH, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=device, requires_grad=True) | |
| v = torch.randn((BATCH, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=device, requires_grad=True) | |
| tq = torch.zeros_like(q) | |
| tk = torch.zeros_like(k) | |
| tv = torch.zeros_like(v) | |
| sm_scale = 1.3 | |
| fn = lambda: attention(q, k, v, tq, tk, tv, causal, sm_scale)[0] | |
| if mode == "bwd": | |
| o = fn() | |
| do = torch.randn_like(o) | |
| fn = lambda: o.backward(do, retain_graph=True) | |
| ms = triton.testing.do_bench(fn) | |
| if provider == "flash": | |
| from flash_attn.flash_attn_interface import flash_attn_func | |
| q = torch.randn((BATCH, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=device, requires_grad=True) | |
| k = torch.randn((BATCH, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=device, requires_grad=True) | |
| v = torch.randn((BATCH, H, SEQ_LEN, HEAD_DIM), dtype=dtype, device=device, requires_grad=True) | |
| fn = lambda: flash_attn_func(q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), causal=causal) | |
| if mode == "bwd": | |
| o = fn() | |
| do = torch.randn_like(o) | |
| fn = lambda: o.backward(do, retain_graph=True) | |
| ms = triton.testing.do_bench(fn) | |
| # there are 2 matmuls in the forward pass | |
| flops_per_matmul = 2.0 * BATCH * H * SEQ_LEN * SEQ_LEN * HEAD_DIM | |
| total_flops = 2 * flops_per_matmul | |
| if causal: | |
| total_flops *= 0.5 | |
| if mode == "bwd": | |
| # there are 5 matmuls in the backward pass | |
| total_flops *= 2.5 # 2.0(bwd) + 0.5(recompute) | |
| elif "triton" in provider: | |
| # there are 6 matmuls in the forward pass with JVP computation | |
| total_flops *= 3 | |
| return total_flops * 1e-12 / (ms * 1e-3) | |
| def _test_fwd_bwd_ca(Z, H, SEQ_LEN_Q, SEQ_LEN_KV, HEAD_DIM, dtype=torch.float16): | |
| torch.manual_seed(20) | |
| q = torch.empty((Z, H, SEQ_LEN_Q, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| k = torch.empty((Z, H, SEQ_LEN_KV, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| v = torch.empty((Z, H, SEQ_LEN_KV, HEAD_DIM), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| tq = torch.zeros_like(q) | |
| tk = torch.zeros_like(k) | |
| tv = torch.zeros_like(v) | |
| sm_scale = 0.5 | |
| dout = torch.randn((Z, H, SEQ_LEN_Q, HEAD_DIM), device=q.device, dtype=q.dtype) | |
| # reference implementation | |
| p = torch.matmul(q, k.transpose(2, 3)) * sm_scale | |
| p = torch.softmax(p.float(), dim=-1).to(dtype) | |
| ref_out = torch.matmul(p, v) | |
| ref_out.backward(dout) | |
| ref_dv, v.grad = v.grad.clone(), None | |
| ref_dk, k.grad = k.grad.clone(), None | |
| ref_dq, q.grad = q.grad.clone(), None | |
| # triton implementation | |
| tri_out = attention(q, k, v, tq, tk, tv, False, sm_scale)[0].to(dtype) | |
| tri_out.backward(dout) | |
| tri_dv, v.grad = v.grad.clone(), None | |
| tri_dk, k.grad = k.grad.clone(), None | |
| tri_dq, q.grad = q.grad.clone(), None | |
| # compare | |
| atol = 2e-2 if dtype == torch.bfloat16 else 1e-2 | |
| rtol = 2e-2 if dtype == torch.bfloat16 else 0 | |
| torch.testing.assert_close(ref_out, tri_out, atol=1e-2, rtol=0) | |
| torch.testing.assert_close(ref_dq, tri_dq, atol=atol, rtol=rtol / 2) | |
| torch.testing.assert_close(ref_dk, tri_dk, atol=atol, rtol=rtol / 2) | |
| torch.testing.assert_close(ref_dv, tri_dv, atol=atol, rtol=rtol) | |
| def test_fwd_bwd_ca(): | |
| for shape in [(1, 2, 256, 1024, 128), (1, 2, 1024, 256, 128), (1, 2, 1024, 512, 64), (1, 2, 1000, 515, 64)]: | |
| for dtype in [torch.float16, torch.bfloat16]: | |
| _test_fwd_bwd_ca(*shape, dtype) | |
| print(f"Shape={shape}, Dtype={dtype} Passed (CA fwd/bwd with the same headdim).") | |
| def _test_jvp_ca(Z, H, SEQ_LEN_Q, SEQ_LEN_KV, HEAD_DIM_QK, HEAD_DIM_V, dtype=torch.float16): | |
| torch.manual_seed(20) | |
| q = torch.empty((Z, H, SEQ_LEN_Q, HEAD_DIM_QK), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| k = torch.empty((Z, H, SEQ_LEN_KV, HEAD_DIM_QK), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| v = torch.empty((Z, H, SEQ_LEN_KV, HEAD_DIM_V), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5).requires_grad_() | |
| tq = torch.empty((Z, H, SEQ_LEN_Q, HEAD_DIM_QK), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5) | |
| tk = torch.empty((Z, H, SEQ_LEN_KV, HEAD_DIM_QK), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5) | |
| tv = torch.empty((Z, H, SEQ_LEN_KV, HEAD_DIM_V), dtype=dtype, device=DEVICE).normal_(mean=0.0, std=0.5) | |
| sm_scale = 0.5 | |
| def naive_attention(q, k, v): | |
| # reference implementation | |
| p = torch.matmul(q, k.transpose(2, 3)) * sm_scale | |
| p = torch.softmax(p.float(), dim=-1).to(dtype) | |
| ref_out = torch.matmul(p, v) | |
| return ref_out | |
| ref_out, ref_tout = torch.func.jvp(naive_attention, (q, k, v), (tq, tk, tv)) | |
| # triton implementation | |
| tri_out, tri_tout = attention(q, k, v, tq, tk, tv, False, sm_scale) | |
| # compare | |
| atol = 2e-2 if dtype == torch.bfloat16 else 1e-2 | |
| torch.testing.assert_close(ref_out, tri_out, atol=1e-2, rtol=0) | |
| torch.testing.assert_close(ref_tout, tri_tout, atol=atol, rtol=1e-2) | |
| def test_jvp_ca(): | |
| for shape in [ | |
| (1, 2, 256, 1024, 64, 128), | |
| (1, 2, 1000, 15, 128, 32), | |
| (1, 2, 512, 512, 16, 32), | |
| (1, 2, 515, 999, 16, 32), | |
| ]: | |
| for dtype in [torch.float16, torch.bfloat16]: | |
| _test_jvp_ca(*shape, dtype) | |
| print(f"Shape={shape}, Dtype={dtype} Passed (CA fwd/JVP with different headdim).") | |
| if __name__ == "__main__": | |
| test_fwd_bwd() | |
| test_jvp() | |
| # only works on post-Ampere GPUs right now | |
| bench_flash_attention.run(save_path=".", print_data=True) | |
| test_fwd_bwd_ca() | |
| test_jvp_ca() |