Spaces:
Running on Zero
Running on Zero
| # Copyright (c) 2026 Bytedance Ltd. and/or its affiliate | |
| # | |
| # 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. | |
| """Variable-length attention with an auto-selected backend. | |
| Backend priority, probed once at import time: | |
| 1. FlashAttention-3 (``flash_attn_interface``) -- fastest on Hopper / H100 | |
| 2. FlashAttention-2 (``flash_attn``) -- general CUDA GPUs | |
| 3. PyTorch SDPA -- always available, no extra dep | |
| All backends share the same varlen contract: ``q``/``k``/``v`` are packed as | |
| ``[total_tokens, num_heads, head_dim]`` and ``cu_seqlens_*`` give the per-sample | |
| offsets into ``total_tokens``. | |
| """ | |
| import torch | |
| import torch.nn.functional as F | |
| _BACKEND = None | |
| _flash_varlen = None | |
| def _select_backend(): | |
| global _BACKEND, _flash_varlen | |
| if _BACKEND is not None: | |
| return | |
| try: | |
| from flash_attn_interface import flash_attn_varlen_func # FA3 | |
| _flash_varlen, _BACKEND = flash_attn_varlen_func, "fa3" | |
| return | |
| except Exception: | |
| pass | |
| try: | |
| from flash_attn import flash_attn_varlen_func # FA2 | |
| _flash_varlen, _BACKEND = flash_attn_varlen_func, "fa2" | |
| return | |
| except Exception: | |
| pass | |
| _BACKEND = "sdpa" | |
| def get_attention_backend() -> str: | |
| _select_backend() | |
| return _BACKEND | |
| def _sdpa_varlen(q, k, v, cu_seqlens_q, cu_seqlens_k, causal): | |
| """Varlen attention via SDPA: run each sample's segment, then concatenate.""" | |
| cq = cu_seqlens_q.tolist() | |
| ck = cu_seqlens_k.tolist() | |
| outs = [] | |
| for i in range(len(cq) - 1): | |
| # [seq, H, D] -> [1, H, seq, D] | |
| qi = q[cq[i] : cq[i + 1]].transpose(0, 1).unsqueeze(0) | |
| ki = k[ck[i] : ck[i + 1]].transpose(0, 1).unsqueeze(0) | |
| vi = v[ck[i] : ck[i + 1]].transpose(0, 1).unsqueeze(0) | |
| oi = F.scaled_dot_product_attention(qi, ki, vi, is_causal=causal) | |
| outs.append(oi.squeeze(0).transpose(0, 1)) # back to [seq, H, D] | |
| return torch.cat(outs, dim=0) | |
| def varlen_attention( | |
| q, | |
| k, | |
| v, | |
| cu_seqlens_q, | |
| cu_seqlens_k, | |
| max_seqlen_q, | |
| max_seqlen_k, | |
| causal: bool = False, | |
| ): | |
| """Variable-length attention. Returns ``[total_q_tokens, num_heads, head_dim]``.""" | |
| _select_backend() | |
| if _BACKEND == "fa3": | |
| out = _flash_varlen( | |
| q, | |
| k, | |
| v, | |
| cu_seqlens_q=cu_seqlens_q, | |
| cu_seqlens_k=cu_seqlens_k, | |
| max_seqlen_q=int(max_seqlen_q), | |
| max_seqlen_k=int(max_seqlen_k), | |
| causal=causal, | |
| ) | |
| return out[0] if isinstance(out, tuple) else out | |
| if _BACKEND == "fa2": | |
| return _flash_varlen( | |
| q, | |
| k, | |
| v, | |
| cu_seqlens_q, | |
| cu_seqlens_k, | |
| int(max_seqlen_q), | |
| int(max_seqlen_k), | |
| causal=causal, | |
| ) | |
| return _sdpa_varlen(q, k, v, cu_seqlens_q, cu_seqlens_k, causal) | |