Skip to content

Add sequence-parallel multi-GPU inference (PyTorch and JAX) and a cuDNN flash attention option - #81

Open
weihaokong wants to merge 5 commits into
google-research:mainfrom
weihaokong:pr/pytorch-seqpar
Open

Add sequence-parallel multi-GPU inference (PyTorch and JAX) and a cuDNN flash attention option#81
weihaokong wants to merge 5 commits into
google-research:mainfrom
weihaokong:pr/pytorch-seqpar

Conversation

@weihaokong

@weihaokong weihaokong commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three additions for large-context inference:

  1. tabfm/src/pytorch/seqpar.py — sequence-parallel (row-sharded)
    multi-GPU inference for the PyTorch backend under torch.distributed.
  2. tabfm/src/jax/seqpar.py — the equivalent for the JAX backend: a
    single process shards the rows across all local devices with
    jax.shard_map.
  3. AttentionImplementation.CUDNN — fused cuDNN flash attention option
    for 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.

  • Cell embedding, row interaction (attention over features), feed-forward
    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).
  • The column set-transformer's inducing attention runs an fp32 online
    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.
  • ICL self-attention gathers each device's projected context K/V (test rows
    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_proba mirror TabFMRegressor.predict
    and TabFMClassifier.predict_proba (class-shift undo, ensemble
    combination) for any n_estimators, with cat_mask / feature-padding
    support. 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.2s
with 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 a
median deviation of 0.20%.

cuDNN flash attention (JAX backend)

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 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
NotImplementedError otherwise.

Measured on 1x H100 (same fold-0 task, 135k-row context): single-member
predict_proba 634s -> 7.4s, with predictions matching the PyTorch backend
(probability correlation 0.999; the chunked path's bf16 accumulation was
the numerical outlier).

Tests

  • PyTorch: seqpar_test.py runs 2 CPU processes with the gloo backend and
    compares the sharded forward against the single-process model forward at
    1e-4, covering regression/classification, even and ragged shards, and
    cat_mask/d feature padding.
  • JAX: seqpar_test.py runs on two simulated CPU host devices (same
    pattern as classifier_and_regressor_multidevice_test.py) and compares
    sharded predict/predict_proba against the estimators' single-device
    paths at 1e-4 in float32, including multi-member ensembles.
    seqpar_multiprocess_test.py rehearses the multi-host execution model
    (two spawned processes joined via jax.distributed.initialize, e.g. for
    multi-host TPU slices) against the same reference.
  • Full repo suite passes with all commits (100 tests).

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.
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.
@weihaokong weihaokong changed the title Add sequence-parallel multi-GPU inference for the PyTorch backend Add sequence-parallel multi-GPU inference (PyTorch and JAX) and a cuDNN flash attention option Jul 23, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant