Add sequence-parallel multi-GPU inference (PyTorch and JAX) and a cuDNN flash attention option - #81
Open
weihaokong wants to merge 5 commits into
Open
Add sequence-parallel multi-GPU inference (PyTorch and JAX) and a cuDNN flash attention option#81weihaokong wants to merge 5 commits into
weihaokong wants to merge 5 commits into
Conversation
weihaokong
requested review from
abhidas,
erzel,
rajatsen91,
siriuz42 and
tamannarayan
as code owners
July 23, 2026 05:57
tabfm/src/pytorch/seqpar.py shards one ensemble member's in-context rows across the ranks of a torch.distributed process group: - Cell embedding, row interaction, FFNs and the decoder are row-independent and run locally per shard. - The column set-transformer's inducing attention combines per-rank online-softmax partials exactly across ranks via log-sum-exp weights. - ICL self-attention all-gathers each rank's projected context K/V (test rows are never keys) and runs one fused SDPA per rank. Ragged shards are supported via padded gathers plus a key mask. seqpar.predict / seqpar.predict_proba mirror TabFMRegressor.predict and TabFMClassifier.predict_proba (class-shift undo, ensemble combination) for any n_estimators. This enables contexts that exceed one device's memory: a 1M-row context runs in ~35GB/GPU across 4x H100 (a single 80GB device OOMs above ~450k rows), ~104s per member. At single-device-feasible sizes the sharded path is ~5x faster on 4 GPUs (TabArena GiveMeSomeCredit fold 0, 135k-row context: 17.8s -> 5.2s, AUC unchanged at bf16 noise level). Tests run on CPU with the gloo backend (2 simulated ranks) and compare the sharded forward against the plain single-process model forward, covering regression/classification, ragged shards, and cat_mask/d feature padding.
weihaokong
force-pushed
the
pr/pytorch-seqpar
branch
from
July 23, 2026 06:01
c31b089 to
5c4fcd7
Compare
Adds AttentionImplementation.CUDNN, selectable via load(..., col_attention_impl='cudnn', icl_attention_impl='cudnn'). The chunked FLASH path stays the default; CUDNN dispatches to jax.nn.dot_product_attention(implementation='cudnn'). TabFM's boolean masks are always key-prefix (padding) masks, so they are mapped onto cuDNN's key_value_seq_lengths instead of a dense mask, which cuDNN would otherwise require in full [B, N, T, T_src] form. Measured on 1x H100 (TabArena GiveMeSomeCredit fold 0, 135k-row context, n_estimators=1): predict_proba 634s -> 7.4s, AUC 0.86327 -> 0.87311 (now matching the PyTorch backend at 0.87292; the chunked path's bf16 accumulation was the numerical outlier).
tabfm/src/jax/seqpar.py mirrors tabfm/src/pytorch/seqpar for the JAX backend: a single process shards each ensemble member's in-context rows across all local devices with jax.shard_map (weights replicated). - Row-independent stages (cell embedding, row interaction, FFNs, decoder) run locally per shard through the stock nnx modules. - The column set-transformer's inducing attention runs an fp32 online softmax over each device's context shard, scanned in key chunks so no full-length score matrix materializes, and combines the partial results exactly across devices via log-sum-exp statistics. - ICL self-attention all-gathers each device's projected context K/V and runs the chunked memory-efficient attention locally, with padded key slots masked via an additive bias. Device blocks are padded to a common 128-multiple, so unequal shards are supported. seqpar.predict / seqpar.predict_proba mirror the estimators' prediction paths (class-shift undo, ensemble combination) for any n_estimators, with cat_mask / feature-padding support. Tests run on two simulated CPU host devices (same pattern as classifier_and_regressor_multidevice_test.py) and compare sharded predictions against the estimators' single-device paths at 1e-4 in float32, covering regression/classification, even and ragged shards, and multi-member ensembles.
Input shards are now assembled with jax.make_array_from_callback (each process contributes the slices its addressable devices need) and the output is re-replicated across devices via out_shardings, so it is readable from every process. Single-process behavior is unchanged; on a multi-host slice, initialize the runtime with jax.distributed.initialize and run the same program on every host (fit is deterministic given random_state, mirroring how the torch.distributed ranks operate in the PyTorch module). Adds a multi-process test that rehearses this execution model on CPU: two spawned processes with two simulated host devices each, joined via jax.distributed.initialize, compared against the estimator's plain single-process prediction path. Test modules pin fp32 matmuls to full precision so the comparisons also hold on GPU dev machines (XLA otherwise uses TF32 on Ampere+); on such machines the single-process tests then exercise a real multi-GPU mesh.
AttentionImplementation.SPLASH is the TPU analogue of the cuDNN option: a fused flash-attention kernel with fp32 softmax accumulation, so it avoids both the chunked FLASH path's small-matmul overhead and its bf16 accumulation noise. TabFM's key-prefix masks are expressed via splash segment ids (queries and valid keys carry segment 1, padded keys 0). The kernel is TPU-only; set_splash_interpret(True) runs it in Pallas interpret mode so the mask translation and layout handling are tested on CPU in CI against the stock JAX attention (with and without a prefix mask). Kernel performance is only measurable on real TPUs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three additions for large-context inference:
tabfm/src/pytorch/seqpar.py— sequence-parallel (row-sharded)multi-GPU inference for the PyTorch backend under
torch.distributed.tabfm/src/jax/seqpar.py— the equivalent for the JAX backend: asingle process shards the rows across all local devices with
jax.shard_map.AttentionImplementation.CUDNN— fused cuDNN flash attention optionfor the JAX backend.
Sequence-parallel inference (both backends)
TabFM reads the whole training fold as one in-context sequence, so
activations grow with the number of rows and exceed a single device's memory
for very large contexts (roughly >450k rows on an 80GB GPU). These modules
shard the rows of one ensemble member's sequence across devices with
mathematically exact attention — no approximation; outputs match the
single-device path up to floating-point summation order.
blocks and the decoder are row-independent and run locally on each shard,
reusing the stock modules (so all fp32-upcast behavior is inherited
unchanged).
softmax over each device's context shard and the partial results are
combined exactly across devices via log-sum-exp weights; only softmax
statistics cross devices, so unequal shards work natively. The JAX
version additionally scans the keys in chunks so no full-length score
matrix materializes.
are never attention keys anywhere in the model) and runs one fused SDPA
(PyTorch) or the chunked memory-efficient attention (JAX) per device,
with padded key slots masked.
seqpar.predict/seqpar.predict_probamirrorTabFMRegressor.predictand
TabFMClassifier.predict_proba(class-shift undo, ensemblecombination) for any
n_estimators, withcat_mask/ feature-paddingsupport. A runnable PyTorch example is included under
examples/.Measured (PyTorch, 4x H100): a 1M-row context runs at ~35GB/GPU, ~104s per
ensemble member — a single 80GB device cannot run it at all. At
single-device-feasible sizes the sharded path is faster (TabArena
GiveMeSomeCredit fold 0, 135k-row context,
n_estimators=1: 17.8s -> 5.2swith AUC unchanged at bf16 noise level). Fold 0 of all 13 TabArena
regression tasks with the 32-member ensemble preset and sharded predict
reproduces the published TabFM-Ensemble reference RMSEs (
results/) with amedian deviation of 0.20%.
cuDNN flash attention (JAX backend)
AttentionImplementation.CUDNN, selectable viaload(..., col_attention_impl='cudnn', icl_attention_impl='cudnn'). Thechunked FLASH path stays the default; CUDNN dispatches to
jax.nn.dot_product_attention(implementation='cudnn'). TabFM's booleanmasks are always key-prefix (padding) masks, so they are mapped onto
cuDNN's
key_value_seq_lengthsinstead of the dense[B, N, T, T_src]mask cuDNN would otherwise require. Head dims > 128 (e.g. the v1.0.0 ICL
stage's 256) require a Hopper-or-later GPU; cuDNN raises
NotImplementedErrorotherwise.Measured on 1x H100 (same fold-0 task, 135k-row context): single-member
predict_proba634s -> 7.4s, with predictions matching the PyTorch backend(probability correlation 0.999; the chunked path's bf16 accumulation was
the numerical outlier).
Tests
seqpar_test.pyruns 2 CPU processes with the gloo backend andcompares the sharded forward against the single-process model forward at
1e-4, covering regression/classification, even and ragged shards, and
cat_mask/dfeature padding.seqpar_test.pyruns on two simulated CPU host devices (samepattern as
classifier_and_regressor_multidevice_test.py) and comparessharded
predict/predict_probaagainst the estimators' single-devicepaths at 1e-4 in float32, including multi-member ensembles.
seqpar_multiprocess_test.pyrehearses the multi-host execution model(two spawned processes joined via
jax.distributed.initialize, e.g. formulti-host TPU slices) against the same reference.