Repository navigation
Conversation
vshah1016
marked this pull request as ready for review
November 13, 2026 10:55
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
This PR makes
ESM3.logits()usable inside a CUDA Graph. The current implementation creates its default confidence scalar through a CPU-backed operation; the resulting host-to-device copy fails during capture. Creating that scalar directly on the target device removes the capture failure.The PR also converts only the outputs requested by
LogitsConfigto FP32, adds regression tests, and provides a benchmark for repeated, fixed-shape inference. The cookbook includes an optional strict SwiGLU compilation recipe and records the alternatives we tested.On one B200, the original 76-residue ubiquitin experiment measured 32.81 ms eager versus 6.269 ms with fresh input copy and graph replay, about 5.23× faster. A separate experiment reduced graph latency by a further 3.69–5.61% across three workloads by compiling only SwiGLU. Applications must explicitly capture and replay a graph to obtain these gains; ordinary calls remain eager.
1. Changes in this PR
logits()torch.tensor(1.0, device=input.device)introduces a CPU-to-GPU transfer that fails during capture.torch.ones((), device=input.device). The scalar retains its value, shape, and default floating dtype while being created on the target device.None.The output-selection change still computes every model head. Both the model's forward hooks and output-head hooks continue to receive the complete raw outputs. The library's default precision, attention backend, and compilation settings are unchanged.
Files to review
esm/models/esm3.pyesm3_cuda_graph_test.py,esm3_logits_selection_test.pyesm3_cuda_graph.pyCONTRIBUTIONS.md,README.md2. Correctness and API behavior
The two regression modules contain 24 cases: 20 CPU cases and four CUDA cases. They cover:
torch.testing.assert_close.Full-model benchmark checks provide separate evidence. For the accepted graph and strict-SwiGLU configurations, every requested tensor passed
torch.equalagainst the appropriate eager reference across four changing inputs. The SwiGLU candidates also matched the original, uncompiled BF16 model. These are exact returned-value comparisons on the recorded fixtures; they do not establish equivalence for every possible input or biological task.Applications own the graph's static input and output buffers. They must warm the model before capture and copy new inputs into the captured buffers. Returned graph outputs reuse storage, so an application must consume or clone them before the next replay. A different shape, input-track set, output configuration, or model configuration requires another warmed capture.
3. Performance results
A. Enabling CUDA Graph execution
The original capture experiment used one B200, batch size 1, and 76-residue ubiquitin encoded to 78 tokens. All six output heads and embeddings were requested.
The fresh-host-input path is 5.23× faster than the mean of the two eager control medians. Its increment over fixed-input replay is 7.56 microseconds for the 624-byte sequence input, approximately 0.12% of the complete graph call. That increment is a difference of measured medians, not a standalone transfer-bandwidth measurement.
The unmodified capture failure and patched timing/correctness record are retained. This comparison measures the execution path enabled by the patch; it does not claim that replacing the scalar alone accelerates eager inference by 5.23×.
B. Optional SwiGLU compilation inside the graph
Graph replay reduces CPU launch overhead, but the captured kernels still execute individually. A node-level Nsight Systems trace showed many small elementwise, conversion, and copy kernels. We therefore tested compiling only each block's SwiGLU activation, using
numerics="strict"and disabling Inductor's own CUDA Graph handling.The fresh-host rerun measured the following, with a graph control before and after each candidate:
All seven returned tensors passed exact comparisons across four input variants for each workload. A separate profile recorded 2,386 → 2,338 kernels per replay: 48 SiLU/multiply pairs became 48 fused kernels. Normalization, attention, and GEMM operations were left unchanged. Fresh-run samples · Profiler record
The original series measured reductions of 3.64%, 5.67%, and 4.11%. The fresh run is a replication by the same team on another host. It is separate from the capture experiment above; their timings are not combined into a new end-to-end speedup.
The first candidate call took 1.96–4.26 seconds in the fresh run, including compilation-related startup. This cost must be amortized over repeated calls. The original batch-1 series also contains a 14.482 ms outlier, retained in the data. These results support a median-latency improvement; they do not establish improved tail latency.
C. Selective output conversion
Sequence-only and embeddings-only requests each remove six FP32 conversion copies. Full-model comparisons passed exact output checks at batch 1 and 8 for ubiquitin and batch 1 for the 512-residue synthetic fixture.
The measured graph-latency difference was only about 0–0.2%, and whole-call peak allocation did not decrease in the batch-1 ubiquitin measurement. This change removes unnecessary work, but the measurements do not support a meaningful latency or peak-memory improvement. Selection measurements
4. Measurement method and reproduction
Recorded environment
p6-b200.48xlargehostsOMP_NUM_THREADS=447f0545b2b6daf26a93439a3cd610f4f7f3d5478The primary input is ubiquitin with four sequence variants: unmasked and three mask patterns. Batch rows cycle through the variants. Longer fixtures repeat ubiquitin before masking; they are synthetic shape tests.
The capture and reported SwiGLU experiments each use 20 warmups and 100 timed calls per path, with GPU synchronization after each call. Controls bracket the candidates. The tables report medians; reductions use the mean of the before/after control medians as the reference.
For the headline fresh-input and SwiGLU comparisons, timing includes the pinned-host input copy, model execution or graph replay, and synchronization. Section 3A labels the additional fixed-input and device-copy controls separately. Timing excludes tokenization, host allocation/pinning, capture, compilation, and copying outputs back to the CPU. Profiling is separate from the reported uninstrumented latency measurements. Preliminary attention/quantization runs whose later controls were disturbed by profiling are retained in the archive but excluded from the reported comparisons.
Consecutive calls measure variation within a run. They are not independent experimental trials, and no independent-run confidence interval was estimated. The measurements establish behavior on the tested B200 fixtures; biological quality, B300 performance, and a comparison against Hopper remain untested.
Running the public recipe
Start from this PR's checkout with Python >=3.12 and a working CUDA environment. The cookbook setup installs the measured performance stack and explains how to pin the checkpoint. Keep this environment separate from the repository's locked test environment.
After that setup, run from the checkout root:
export CUDA_VISIBLE_DEVICES=0 OMP_NUM_THREADS=4 PYTHONPATH=. python cookbook/local/esm3_cuda_graph.py --output graph.json PYTHONPATH=. python cookbook/local/esm3_cuda_graph.py --compile-swiglu --output swiglu-b1.json PYTHONPATH=. python cookbook/local/esm3_cuda_graph.py --compile-swiglu --batch-size 8 --output swiglu-b8.json PYTHONPATH=. python cookbook/local/esm3_cuda_graph.py --compile-swiglu --length 512 --output swiglu-l512.jsonThe JSON records source/checkpoint identity, output comparisons, setup costs, and individual timing samples. For the default and strict-SwiGLU paths, failing exact output checks produces a nonzero exit. Approximate experimental configurations have separately reported tolerance checks.
The complete reproduction guide covers the pinned GPU-tested revision, locked tests, performance environment, and FA2 source build. Its portable command sequence was assembled from executed scripts and has not itself been rerun end to end on another GPU host.
5. Validation status
CPU CI and formatting/typing were rerun at the current head,
34bbb9b. The final GPU validation and fresh-host benchmark rerun usedd285dde; the later commit changes documentation only. Model code, regression tests, benchmark code/results, and dependency files are byte-identical between those two revisions. Full revision identifiers are in the appendix.34bbb9bty;34bbb9bd285dded285dded285dded285ddesnippets/esm3.py,snippets/esmc.py, andlocal/raw_forwards.pypassed on the B200 host34bbb9b; image installs the published packageESM_API_KEY, before inference34bbb9baction_required); no jobs executedThese suites have different scopes and overlap; their counts are not added into a single total. The fork workflow remains red because of the Forge failures. Passing the local checks does not complete the hosted integration or notebook checks.
Why nine GPU tests initially failed, and how the environment was repaired
The initial GPU selection reported 40 passed and 9 failed. The failures were ESMC/ESMFold2 paths entering FlashAttention2 with
no kernel image: the repository's dev wheel targets SM80/90, while B200 is SM100.We built official FA2 2.8.3.post2 source for SM100 against locked Torch 2.11/CUDA 13.0 in a separate overlay. Native cubins and a forward/backward probe were verified before rerunning the suite. The final 59-case selection retained every original 49-case identity, including the nine failures, and added ten cached-checkpoint cases. No ESM source or test assertions changed during this repair.
The repair changed both the FA2 version and binary architecture; it does not isolate architecture flags alone. The ESM3 performance measurements used a separate Torch 2.15 environment without FA2 installed, so their speedups cannot be attributed to this rebuild.
Original failures · Build command · Native architectures · Probe · Reproduction instructions
6. Other experiments and related work
The benchmark exposes the following alternatives for investigation. Their measurements explain why they were not adopted as default settings.
rtol=atol=1e-2. Whole-forwardfullgraph=Truealso encountered the tokenizer's Rust lookup.Detailed comparisons and raw records include the original-reference checks. Agreement between a candidate's graph and its own eager execution alone does not establish agreement with the original model.
Earlier batching and fused-AdamW measurements used existing APIs and PyTorch settings. They are documented in the historical experiments, separately from this PR's implementation and inference-latency claims.
Two related changes remain outside this branch: #417 addresses raw BF16
forward(), and #430 addresses padding whensequence_idis absent. These measurements include neither change. Equal-length batching here does not validate mixed-length padding; integrating #430 would also need a capture check. Publiclogits()already establishes CUDA BF16 autocast, while directforward()calls with the open BF16 weights still require the appropriate context.7. Review request
Please review the scalar and output-selection changes, their regression coverage, and whether the optional compilation recipe fits the cookbook. The upstream workflow also needs maintainer approval to run. Hosted integration and notebook validation remain the outstanding checks listed above.
Evidence appendix: revisions, raw data, and verification
fddac936fc921445da2d099a3e4f7ae928717292d285ddeead83631bbe0bc6aa7c879eb5a8946ccb34bbb9be4e5e4d2dd6104392abf247abef2f1a486b90888707f77dba4815e73ad11db508fd70cfa0The archive records the November 12–13, 2026 experiments and the validation state at publication. Its historical references to draft status and stale contribution instructions precede the current documentation commit; the current status is recorded above.
To check the archive, run
python3 verify.pyfrom itsexperiments/esm3-blackwelldirectory. The archive is separate from the implementation branch; historical probes and logs are evidence, not additional library changes.Contributed by @CumulusLabsIO (Cumulus Labs).