Files
vllm-omni/docs/design/feature/skip_softmax.md
T

90 lines
4.2 KiB
Markdown
Raw Normal View History

# Skip-Softmax
Skip-Softmax is the sparse-attention mode of the `TRTLLM_ATTN` backend. Usage — the config keys
and how to pick an operating point — is in
[TRTLLM Attention](../../user_guide/diffusion/attention_backends/trtllm.md#skip-softmax).
The shared selector contract is documented in
[Diffusion Attention Backend Selection](attention_backend_selection.md). This
page explains the algorithm.
## Motivation
In a long attention row (a video DiT can have tens of thousands of keys), the softmax weight
concentrates on a small fraction of the keys; the rest receive near-zero weight and barely move the
output. Computing softmax and the value-weighted sum over those keys is wasted work. Skip-Softmax
detects, per block of keys, when a block cannot matter and skips its softmax and its value
multiply.
It is approximate: a skipped block still carries a small non-zero contribution, so the mode is
opt-in and off by default.
## The online-softmax pass
Attention is computed in a single streaming pass over the keys, in tiles of 128. Per query row the
kernel maintains three running values:
- `m` — the largest score seen so far,
- `l` — the running denominator `Σ exp(sⱼ − m)`,
- `O` — the running numerator `Σ exp(sⱼ − m)·vⱼ`,
and returns `O / l` at the end. For each key tile it computes the tile's scores `Q · K_jᵀ · scale`,
updates `m`, and accumulates that tile's contribution into `l` and `O`. Rescaling `l` and `O` when
`m` grows keeps the single pass numerically exact.
## The skip test
Once a tile's scores are known, its largest score `tile_max` is compared against the running
maximum:
```text
if exp(tile_max − running_max) < threshold:
skip this tile # do not compute its softmax weights or its P·V accumulation
```
`exp(tile_max − running_max)` is an upper bound on the softmax weight any key in the tile can
receive: if even the tile's best key is far below the current maximum, every key in the tile is
negligible, and both the softmax (the exponentials) and the `P·V` accumulation for that tile can be
dropped. The tile's contribution to `l` and `O` is simply skipped.
## What this bounds
Two properties of the test shape the achievable speedup:
- **`Q · K_jᵀ` always runs.** The test needs `tile_max`, which comes from the tile's scores, so the
score matmul is never skipped — only the softmax and the `P·V` accumulation are. The score matmul
and the value matmul are comparable in cost, so skipping every eligible tile removes at most
roughly half the attention work; the kernel-level speedup is bounded well under 2×, not unbounded.
- **The decision is per tile, not per key.** A tile is skipped only when *all* of its keys are
collectively negligible; a single important key keeps the whole tile. How many tiles actually
qualify depends on the data and rounds down to tile granularity, so `target_sparsity` selects an
operating point on a calibrated curve — it is not a promise that a fixed fraction of tiles is
skipped.
## The threshold
The per-tile threshold is normalized by sequence length:
```text
threshold = factor / seqlen
```
`factor` comes from one of two sources:
- **Calibrated** — `factor = a · exp(b · target_sparsity)`, where `a` and `b` are fit per model (and
per expert, for a multi-expert model) so that a given `target_sparsity` lands near that fraction of
skipped tiles on the calibration data.
- **Direct** — `factor = skip_softmax.threshold · seqlen`, the calibration-free path; the user sets
the threshold themselves.
## Timestep gating
`disabled_until_timestep = D` keeps the mode off during the early, high-noise denoise steps and
turns it on once the normalized timestep `t` drops to `t ≤ D` (`t` runs `1.0` → `0.0` over the
schedule). The early steps set the global structure of the output and their errors propagate through
every later step, so keeping them dense costs a few skipped-tile opportunities but protects fidelity.
`t` is the scheduler's own timestep divided by `num_train_timesteps`, published by the pipeline via
`DenoiseProgressMixin.record_denoise_step`. It is deliberately not derived from the step index,
because schedulers space their steps non-uniformly. A pipeline that does not publish a timestep
stays dense when `disabled_until_timestep` is set.