Rebuild the e2e training benchmarks around per-op MaxSim VRAM (ColQwen2 + PyLate)#102
Conversation
- Add bench_colpali_e2e.py (adapted from colpali PR #412): real ColModelTraining steps on vidore/colpali_train_set with the loss head instrumented per MaxSim call, isolated-op replay, OOM-as-outcome - Add summarize_colpali_e2e.py (op-VRAM + batch-ceiling tables, log-log plot) and sky_colpali_e2e.yaml (fresh process per (B, variant) cell) - Shim colpali-engine 0.3.16's ContrastiveTrainer bugs under transformers 5.x (fixed upstream in PR #412, unreleased) - Remove the five superseded e2e benches, _bench_common.py, and the colpali/pylate sky benchmark jobs; keep bench_colpali_loss.py - Update docs/benchmarks.md and README with measured H100 results: MaxSim op 7.81 GiB -> 61 MiB at B=128 (~130x), vanilla OOMs at B=128 where LIK trains it (2x batch ceiling), step time at parity
- Resolve the defining module per layout: pylate.scores.scores (<=1.4) vs pylate.scores.colbert (>=1.5, where ColBERTScores resolves colbert_scores from module globals at call time) - Rewrite only Distillation's import-time capture on the new layout; 1.5's contrastive losses need no per-module rewriting - Make the CUDA compat tests layout-aware - Drop the stale "1.5 broke the patch / pin 1.3.3" notes
- Add bench_pylate_e2e.py: real SentenceTransformerTrainer steps (GTE-ModernColBERT-v1 + Contrastive on MS MARCO triplets) with the loss's score_metric instrumented per call, isolated-op replay, OOM-as-outcome; --variant vanilla|lik toggles patch_pylate() - Expose PyLate's own mitigation via --score-mini-batch-size so the sweep compares LIK against built-in query-axis chunking - Add summarize_pylate_e2e.py (leads with the forward transient: PyLate's .max(dim) holds indices, not the grid) and sky_pylate_e2e.yaml (fresh process per cell, chunked-vanilla cells at the largest batches)
- Add --grad-checkpoint to bench_pylate_e2e.py: on the canonical recipe the encoder activations cap the batch at B=128 (every variant OOMs at B=256 alike), so this regime — matching the ColQwen2 bench — is where the B^2 score transient binds - Sweep both regimes in sky_pylate_e2e.yaml; summarizer gains --regime and regime-aware plot labels - Record measured H100 results in docs/benchmarks.md, README, CHANGELOG: vanilla OOMs at B=1024 on a single 56.25 GiB grid alloc where LIK trains it at 4.81 s/step; step peak 54.1 -> 29.7 GiB at B=512; LIK 1.07-1.12x faster per step than vanilla and 1.25x faster than PyLate's score_mini_batch_size chunking at matched B=1024
- Add --ceiling-plot to summarize_colpali_e2e.py and summarize_pylate_e2e.py: grouped bars of whole-step peak alloc per variant, hatched where a variant OOMs (the pre-OOM peak) — the batch-sweep figure from colpali PR #412's description, which the markdown table alone doesn't convey at a glance - Mention the new flag in both sky yaml reproduce hints
|
Went through this carefully and cross-checked the harnesses against colpali-engine 0.3.16 and pylate 1.5.0 source — really clean, the hook points and the fp32-grid replay all line up with what the real trainers do. Just a few small things, nothing blocking:
The ~150 lines shared between the two benches (recorder/replay/timer) I assume is intentional given the |
- Replay pylate masks as bool (the dtype extract_skiplist_mask produces), and pass bool masks through without the != 0 copy the real patched path never makes - Derive the op-VRAM plot's per-doubling slope annotations from the data instead of hardcoding x4/x2, so a drifting run can't produce a figure asserting a scaling the points don't show - Surface step_peak_reserved_mib in both batch-ceiling tables (alloc / reserved) — fragmentation is a reserved-vs-alloc story
Thanks for the careful pass — all four are right. Addressed in 841335f:
Caveat: 1–2 touch GPU-only paths and are verified by lint + regenerated plots from the existing JSONs; the bool-mask replay gets its first real exercise on the next sweep. Sub-MiB delta, so I didn't relaunch an H100 for it. Will do in follow-up PR. |
The 1.3.3 pin and its 'pylate 1.5+ broke patch_pylate' comment predate #102, which made patch_pylate() support the 1.5 score-module layout. 1.5.0 is the pinned baseline in docs/benchmarks.md and what sky_pylate_e2e.yaml already installs.
) * chore(benchmarks): remove stale one-off benches and orphaned scripts - bench_backward_0_5.py: one-off v0.5.0 backward investigation; no docs tables back it, not part of any SkyPilot job. - bench_fastplaid.py: isolated rerank microbench superseded by bench_fastplaid_e2e.py (same shapes, real on-disk index). - bench_training.py: synthetic full-train-step sweep superseded by the e2e harnesses (bench_colpali_e2e / bench_pylate_e2e) and bench_backward_method. - scripts/_bench_autotune_persistence.py + sky_bench_autotune_persistence.yaml: orphaned PR #64 reproducer; the feature is covered by tests since. * refactor(benchmarks): group benches per comparison stack benchmarks/ is now kernels/ (synthetic-tensor microbenches, incl. the platform-specific bench_mps.py), plaid/, colpali/, and pylate/ — each e2e harness sits next to its summarizer, and each SkyPilot job has an obvious scope. - bench_lateon.py -> kernels/bench_longdoc.py: its value is the long-document regime (Ld up to 16 384), not the LateOn brand. --only tags and the lateon_*.json output name are unchanged so existing results stay valid; the RUN_ONLY tag is now 'longdoc'. - Pure moves otherwise (git mv): no behavior change, no JSON schema change. Sky yamls, run_all_benchmarks.sh, docs, and the benches' own usage docstrings point at the new paths. - benchmarks/README.md restructured around the four folders and now documents that the e2e harnesses' shared machinery is deliberate duplication (standalone-by-design, per the #102 review). * docs(benchmarks): qualify bench mentions with their new stack folder First mention of each bench in docs/benchmarks.md now carries its benchmarks/<stack>/ path, and the long-documents section names its source (kernels/bench_longdoc.py, formerly bench_lateon.py) — it was the only table section that never said which bench produced it. * fix(scripts): bump sky_run_all's pylate pin to the 1.5.0 baseline The 1.3.3 pin and its 'pylate 1.5+ broke patch_pylate' comment predate #102, which made patch_pylate() support the 1.5 score-module layout. 1.5.0 is the pinned baseline in docs/benchmarks.md and what sky_pylate_e2e.yaml already installs. * docs: update bench glob references that slipped the path sweep CONTRIBUTING.md's autotune section and the bug-report template still pointed at the pre-reorg flat benchmarks/bench_*.py glob; the original sweep didn't cover .github/ or CONTRIBUTING.md. --------- Co-authored-by: Tony Wu <28306721+tonywu71@users.noreply.github.com>
Context
The previous e2e training benches (
bench_colpali_training/realdata.py,bench_pylate_training/realdata/lateon.py) ran hand-rolled step loops on synthetic data or small eval subsets and reported average step time plus whole-step peak memory. On encoder-dominated training steps that mostly measures parity (~1×), which is not where the kernel's value is.colpali PR #412 built a better harness for its own validation: real trainer steps on real train data, the MaxSim op instrumented per call (forward transient, bytes held for backward), every recorded shape replayed on an isolated graph so the op's backward is bracketed exactly, and OOM treated as a recorded sweep outcome instead of a crash. What that surfaces (the op's B²-growing memory term and the batch-size ceiling it sets) survives encoder dilution where the step-time speedup does not.
This PR replaces the five old benches with two ports of that harness, kept as symmetrical as possible:
bench_colpali_e2e.py— ColQwen2 + LoRA through colpali-engine's ownColModelTrainingrecipe, onvidore/colpali_train_setshards (releasedcolpali-engine==0.3.16, toggled withpatch_colpali_engine()).bench_pylate_e2e.py— GTE-ModernColBERT-v1 +ContrastivethroughSentenceTransformerTrainer, on MS MARCO triplets (pylate==1.5.0, toggled withpatch_pylate()), with a--grad-checkpointregime and--score-mini-batch-sizecells covering PyLate's own chunking mitigation.Benchmarking current PyLate required a product fix that rides along: PyLate 1.5 renamed the scoring module and rerouted its losses through
ColBERTScores, which brokepatch_pylate(). The old benches pinned 1.3.3 because of this; the patch now detects the layout, so the>=1.3.3,<2range in the extra is accurate again.Results (1× H100 80 GB)
Full tables and readings live in
docs/benchmarks.md; per-cell JSONs are reproducible via the SkyPilot jobs below.ColQwen2 reproduces colpali PR #412's published numbers to the MiB. The MaxSim op costs vanilla 7.81 GiB of VRAM at B=128 vs 61 MiB with LIK (~130×, ×4 vs ×2 per batch doubling); step time is at parity since the 2B encoder hides the op. Vanilla OOMs at B=128 (a 1.81 GiB request failing while 25 GiB sit reserved-but-unallocated, fragmentation caused by the multi-GiB score grid) where LIK trains it at 7.98 samples/s:
PyLate has two regimes and two different stories. On the canonical recipe (no grad-checkpointing) the encoder activations fill the card at B=256 before the score grid matters: vanilla, LIK, and PyLate's
score_mini_batch_sizechunking all OOM identically, so LIK only trims the op transient (1.8 GiB → 56 MiB at B=128) without moving the ceiling. With--grad-checkpoint, the regime matching the ColQwen2 bench, the B² transient binds and the 149 M encoder is too small to hide the op:score_mini_batch_size=64The harness pins vanilla's B=1024 death inside the score call: a single contiguous allocation of exactly the
[1024, 1024, 48, 300]fp32 per-slot grid. LIK is also 1.07–1.12× faster per step than vanilla here, and 1.25× faster than PyLate's own chunking at matched B=1024. Chunking buys the ceiling with serialized recompute; the fused kernel buys it while speeding the step up.Quick start
sky launch scripts/sky_colpali_e2e.yaml -c lik-colpali-e2e -y sky launch scripts/sky_pylate_e2e.yaml -c lik-pylate-e2e -y rsync -avP 'lik-colpali-e2e:sky_workdir/benchmarks/results/' ./benchmarks/results/ python benchmarks/summarize_colpali_e2e.py --results-dir benchmarks/results --plot benchmarks/results/colpali_e2e_vram.png python benchmarks/summarize_pylate_e2e.py --results-dir benchmarks/results --regime ckptNote
bench_colpali_e2e.pyships a smallContrastiveTrainershim for two colpali-engine 0.3.16 bugs under transformers 5.x (_get_train_samplersignature; single-dataset loss prefixes). Both are fixed upstream in colpali PR #412 but unreleased; the shim goes away once colpali-engine > 0.3.16 ships.bench_colpali_loss.py(loss-head isolation, no encoder) is kept: it backs the 2.5–4.3× explicit-negative numbers already in the docs.docs/benchmarks.mdstay; only references to removed scripts were updated.Changes
Click here to expand
benchmarks/bench_colpali_e2e.py— ColQwen2 harness: per-MaxSim-call VRAM recording at the loss head, isolated-op replay (vanilla replays the release's exacteinsum → amax → sum, lik replayslik.autograd.maxsim), step timer, OOM-as-outcomebenchmarks/bench_pylate_e2e.py— PyLate sibling: wraps the loss'sscore_metric, replays PyLate 1.5's exact per-slotColBERTScoresmath vs the fused per-slot path;--grad-checkpointand--score-mini-batch-sizedimensionssummarize_colpali_e2e.py/summarize_pylate_e2e.py— op-VRAM and batch-ceiling markdown tables plus log-log plots from the per-cell JSONs (--regime plain|ckptfor pylate)scripts/sky_colpali_e2e.yaml/sky_pylate_e2e.yaml— fresh process per (batch, variant) cell so OOMs are isolated; crash markers keep the sweep going; the colpali job is also the new home forbench_colpali_loss.py --sweeppatch_pylate()1.5 support — resolve the defining module per layout (scores.scoresfor <=1.4,scores.colbertfor >=1.5), rewrite onlyDistillation's import-time capture on 1.5; layout-aware CUDA compat testsbench_colpali_training/realdata.py,bench_pylate_training/realdata/lateon.py,_bench_common.py,sky_colpali_benchmark.yaml,sky_pylate_benchmark.yaml; pylate blocks dropped fromsky_run_all_benchmarks.yamldocs/benchmarks.mdwith the measured tables, README benchmark/memory updates, CHANGELOG entries, benchmarks/README operator guideNext steps
COLPALI_SCORES_BACKEND(per that PR's plan, the LIK-side patches eventually retire in favor of the native integrations).benchmarks/per stack —kernels/(synthetic micro benches),plaid/,colpali/,pylate/— with eachsummarize_*next to its bench, and renamebench_lateon.py→kernels/bench_longdoc.py(it sweeps long-document shapes, and its e2e namesake is gone). Kept out of this PR so the benchmark work isn't buried in rename noise.