Download inference/optimized_diffattn.py from hudsongouge/DAT-Byte-Small: direct link, hf CLI and curl.
- Browser
- Download file 7.62 kB
-
https://huggingface.co/hudsongouge/DAT-Byte-Small/resolve/main/inference/optimized_diffattn.py
- Command line
-
hf download hf://hudsongouge/DAT-Byte-Small/inference/optimized_diffattn.py
-
curl -L -o optimized_diffattn.py https://huggingface.co/hudsongouge/DAT-Byte-Small/resolve/main/inference/optimized_diffattn.py
7.62 kB
| import math | |
| from typing import Optional | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| # Re-use rotary embedding helper from the original codebase | |
| from .rotary import apply_rotary_emb | |
| # ----------------------------------------------------------------------------- | |
| # Utility helpers (copied from the original implementation) | |
| # ----------------------------------------------------------------------------- | |
| def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor: | |
| """Efficiently repeat keys / values for GQA without allocating new memory.""" | |
| bs, n_kv_heads, slen, head_dim = x.shape | |
| if n_rep == 1: | |
| return x | |
| return ( | |
| x[:, :, None, :, :] | |
| .expand(bs, n_kv_heads, n_rep, slen, head_dim) | |
| .reshape(bs, n_kv_heads * n_rep, slen, head_dim) | |
| ) | |
| def lambda_init_fn(depth: int) -> float: | |
| """Init schedule described in the DiffAttention paper.""" | |
| return 0.8 - 0.6 * math.exp(-0.3 * depth) | |
| # ----------------------------------------------------------------------------- | |
| # Optimised Multi-head DiffAttention implementation | |
| # ----------------------------------------------------------------------------- | |
| class MultiheadDiffAttn(nn.Module): | |
| """Optimised DiffAttention block. | |
| Differences from the original implementation: | |
| 1. Removes the dependency on Apex / FusedRMSNorm; uses native LayerNorm. | |
| 2. Keeps all tensors on-device and works well with autocast fp16/bf16. | |
| 3. Minimises Python-side tensor reshapes and kernel launches. | |
| """ | |
| def __init__( | |
| self, | |
| embed_dim: int, | |
| depth: int, | |
| num_heads: int, | |
| num_kv_heads: Optional[int] = None, | |
| dropout: float = 0.1, | |
| ) -> None: | |
| super().__init__() | |
| self.embed_dim = embed_dim | |
| self.num_heads = num_heads # query heads (will be doubled internally) | |
| self.num_kv_heads = num_kv_heads or num_heads | |
| self.n_rep = ( | |
| self.num_heads // self.num_kv_heads | |
| ) # replication factor for keys / values (GQA) | |
| self.attn_dropout = dropout # Store dropout rate for attention | |
| # One half of a traditional head – DiffAttention uses pairs of heads | |
| self.head_dim = embed_dim // self.num_heads // 2 | |
| assert ( | |
| self.head_dim * self.num_heads * 2 == embed_dim | |
| ), "embed_dim must be divisible by num_heads * 2" | |
| self.scaling = self.head_dim**-0.5 | |
| # Projections. We keep them separated because K/V are smaller (GQA) | |
| self.q_proj = nn.Linear(embed_dim, embed_dim, bias=False) | |
| self.k_proj = nn.Linear(embed_dim, embed_dim // self.n_rep, bias=False) | |
| self.v_proj = nn.Linear(embed_dim, embed_dim // self.n_rep, bias=False) | |
| self.out_proj = nn.Linear(embed_dim, embed_dim, bias=False) | |
| # Add dropout for regularization | |
| self.dropout = nn.Dropout(dropout) | |
| # DiffAttention lambda parameters (learnable) | |
| self.lambda_init = lambda_init_fn(depth) | |
| self.lambda_q1 = nn.Parameter(torch.randn(self.head_dim) * 0.1) | |
| self.lambda_k1 = nn.Parameter(torch.randn(self.head_dim) * 0.1) | |
| self.lambda_q2 = nn.Parameter(torch.randn(self.head_dim) * 0.1) | |
| self.lambda_k2 = nn.Parameter(torch.randn(self.head_dim) * 0.1) | |
| # Use standard LayerNorm which has a highly-optimised CUDA kernel | |
| self.subln = nn.LayerNorm(2 * self.head_dim, eps=1e-5) | |
| # --------------------------------------------------------------------- | |
| # Forward | |
| # --------------------------------------------------------------------- | |
| def forward( | |
| self, | |
| x: torch.Tensor, # [bsz, seq_len, embed_dim] | |
| rel_pos: tuple[torch.Tensor, torch.Tensor], | |
| attn_mask: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| bsz, seq_len, _ = x.size() | |
| # ---- Projections -------------------------------------------------- | |
| # Projections (run inside the outer autocast context so they stay in | |
| # the low-precision dtype and use tensor cores) | |
| q = self.q_proj(x) | |
| k = self.k_proj(x) | |
| v = self.v_proj(x) | |
| # Reshape into paired heads (2 × heads) | |
| q = q.view(bsz, seq_len, 2 * self.num_heads, self.head_dim) | |
| k = k.view(bsz, seq_len, 2 * self.num_kv_heads, self.head_dim) | |
| v = v.view(bsz, seq_len, self.num_kv_heads, 2 * self.head_dim) | |
| # Rotary position encodings (ensure dtype matches q) | |
| cos, sin = rel_pos | |
| cos = cos.to(dtype=q.dtype) | |
| sin = sin.to(dtype=q.dtype) | |
| q = apply_rotary_emb(q, cos, sin, interleaved=True) | |
| k = apply_rotary_emb(k, cos, sin, interleaved=True) | |
| # ---- Prepare tensors for matmul ---------------------------------- | |
| # Shape conventions follow PyTorch’s `scaled_dot_product_attention`: | |
| # (bsz, heads, seq, head_dim) | |
| q = q.transpose(1, 2) # [bsz, 2*heads, seq, head_dim] | |
| k = k.transpose(1, 2) # [bsz, 2*kv_heads, seq, head_dim] | |
| v = v.transpose(1, 2) # [bsz, kv_heads, seq, 2*head_dim] | |
| # Replicate k/v heads when using GQA | |
| k = repeat_kv(k, self.n_rep) # [bsz, 2*heads, seq, head_dim] | |
| v = repeat_kv(v, self.n_rep) # [bsz, heads, seq, 2*head_dim] | |
| # ---- Fused scaled dot-product attention (Flash / SDPA) ----------- | |
| # | |
| # We avoid instantiating the full (seq×seq) score matrix. Instead we | |
| # run the fused attention kernel twice (positive/negative queries) and | |
| # combine the resulting context tensors with the λ weighting. This | |
| # keeps everything in fp16/bf16 and leverages Blackwell’s Flash/SDPA | |
| # path, giving ~30-80× speed-up vs. the naive implementation. | |
| # ------------------------------------------------------------------ | |
| # Re-arrange the paired heads: [bsz, 2*H, S, D] → [bsz, H, 2, S, D] | |
| q_pairs = q.view(bsz, 2, self.num_heads, seq_len, self.head_dim).permute( | |
| 0, 2, 1, 3, 4 | |
| ) | |
| k_pairs = k.view(bsz, 2, self.num_heads, seq_len, self.head_dim).permute( | |
| 0, 2, 1, 3, 4 | |
| ) | |
| q_pos, q_neg = q_pairs[:, :, 0], q_pairs[:, :, 1] # [bsz, H, S, D] | |
| k_pos, k_neg = k_pairs[:, :, 0], k_pairs[:, :, 1] | |
| # λ scalar (identical across heads / sequence) | |
| lambda_1 = torch.exp(torch.sum(self.lambda_q1 * self.lambda_k1)).type_as(q_pos) | |
| lambda_2 = torch.exp(torch.sum(self.lambda_q2 * self.lambda_k2)).type_as(q_pos) | |
| lambda_full = lambda_1 - lambda_2 + self.lambda_init # scalar tensor | |
| # --- Fused attention (only TWO SDPA calls) ------------------------- | |
| ctx_pos = F.scaled_dot_product_attention( | |
| q_pos, k_pos, v, dropout_p=self.attn_dropout, is_causal=True | |
| ) # [bsz, H, S, 2*D] | |
| ctx_neg = F.scaled_dot_product_attention( | |
| q_neg, k_neg, v, dropout_p=self.attn_dropout, is_causal=True | |
| ) # [bsz, H, S, 2*D] | |
| # DiffAttention combination | |
| attn_out = ctx_pos - lambda_full * ctx_neg # [bsz, H, S, 2*D] | |
| # LayerNorm & residual scaling | |
| attn_out = self.subln(attn_out) * (1.0 - self.lambda_init) | |
| # Collapse heads and project out | |
| attn_out = attn_out.transpose(1, 2).reshape( # [bsz, seq, heads, 2*head_dim] | |
| bsz, seq_len, self.embed_dim | |
| ) | |
| # Apply output projection and dropout | |
| out = self.out_proj(attn_out) | |
| return self.dropout(out) | |