5.2 KiB
fastvideo/attention/ — Attention Backends
Generated: 2026-05-02
Backend registry + selector wrapping FlashAttn / SageAttn / SageAttn3 / SDPA / VSA / VMoBA / SLA / BSA.
Layout
attention/
├── __init__.py # Exports DistributedAttention, LocalAttention, get_attn_backend
├── layer.py # DistributedAttention, DistributedAttention_VSA, LocalAttention
├── selector.py # get_attn_backend (cached) + _component_attention_backend_scope
├── backends/
│ ├── abstract.py # AttentionBackend / AttentionMetadata / AttentionMetadataBuilder
│ ├── flash_attn.py # FA2/FA3
│ ├── sage_attn.py # SageAttention v1
│ ├── sage_attn3.py # SageAttention v3
│ ├── sdpa.py # torch SDPA fallback
│ ├── video_sparse_attn.py # VSA (paper: Video Sparse Attention)
│ ├── vmoba.py # Video-MoBA
│ ├── sla.py # Sliding-window (STA)
│ └── bsa_attn.py # Block-sparse
└── utils/
├── flash_attn_cute.py
└── flash_attn_no_pad.py
Where the Decision Is Made
Resolved once, at load time, and carried on the component. The environment
variable is folded into FastVideoArgs.attention_backend in __post_init__
(the parse-once adapter); PipelineComponentLoader.load_module applies that
request per component; each loader records what its component resolved onto
that component's own config as ModelConfig._resolved_attention_backend. After
load, the decision is readable from the component itself:
transformer.config._resolved_attention_backend
A loader may narrow the request for one component — the DMD teacher/critic transformers build dense — and the recorded value is what that component actually resolved, not what the run asked for globally.
A call site that already knows its component passes the decision explicitly:
get_attn_backend(..., requested=component_attention_backend(self.transformer))
requested distinguishes three cases, and the distinction matters — None is
an answer, not an absence:
| value | meaning |
|---|---|
| a backend | use it; ignore the scope and the environment |
None |
this component resolved to automatic selection; do not consult the environment behind it |
omitted (NO_REQUEST) |
the caller has no opinion; fall back to the scope, then the environment |
Nothing switches backends at runtime and nothing should: every request is an input to construction.
Selection Order
get_attn_backend() reads every selection input, then resolves via, in
precedence order:
- An explicit
requested=argument — the component's own recorded decision. - The per-component request carried by the construction scope.
- Env-var
FASTVIDEO_ATTENTION_BACKEND(seeSTR_BACKEND_ENV_VARinfastvideo/utils.py), consulted only when the caller supplied no request and no scope is active — layers built outside a loader, and direct model construction in tests. - The layer-declared
default_backend. - Per-platform automatic selection from
fastvideo/platforms/, which probes the current device's capability.
The result is cached on all of those inputs (plus component identity and
device index), so a changed request simply lands on a different cache key —
no cache_clear() is needed and none should be added.
A layer that does not declare support for the requested backend falls back, so the component-level decision is the request every layer of that component resolves against, not a promise about the kernel any one layer runs.
Never mutate FASTVIDEO_ATTENTION_BACKEND mid-process.
Transitional: how the request reaches layers
The request travels from loader to layers via
_component_attention_backend_scope, a private ContextVar. It is plumbing with
an end date, not API: layer constructors nested inside a model receive only a
bare supported_attention_backends tuple, threaded through block constructors
up to four levels deep, so they cannot yet read the decision off their own
config. Thread the request alongside that tuple, one model family at a time —
Wan, LTX-2 and Kandinsky5 first, since those carry per-role requests — and the
scope goes away when the last family lands. Do not build on it.
Adding a Backend
- Subclass
AttentionBackendinbackends/<name>.py. - Implement
AttentionMetadata+AttentionMetadataBuilderfor the new path. - Register the enum value in
fastvideo/platforms/interface.py(AttentionBackendEnum). - Wire string → class resolution in
selector.py. - Verify the new backend works with
DistributedAttention(sequence parallel) andLocalAttention(single-rank). If it cannot support SP, document the gap in the backend file's module docstring.
Anti-Patterns
- Calling
torch.nn.functional.scaled_dot_product_attentiondirectly inside a model's forward — go throughDistributedAttention/LocalAttention. - Reading
os.environ[STR_BACKEND_ENV_VAR]from arbitrary call sites. Useget_env_variable_attn_backend(). - Caching backend instances per-module. The selector cache is process-wide; do not duplicate it.