Repository navigation
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1e4ce28. Configure here.
|
Thanks. All three addressed in fd3bb0d: the warning is gated on |
|
Thank you for starting this contribution! I will also run a few test on my end.
could you please share the numbers for this experiment? in particular which "short-Q" ranges did you check? |
|
Good question, I see an issue in what I measured. The 2–9% figure was wrong for this case: it came from runs at batch ≥ 10 or with Q above FA3's split threshold, not the batch-1, few-rows shape of cached prediction. Measured properly, batch 1, fp16, MQA 8/1, head_dim 64, H100, ms per call:
End-to-end, FA4 returns the LSE, so split-KV can be done outside the kernel; a prototype recovers most of the gap (0.43 ms vs FA3's 0.39 at q=16, KV=1M). I'll add that to the backend and update the PR. |
|
Done: split-KV outside the kernel for short-Q / long-KV calls on sm_90 (bbb5860, refined in 54d99f3). Cached prediction,
The remaining gap to FA3 is FA4's per-call dispatch overhead. Matches SDPA to 6e-5; the path is inference-only, and under autograd FA4 runs unsplit with a backward test against SDPA. One other change since: the backend now dispatches only on sm_90 and sm_100, the architectures it was measured on. sm_110 and consumer Blackwell (sm_12x) stay on SDPA until someone measures them. |
|
Hi @tolleybot thanks foir your changes. I will try to land #1312 first. Then I would rebenchmark and potentially adjust the splitting. So it will be a bit of time, but hope that is ok. |
New `shared/fa4_backend.py` mirroring `fa3_backend.py`: lazy cached import of `flash_attn.cute.flash_attn_func`, ROCm guard, per-architecture head-dim gate (sm_90 8..256, sm_100/110/120 8..128, step 8), fp16/bf16 only. Registered ahead of FA3 so Hopper prefers the CuTeDSL path where both are eligible. Handles three FA4 4.0.0b30 specifics: the function returns `(out, lse)` unconditionally; split-KV is not implemented on sm_90 (and sm_12x accepts only `num_splits=1`), so the FA3 split rule is not carried over; and the kernel launches one grid entry per batch element, so batches above 65,535 are chunked. `_FA4_MIN_SEQLEN_FOR_SPEEDUP` is a placeholder inherited from FA3 pending the Blackwell half of the benchmark (TabPFN#1235). Hopper measurements show no low-end penalty. Tests: FA4 twins of the FA3 dispatch and equivalence tests, plus long-KV cross-attention and batch>65535 cases; registration test lists FA4.
- pyproject: `fa4` and `fa4-cu13` extras. FA4 has beta releases only; a pre-release floor (`>=4.0.0b30`) lets pip resolve it without --pre. - fa4_setup.md beside fa3_setup.md: install, dispatch conditions, the H100 whole-model measurements, and the FA4-vs-FA3 differences the backend handles. The seqlen threshold is documented as a placeholder pending the Blackwell half of the benchmark. - `blackwell` pytest marker. FA4 tests carry both `hopper` and `blackwell` via a `_skip_unless_fa4` helper that also gates on a supported compute capability and the package being importable. - Drop two unused constants from fa4_backend.py; ruff clean.
Deletes fa3_backend.py and fa3_setup.md; FA4 takes over Hopper. Measured on H100 (TabPFN#1235), FA4 matches or beats FA3 at every sequence length, has no short-sequence penalty against SDPA, and additionally serves the head_dim-16 feature-attention stages FA3 could not. It is also pip-installable, where FA3 needed a 30-60 minute source build. Consult order becomes (FA4, torch-MPS, MLX). FA3-specific tests are removed; their FA4 twins remain. The `hopper` marker now means "sm_90 + flash-attn-4". Stacked on the fa4-backend branch as a separate commit so it can be dropped if upstream prefers to keep FA3 alongside.
Per the maintainer decisions on TabPFN#1235 after the H100/GB200 benchmark: - No sequence-length threshold. FA4 is within noise of SDPA from n_train=100 up and ahead from ~3k on both architectures, so `is_preferred` is eligibility alone. `_FA4_MIN_SEQLEN_FOR_SPEEDUP` is gone. - bf16 is not dispatched to FA4 on compute capability >= 10, where its bf16 kernels are ~20% slower than SDPA from ~10k rows (fp16 is unaffected and is TabPFN's default). A one-time UserWarning says why. Hopper keeps bf16. Tests: is_preferred == eligibility for tiny/huge/unknown lengths; bf16 gate and single warning with a mocked compute capability; the per-arch eligibility test expects bf16 only below cc 10.
The registry consults `is_preferred` inside torch.compile'd regions and `warnings.warn` is not Dynamo-traceable, so the one-time bf16 warning now only fires when not tracing (same gate the registry uses for its logging). A fullgraph test pins this. Also: module docstring describes FA4 as it is rather than by contrast with FA3, and notes that `flash_attn_func` itself is not traceable (measured at ~4% on v3 predict() at 100k rows, FA4 still well ahead of SDPA); changelog fragments cut to one or two sentences.
… none FA4 implements split-KV on sm_100/110 only. On sm_90 a call like (1, 16, 8, 64) x (1, 1M, 1, 64), the cached-prediction shape, runs on a handful of SMs: 9.9 ms on H100 against 0.4 ms for FA3's 32-way split, and end-to-end `fit_with_cache` + predict of 16-256 rows was 38 ms (100k rows) / 90 ms (300k) with FA4, equal to SDPA, against 23-25 ms with FA3. `_split_kv_plan` decides when an unsplit launch would under-fill the GPU (few batch x q-tile work items, KV long enough for chunks >= 2k keys) and `_fa4_split_kv` folds the KV chunks into the batch dimension for a single FA4 launch, then combines the partial outputs with the per-chunk LSE FA4 returns; a non-divisible remainder runs as one extra chunk. Matches SDPA to 6e-5. Cached prediction is now 27-28 ms at both 100k and 300k rows. Tests: plan boundaries (CPU), and the long-KV equivalence test now covers q=16, a KV length not divisible by the chunk count, and batch 2.
…it, edge cases - Dispatch only on architectures this backend was measured on (sm_90 and sm_100); sm_110 and consumer Blackwell (sm_12x) stay on SDPA until measured, like Ampere. The bf16 decline is scoped to sm_100 accordingly. - The out-of-kernel split-KV path is inference-only: under autograd FA4 runs its own kernel unsplit, and a backward test checks its gradients against SDPA. - `_split_kv_plan`: no division by zero on an empty query; for batch > 1 the chunk count must divide KV exactly so the fold stays copy-free (largest divisor), since a sequence slice of a (B, S, ...) tensor is only contiguous for B == 1. - The bf16 warning fires only when dtype is the sole reason a call is declined (head-dim checks first). - `_is_bf16_slow` no longer caches on top of the cached capability lookup; the one-time warning cache is reset by an autouse fixture instead of ad-hoc clears. - Comments: the split plan is measured rather than derived from tile occupancy (splitting past nominal occupancy is still faster on H100), and the eager combine is used because FA4's fused one is not public.
Upstream PriorLabs#1312 batches equal-shape ensemble members into one forward, so the cached-predict long-KV calls now arrive at batch > 1 (batch 4 at the default n_estimators=8). The plan only split there when a divisor of seq_kv <= 32 existed, so for an n_train with no small divisor it ran unsplit: 10-22x slower than FA3 at (4, 16) x (4, 99_991..1_000_003). Uneven chunks at batch > 1 now go through FA4's varlen entry point over a zero-copy flat view of K and V; an even split within 2x of the target is still preferred because the fold is slightly cheaper. Plans under 3 splits run unsplit, since 2-way splits never beat that in the sweep.
82346b6 to
5b5bb89
Compare
|
Thanks, and no rush. Since #1312 landed I rebased onto it and re-ran the cached-prediction case at the default n_estimators=8. The batching exposed a problem in my splitting: the long-KV calls now arrive at batch 4, and at batch > 1 I only split when n_train had a divisor ≤ 32, so for an n_train like 99,991 or 300,007 FA4 ran unsplit, at SDPA speed. I've pushed a fix: uneven chunks at batch > 1 go through FA4's varlen path, and splits under 3 are skipped.
Happy for you to adjust the splitting from here as you planned. |
|
Quick update since v9.1/v9.2 landed: I rebased locally onto current main (ffd5371, including #1365) and re-ran the cached-prediction benchmark at n_estimators=8. FA4 is within ±3% of the 09-29 numbers above, the split plan is unchanged at every shape I swept, and the attention tests pass. The PR still merges cleanly as is, so I haven't pushed the rebase, but I can if you'd prefer to review on current main. |

Issue
Closes #1235.
Motivation and Context
fa3_backend.pyis Hopper-only and noted that Blackwell would need FlashAttention-4. FA4 isnow on PyPI (
flash-attn-4, CuTeDSL, weekly betas) with kernels for sm_90 and sm_100+, so onebackend can serve both. Per the discussion on #1235, this PR replaces FA3 with FA4 rather than
adding it alongside.
What changes for a user:
up, tie below) and, unlike FA3, is
pip install "tabpfn[fa4]"rather than a 30–60 minutesource build.
predict()at ≥30k rows with ≥100 features._FA3_MIN_SEQLEN_FOR_SPEEDUP = 10_000existed becauseits fixed
num_splits=32made it 3–6× slower than SDPA at 1k rows. FA4 has no such penaltyon either architecture (within noise from 100 rows, ahead from ~3k), so
is_preferrediseligibility alone.
so it also takes the head_dim-16 feature-attention stages FA3 could not. On Blackwell this is
where all the gain is: torch's SDPA picks cuDNN attention there, which already runs the
head_dim-64 ICL calls at FA4 speed.
UserWarning. FA4's bf16 forward on sm_100is 18–23% slower than SDPA from ~10k rows (fp16 is not; Hopper is fine in both). TabPFN
defaults to fp16 autocast on CUDA, so this only affects
inference_precision=torch.bfloat16.Measurements (H100 and GB200, torch 2.14.0+cu130, flash-attn-4 4.0.0b30) are in
#1235 and
summarised in
fa4_setup.md.Split-KV on Hopper (asked on the issue): FA4 implements it on sm_100/110 only, so on sm_90 a short-Q call against a long KV at batch 1, the cached-prediction shape, fills only a handful of SMs: 9.9 ms for (1, 16, 8, 64) × (1, 1M, 1, 64) against 0.4 ms for FA3's 32-way split. The backend now splits outside the kernel on such calls (
_split_kv_plan,_fa4_split_kv): KV chunks folded into the batch dimension for one launch, partial outputs combined with the LSE FA4 returns. Cached prediction (fit_with_cache, then predict 16–256 rows) on H100: 27–28 ms at 100k and 300k rows, against 23–25 ms with FA3 and 37–92 ms with SDPA or unsplit FA4.Implementation
shared/fa4_backend.py(new): same shape asfa3_backend.py. Lazy cached import offlash_attn.cute.flash_attn_funcsois_available()is one cheap check at registration;ROCm guard; per-architecture head-dim table; bf16 gate on cc ≥ 10. Three FA4 4.0.0b30
specifics it handles:
flash_attn_funcreturns(out, lse)unconditionally; split-KV issm_100/110 only (sm_12x accepts
num_splits=1only); the kernel launches one grid entry perbatch element, so batches above 65,535 are chunked.
shared/scaled_dot_product_attention.py: consult order(FA4, torch-MPS, MLX).shared/fa3_backend.py,shared/fa3_setup.md: removed.shared/fa4_setup.md: install, dispatch conditions, measured speedups on both GPUs, bf16 rule.pyproject.toml:fa4andfa4-cu13extras (flash-attn-4>=4.0.0b30; the pre-release floorlets pip resolve the beta without
--pre;[cu13]selects the CUDA 13 CuTeDSL libs). Newblackwellpytest marker;hoppernow means sm_90 + flash-attn-4.batch > 65,535,
is_preferred == eligibility, and the bf16 gate/warning with a mocked computecapability so it runs anywhere. FA3-only tests removed.
torch.compile: FA4'sflash_attn_funcis not Dynamo-traceable (FA3's was), so withenable_torch_compile=Trueeach FA4 call is a graph break insideflash_attn/cute/. Measured on v3predict()on H100: within noise at 30k rows, ~4% at 100k, with FA4 still 1.17× faster than SDPA under compile.enable_torch_compileis off by default.One thing found on the way, unrelated to this change: FA3's split-KV path crashed the process at
batch ≥ 65536 for any
num_splits > 1(CUDA grid limit), whichfa3_attn_funcused forseq_q ≤ 1024since #1168, sotest__fa3_batch_heads_above_cuda_max_gridhad been failingsince then without CI noticing. Moot once FA3 is gone.
Public API Changes
fa3_backendwas an internal module. Dispatch stays automatic and falls back to SDPA whenflash-attn-4is not installed; there is no user-facing switch. Installing FA3 no longer has anyeffect.
How Has This Been Tested?
tests/test_architectures/test_attention_backends.py,tests/test_architectures/test_shared/test_attention_backends.py,tests/test_architectures/test_attention_dispatch.py: 27 pass on an H100 (sm_90) and on aGB200 (sm_100), Python 3.11 / 3.12, torch 2.14.0+cu130, flash-attn-4 4.0.0b30.
atol=rtol=5e-3) on the six canonical shapes ×fp16/bf16, on (1, 256, 100k, 8, 1, 64), and on batch=70,000, on both architectures.
warning, fp16 preferred at every sequence length.
ruff check/ruff formatclean at the pinned 0.15.12;mypyclean on the changed files.Checklist
changelog/README.md), or "no changelog needed" label requested.