| """ldsa — fused Local Dense Synthesizer Attention window op as a noarch Triton kernel. |
| |
| Given synthesized per-position window logits `a_logits [B, H, W, T]` and values |
| `v [B, H, Dh, T]` with a window `(left, right)`, `W = left + right + 1`: |
| |
| a[b,h,k,t] = softmax over k of a_logits[b,h,:,t] # softmax across the window |
| out[b,h,d,t] = sum_k a[b,h,k,t] * v[b,h,d, t-left+k] # weighted sum; zero outside [0,T) |
| |
| i.e. local attention whose weights are SYNTHESIZED per frame (an MLP over the input) rather |
| than from query-key dot products (Synthesizer, arXiv 2005.00743). The kernel fuses the softmax |
| + windowed weighted-sum into one launch (fp32 accumulation); it does NOT include the weight/ |
| value/output projections — the caller supplies `a_logits` and `v`. |
| |
| from kernels import get_kernel |
| k = get_kernel("futo-org/ldsa", version=1) |
| out = k.ldsa_local_attention(a_logits, v, left, right) # [B,H,Dh,T]; contiguous, any float |
| |
| Inference kernel (no backward): the Triton path runs only on CUDA under `torch.no_grad()`; |
| grad-enabled / CPU calls take the autograd-safe `eager_ldsa` reference (also exported). |
| |
| Reference: M. Xu, S. Li, X.-L. Zhang, "Transformer-based End-to-End Speech Recognition with |
| Local Dense Synthesizer Attention," ICASSP 2021 (arXiv:2010.12155; |
| github.com/mlxu995/multihead-LDSA). Differences from that reference are in the model card. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import torch |
| import torch.nn.functional as F |
|
|
| |
| |
| |
| |
| import triton |
| import triton.language as tl |
|
|
|
|
| def eager_ldsa(a_logits: torch.Tensor, v: torch.Tensor, left: int, right: int) -> torch.Tensor: |
| """Autograd-safe reference — bit-matches LocalSynthAttention's softmax+pad+window loop. |
| a_logits [B,H,W,T], v [B,H,Dh,T] -> out [B,H,Dh,T].""" |
| a = a_logits.softmax(dim=2) |
| t = v.shape[3] |
| vp = F.pad(v, (left, right)) |
| out = a[:, :, 0:1, :] * vp[:, :, :, 0:t] |
| for k in range(1, a.shape[2]): |
| out = out + a[:, :, k : k + 1, :] * vp[:, :, :, k : k + t] |
| return out |
|
|
|
|
| def _configs(): |
| return [triton.Config({"BLOCK_T": bt}, num_warps=nw) for bt in (64, 128, 256) for nw in (4, 8)] |
|
|
|
|
| @triton.autotune(configs=_configs(), key=["T", "Dh", "W"]) |
| @triton.jit |
| def _ldsa_kernel( |
| a_ptr, |
| v_ptr, |
| out_ptr, |
| T, |
| Dh, |
| left, |
| stride_a_bh, |
| stride_v_bh, |
| W: tl.constexpr, |
| BLOCK_T: tl.constexpr, |
| BLOCK_DH: tl.constexpr, |
| BLOCK_W: tl.constexpr, |
| ): |
| bh = tl.program_id(0) |
| offs_t = tl.program_id(1) * BLOCK_T + tl.arange(0, BLOCK_T) |
| mt = offs_t < T |
| a_base = a_ptr + bh * stride_a_bh |
|
|
| |
| offs_w = tl.arange(0, BLOCK_W) |
| a_tile = tl.load( |
| a_base + offs_w[:, None] * T + offs_t[None, :], |
| mask=(offs_w < W)[:, None] & mt[None, :], |
| other=float("-inf"), |
| ).to(tl.float32) |
| m = tl.max(a_tile, axis=0) |
| z = tl.sum(tl.exp(a_tile - m[None, :]), axis=0) |
|
|
| |
| offs_dh = tl.arange(0, BLOCK_DH) |
| md = offs_dh < Dh |
| v_base = v_ptr + bh * stride_v_bh |
| acc = tl.zeros((BLOCK_DH, BLOCK_T), dtype=tl.float32) |
| for k in tl.static_range(W): |
| a_k = tl.load(a_base + k * T + offs_t, mask=mt, other=float("-inf")).to(tl.float32) |
| wk = tl.exp(a_k - m) / z |
| src = offs_t - left + k |
| vk = tl.load( |
| v_base + offs_dh[:, None] * T + src[None, :], |
| mask=md[:, None] & (src >= 0)[None, :] & (src < T)[None, :] & mt[None, :], |
| other=0.0, |
| ).to(tl.float32) |
| acc += wk[None, :] * vk |
| tl.store( |
| out_ptr + bh * stride_v_bh + offs_dh[:, None] * T + offs_t[None, :], |
| acc, |
| mask=md[:, None] & mt[None, :], |
| ) |
|
|
|
|
| def _triton_ldsa(a_logits: torch.Tensor, v: torch.Tensor, left: int) -> torch.Tensor: |
| b, h, w, t = a_logits.shape |
| dh = v.shape[2] |
| a_c, v_c = a_logits.contiguous(), v.contiguous() |
| out = torch.empty_like(v_c) |
| grid = lambda meta: (b * h, triton.cdiv(t, meta["BLOCK_T"])) |
| _ldsa_kernel[grid]( |
| a_c, |
| v_c, |
| out, |
| t, |
| dh, |
| left, |
| w * t, |
| dh * t, |
| W=w, |
| BLOCK_DH=triton.next_power_of_2(dh), |
| BLOCK_W=triton.next_power_of_2(w), |
| ) |
| return out |
|
|
|
|
| def ldsa_local_attention( |
| a_logits: torch.Tensor, v: torch.Tensor, left: int, right: int |
| ) -> torch.Tensor: |
| """Fused LDSA window op; Triton on CUDA under no_grad, else the autograd-safe eager |
| reference (training, CPU). a_logits [B,H,W,T], v [B,H,Dh,T] -> [B,H,Dh,T].""" |
| if v.is_cuda and not torch.is_grad_enabled(): |
| return _triton_ldsa(a_logits, v, left) |
| return eager_ldsa(a_logits, v, left, right) |
|
|
|
|
| __all__ = ["ldsa_local_attention", "eager_ldsa"] |
|
|