Set transformer benchmark: ISAB vs DeepSet, MLA and full attention¶
Compares equivariant set encoders on small synthetic set-learning problems and
checks the exact invariances InducedSelfAttention is supposed to satisfy.
All numbers below were produced on Apple Silicon CPU (JAX 0.9.2, float32).
JAX_PLATFORMS=cpu python -m docs.scripts.benchmark_set_transformers --steps 5000 \
--output docs/benchmarks/set_transformers.json
Measurement settings¶
Budget per arm and task: 5000 training steps, 3 seeds, batch 64, eval batch 512, Adam 3e-3 with global-norm clip 5. Models are 2 layers wide 64, four heads, ISAB/MLA arms use 32 inducing points, the in-context task uses 48 context + 16 query tokens (N = 64), both set tasks use N = 64. Runtime scaling uses batch four, width 64, two layers and 64 inducing points at N = 64 / 256 / 1024 / 2048.
The arms:
| Arm | Encoder |
|---|---|
deepset |
Permutation-invariant DeepSet pooling broadcast back to every token |
isab |
InducedSelfAttention, no query scaling |
isab_per_head |
ISAB with PerHeadQueryScale on both MABs |
isab_ssmax |
ISAB with SSMaxQueryScale on both MABs |
isab_qassmax |
ISAB with QASSMaxQueryScale on both MABs |
mla |
Pre-norm block with MultiHeadLatentAttention (absorbed, shared KV latent) |
sab |
Full self-attention (Transformer), quadratic reference |
ISAB blocks use zero_init=True (the default): each MAB's attention output
projection and last feedforward layer start at zero, so a block begins as the
identity. They also share one norm between each MAB's query and key/value.
ISAB blocks run two MAB stages each, so their parameter counts are roughly
double the single-attention arms (205k vs 100k at this width).
Invariance audit¶
Checked on one fixed model and input; these are exactness checks, not trainable metrics.
| Check | Max error / status |
|---|---|
Permutation equivariance f(P x) - P f(x) |
9.54e-07 |
Perturbing KVLenMask-masked padding rows |
0.0 |
Unpadded N=6 vs padded + KVLenMask (SSMax on both MABs) |
4.77e-07 |
Perturbing query rows with separate x_kv context |
0.0 |
BatchedLowRankBias token merging, dense kernel |
7.15e-07 |
BatchedLowRankBias token merging, flex_attention |
5.96e-07 |
bfloat16 preserved across both MABs (norm_cls=None) |
True |
The padding row is the regression guard for the kv_len bug: MAB 2 attends
over the inducing bank, not over kv_len keys, so query scaling must resolve
kv_len from the mask for MAB 1 only.
Task quality (mean test MSE over 3 seeds)¶
Lower is better. Baselines are computed per seed on the eval batch.
cluster_denoise (N=64, identity 0.090, oracle 0.003)¶
| Arm | Test MSE | Params | Train step ms |
|---|---|---|---|
deepset |
0.0086 ± 0.0004 | 108,740 | 13.4 |
isab |
0.0056 ± 0.0008 | 204,612 | 28.8 |
isab_per_head |
0.0054 ± 0.0007 | 204,628 | 30.7 |
isab_ssmax |
0.0067 ± 0.0039 | 204,628 | 40.9 |
isab_qassmax |
0.0057 ± 0.0007 | 230,276 | 45.5 |
mla |
0.0070 ± 0.0011 | 112,652 | 30.1 |
sab |
0.0060 ± 0.0008 | 100,548 | 22.0 |
padded_outlier_mean (N in [16, 64], naive mean 0.040, oracle 0.002)¶
| Arm | Test MSE | Params | Train step ms |
|---|---|---|---|
deepset |
0.0060 ± 0.0029 | 108,740 | 16.5 |
isab |
0.0040 ± 0.0002 | 204,612 | 34.0 |
isab_per_head |
0.0043 ± 0.0007 | 204,628 | 31.0 |
isab_ssmax |
0.0036 ± 0.0004 | 204,628 | 34.6 |
isab_qassmax |
0.0113 ± 0.0011 | 230,276 | 41.1 |
mla |
0.0049 ± 0.0007 | 112,652 | 27.0 |
sab |
0.0046 ± 0.0005 | 100,548 | 21.2 |
in_context_regression (48 context + 16 query, zero prediction 4.6)¶
| Arm | Test MSE | Params | Train step ms |
|---|---|---|---|
deepset |
0.0866 ± 0.0191 | 108,609 | 8.1 |
isab |
0.0463 ± 0.0188 | 204,481 | 14.5 |
isab_per_head |
0.0358 ± 0.0090 | 204,497 | 15.3 |
isab_ssmax |
0.0293 ± 0.0070 | 204,497 | 14.7 |
isab_qassmax |
0.0278 ± 0.0063 | 230,145 | 21.8 |
mla |
0.0485 ± 0.0023 | 112,521 | 13.0 |
sab |
0.0582 ± 0.0162 | 100,417 | 10.9 |
Observations:
- At 5000 steps the best ISAB variant beats
sab,mlaanddeepseton all three tasks, while payingO(N M)instead ofO(N²). - On
in_context_regressionthe gap is largest: routing context through the inducing bank is an inductive bias for this task. - Differences between ISAB variants are often within the seed spread (3 seeds), and the best query scaling varies by task.
- Zero init was compared against the default init at the same budget (not committed): it was neutral overall, winning some arm/task pairs and losing others. At 1500 steps it was clearly slower to converge.
Runtime scaling (warm ms, batch 4)¶
| Arm | N | Forward | Backward |
|---|---|---|---|
deepset |
64 | 0.40 | 0.95 |
deepset |
256 | 0.68 | 2.74 |
deepset |
1024 | 3.15 | 8.33 |
deepset |
2048 | 5.50 | 15.18 |
isab |
64 | 1.07 | 3.02 |
isab |
256 | 1.98 | 5.55 |
isab |
1024 | 5.59 | 16.65 |
isab |
2048 | 9.97 | 34.00 |
isab_ssmax |
64 | 0.98 | 2.87 |
isab_ssmax |
256 | 2.25 | 5.54 |
isab_ssmax |
1024 | 5.85 | 17.92 |
isab_ssmax |
2048 | 10.98 | 36.85 |
mla |
64 | 0.50 | 1.80 |
mla |
256 | 2.19 | 6.62 |
mla |
1024 | 35.02 | 110.00 |
mla |
2048 | 124.47 | 513.38 |
sab |
64 | 0.63 | 1.68 |
sab |
256 | 1.77 | 7.80 |
sab |
1024 | 19.27 | 94.10 |
sab |
2048 | 107.64 | 448.90 |
ISAB grows linearly with N at fixed M=64 (about 7x cheaper forward and
9x cheaper backward than sab at N=2048) and its query scaling adds no
measurable overhead. mla still pays O(N²) attention over the shared
latent and is the slowest quadratic arm at large N. CPU timings do not
establish GPU performance.
Re-run with a smaller budget¶
JAX_PLATFORMS=cpu python -m docs.scripts.benchmark_set_transformers \
--steps 50 --seeds 1 --batch-size 16 --set-size 32 --num-inducing 8 \
--model-dim 32 --tokens 32 128 --output docs/benchmarks/set_smoke.json
Write smoke results to a distinct filename so the published measurements above stay intact. See the benchmark index for the general caveats.