TLS: survey-scale fast path (batch phase-binned kernel + exact refinement)#68
Conversation
… refinement)
Rewrite Transit Least Squares for survey-scale throughput. The legacy
kernel did two full O(ndata) passes per (duration, t0) trial and capped
light curves at ~3,500 points; the new default (`use_fast=True`) removes
both limits and processes a whole survey chunk in one launch.
Architecture (cuvarbase/kernels/tls_fast.cu + tls_search_batch):
- One block per (light curve, period); fold once into shared-memory
phase bins, then scan every trial against bin-averaged integrated
template tables (S1=int T, S2=int T^2) with a closed-form chi2
(chi2_0 - num^2/den). Trial cost is independent of ndata; no shared-
memory cap on light-curve length.
- Period grid split into bin-count bands so long-period searches don't
pay the finest band's per-trial cost.
- Exact float-float ("double-single") fold: ~1e-8 phase error at
4-year baselines with no 1/64-rate double math on consumer GPUs.
- Cancellation-free score output (num^2/den); chi2 reconstructed in
float64 host-side against a float64 chi2_0.
- Second exact kernel re-fits the top-K candidate periods per light
curve on a finer local (duration, t0) grid. Refinement sharpens the
reported parameters only; the SDE/FAP statistics are computed from
the uniform coarse spectrum so the detection statistic's scale stays
consistent with the legacy kernel.
Support fixes: SDE median-detrend window capped at 91 (reference TLS
convention) instead of a pathological nperiods/10 window (minutes ->
~0.1 s at 190k periods); duration_grid_keplerian vectorized (1.1 s ->
40 ms at 190k periods); per-light-curve statistics run on a thread pool;
64-bit batch offsets; qmax<1, power-of-two block_size, and non-negative
refine_top_k validated with clear errors.
Measured end-to-end (scripts/benchmark_tls_survey.py, 100% injected-
transit recovery in every regime; RTX A5000): TESS FFI 1.2 ms/LC
(~800 LC/s), K2 3.1 ms, TESS 2-min 2.8 ms, TESS 1-yr 18 ms, Kepler
4-yr (65k pts, 172k periods) 0.17 s/LC vs ~522 s for reference TLS on a
16-core CPU. Full suite 68/68 on RTX 4000 Ada (incl. golden tests vs the
reference transitleastsquares package); core suite green on A5000 and
V100. Block-size heuristic swept and tuned per compute capability
(sm_70/86/89). See analysis/TLS_COST_ANALYSIS.md for the GPU-vs-CPU and
GPU-vs-GTLS cost comparison.
The legacy per-point kernel is retained behind use_fast=False; the
module stays EXPERIMENTAL pending an injection-recovery validation
campaign at reference-matched epoch fidelity (item D3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEGJJrPcrvkJGeMAEryRPG
…timing Adds an apples-to-apples fidelity experiment answering whether the coarse-epoch-grid + refinement fast path sacrifices detectability. scripts/tls_fidelity_experiment.py runs identical injected light curves through cuvarbase (t0_oversample=3 default AND 33 reference-matched) and the reference transitleastsquares package on one shared Ofir grid, and recomputes SDE with the IDENTICAL statistic on both methods' chi2 spectra (their SR->SDE normalizations differ, so the statistic is held fixed and only spectrum fidelity varies). Result (RTX A5000): the default coarse grid is within 1-3% of the reference SDE (0.97-0.99x) with 100% recovery, including a marginal near-threshold depth and a narrow transit; matched t0=33 closes it to within 1% (1.01-1.03x). SDE is a period-space contrast that is largely insensitive to epoch-grid density, so the coarse grid trades reported t0/parameter precision (restored by refinement) for speed, not detectability. scripts/tls_matched_timing.py measures the matched- fidelity cost: 6-15x over default (Kepler-4yr 8.4x -> 1.48 s/LC). Rewrites analysis/TLS_COST_ANALYSIS.md honestly: throughput is the market-independent invariant (thousands x vs CPU; ~22-190x vs the GTLS CuPy GPU-TLS, arXiv:2607.00348); the earlier dollar ratios over-pinned the multiplier by comparing a spot-price GPU against an AWS on-demand CPU. Raw fidelity numbers in benchmarks/results/tls_survey_jul2026/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WEGJJrPcrvkJGeMAEryRPG
Follow-up: measured fidelity (is the coarse-grid + refinement lossy?)Pushed The fast path is not lossy in detectability. On the identical SDE statistic (RTX A5000):
Default is within 1–3% of the reference SDE (safe direction — slight under-report); matched t0=33 is within 1%. SDE is a period-space contrast that normalizes out epoch-grid density, so the coarse grid trades reported t0/parameter precision (restored by refinement) for speed, not detectability. Matched fidelity costs a measured 6–15× (Kepler-4yr 8.4× → 1.48 s/LC). Also corrected the earlier cost claims in |
…l SDE) Reproduces arXiv:2607.00348 Fig.7 on one RTX A5000 with a matched search: identical Ofir period grid, identical per-period duration window, matched epoch density (t0_oversample=8 == GTLS skip=8), and one injected transit fed to every method. One identical SDE routine scores every spectrum. cuvarbase-TLS-matched is 30 -> 171x faster than GTLS-skip8 across 200->2000 d (growing with baseline; GTLS scales ~N^2.5 from per-batch kernel-launch overhead, cuvarbase ~linear), at 1-3% SDE parity and 100% recovery. It is also 23-40x faster than the paper's own RTX-4090 GTLS numbers (immune to the A5000-vs-4090 question). The paper's Fig.7 used GTLS skip=8, not full-scan. Documents the paper's BLS-comparison caveat: its Kunimoto qmin=2e-4/noverlap=3 config forces up to 5000 phase bins (sub-cadence durations for 30-min data), cheap for GTLS's cumsum but a 25x penalty for cuvarbase-BLS's per-duration re-binning, and noverlap=3 bypasses the fused kernel. A sensible BLS config (qmin=2e-3, fused noverlap=2) is faster than both TLS and GTLS; our improved batched BLS at the paper's exact config is ~23x faster than their 121.1 s. Adds analysis/GTLS_COMPARISON.md + the reproduced figure, the benchmark under scripts/gtls_benchmark/ (BLS curves require feature/bls-survey-speed), and the raw July-2026 A5000 result JSONs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Answers the single-light-curve / cold-start question. On a fresh A5000, one LC, fresh process, kernel JIT compile INCLUDED, on-disk kernel cache cleared (true first-run case), full launch-to-answer wall time: baseline cuvarbase-TLS-matched GTLS-skip8 cold ratio 200 d 4.1 s 10.7 s 2.6x 500 d 4.5 s 27.8 s 6.1x 1000 d 4.8 s 83.8 s 17x 1500 d 5.6 s 191.0 s 34x cuvarbase's cold cost is a ~fixed ~3-4s kernel compile that barely grows with baseline; GTLS's is its exploding search, so the ratio grows 2.6x -> 34x. From the 2nd star onward (pycuda/cupy disk cache warm) cuvarbase drops to ~0.5-2s and the ratio snaps back toward the warm 30-171x; GTLS recompiles + re-searches every call. SDE parity holds cold too. Adds analysis/GTLS_COMPARISON.md section 2b, the raw cold data, and the harness (scripts/gtls_benchmark/cold_shot.py + cold_driver.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…numbers - Add TLS to the survey-scale performance story: 30-170x faster than GTLS (the only other GPU TLS) at 1-3% SDE parity, 23-40x vs GTLS's own 4090 numbers, thousands-x vs CPU transitleastsquares (links analysis/GTLS_COMPARISON.md). - Update the experimental TLS description to the validated fast path (tls_search_batch): arbitrary ndata, SDE parity with reference + GTLS, still flagged experimental pending full injection-recovery. - Correct the BLS "vs previous release" framing: the v1.0 kernel is inherited from 0.2.6 essentially unchanged; the survey-speed campaign (PR #66) makes it 2.9-9.2x faster (kernel) / 2.0-12.7x (end-to-end), plus 34x from kernel caching in a naive loop. (Replaces a misleading "~1x" characterization.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#68 review) _finish_lc's coarse-parameter fallback (taken when a light curve's top-K exact refinements all return the failure sentinel while the coarse scan still has valid periods) reads t0_h/dur_h/depth_h, but those were fetched only under `return_arrays or not K`. In the default batch path (refine_top_k=50 -> K>0, return_arrays=False) they were never bound, so reaching the fallback raised UnboundLocalError and aborted the whole batch. Surfaced by the adversarial PR review. Fetch the coarse arrays whenever the fallback can fire (any LC with all refined scores <= 0), still skipping the D2H on the common path. Adds a deterministic regression test that forces every refinement to the sentinel and asserts the default batch falls back instead of crashing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review of the fast-path library surface found no correctness defects (analysis/tls-audit-jul2026.md has the verified invariants and the full findings table). Applied here: - Remove the stale import-time "EXPERIMENTAL, not for science use" warning from cuvarbase.tls: the fast path is golden-tested against the reference package and ships as a v1.0 feature; the warning also pointed users at an internal analysis/ document - Delete dead _auto_nbins() (superseded by the inline per-period banding in tls_search_batch) - tls_search_gpu: document use_fast/refine_top_k/refine_oversample/ nbins; correct the flux docstring (no path normalizes y) and note the legacy path's float32-fold BJD limitation; warn when the never-used `durations` parameter is passed - _preprocess_batch: explicit int32 guard for pathological per-LC point counts - Tests: banded-vs-single-band parity (GPU; exercises the NBINS band split + period_map scatter, the one intricate path the suite only covered implicitly), SDE median-kernel cap/rounding/short- series behavior (CPU), int32 guard + durations warning (CPU) - bls.py: fix five invalid \chi/\omega escape sequences in docstrings CPU suite: 253 passed / 543 GPU-skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First GPU validation of the #66+#67+#68 union (each PR had only been pod-validated on its own branch). RTX A5000, pycuda 2026.1, numpy 2.4.6, with batman + cufinufft + the reference transitleastsquares installed: - Full suite: 796 passed, 0 skipped, 0 failed (17:14). An initial run showed 2 skips - the TLS golden tests, because the reference package was missing from the pod; with it installed they run and pass. Gate environments must install transitleastsquares (recorded in SUMMARY) - scripts/check_release_gate.py: 14/14 ALL CHECKS PASSED - Re-measured + archived the matched-fidelity timings the claims audit flagged as unarchived: tess-yr 12.8x (25.3 -> 325.2 ms/LC), kepler-4yr 8.1x (188.3 -> 1520.5 ms/LC), 100% recovery at both fidelities (benchmarks/results/tls_survey_jul2026/matched_timing_a5000_jul2026.txt); published range tightened ~5-15x -> ~5-13x, Kepler matched cost ~$114/M, TESS-yr ~$24/M now measured (CHANGELOG, release notes, TLS_COST_ANALYSIS, provenance README updated) - Release notes: GPU test count updated to the gate's 796/0-skip - Docs built on the pod with all 14 GPU-rendered figures, zero warnings (after fixing a docstring rst error in utils.py and letting the pod sync include docs/source/logo.png) - staged as the local orphan branch gh-pages-staging (fab3a5e), to be pushed on release day - Archive: analysis/v1.0-release-gate-jul2026/ (suite/gate/timing logs, environment record, SUMMARY.md). Pod terminated, API-verified Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
Rewrites Transit Least Squares for survey-scale throughput (N ≫ 1 light curves). The new batch API
tls_search_batch()and kerneltls_fast.cubecome the default fortls_search/tls_search_gpu/tls_transit(opt out withuse_fast=False). The legacy per-point kernel is untouched and retained.The old kernel did two full O(ndata) passes per (duration, t0) trial and capped light curves at ~3,500 points. Both limits are gone.
Results (end-to-end survey wall time, 100% injected-transit recovery in every regime)
Fidelity — not lossy for detection (measured)
The key question: does the coarse epoch grid + refinement sacrifice detectability? Measured with
scripts/tls_fidelity_experiment.py— same injected light curves, same Ofir grid, cuvarbase (defaultt0=3and reference-matchedt0=33) vs the realtransitleastsquares, with SDE recomputed by the identical statistic on both methods' χ² spectra (their SR→SDE normalizations differ):Default is within 1–3% of the reference SDE (safe direction — slight under-report), matched t0=33 within 1%, 100% recovery including marginal and narrow transits. SDE is a period-space contrast that normalizes out epoch-grid density, so the coarse grid trades reported t0/parameter precision (restored by the refinement pass) for speed, not detectability. Matched fidelity costs a measured 6–15× (Kepler-4yr 8.4× → 1.48 s/LC).
Speed & cost
Throughput is the market-independent invariant: apples-to-apples on the same machine (same LCs/grid, single GPU vs all CPU cores), ~1,000–3,000× faster than the reference at matched SDE fidelity (more at the default grid; ratio is CPU-dependent). Versus GTLS (the only other GPU TLS, CuPy/RTX 4090, arXiv:2607.00348), a Kepler-class config runs ~22× (matched) to ~190× (default) faster on a cheaper A5000.
Dollar figures are pricing-dependent and were not the headline — an earlier revision over-pinned them by pairing a spot-price GPU with an AWS on-demand CPU.
analysis/TLS_COST_ANALYSIS.mdhas the honest, caveated breakdown (hundreds-to-thousands× cheaper per LC than CPU TLS under any reasonable pricing).How
chi2_0 − num²/den). Trial cost independent of ndata.duration_grid_keplerianvectorized (1.1 s → 40 ms); threaded per-LC stats; 64-bit offsets; input validation (qmax<1, power-of-twoblock_size, non-negativerefine_top_k).Validation
transitleastsquarespackage; core suite green on A5000 (sm_86) and V100 (sm_70).benchmarks/results/tls_survey_jul2026/.Caveats
scripts/setup-remote.shnow honorsRUNPOD_REMOTE_DIR(some pods have broken/workspacevolumes).🤖 Generated with Claude Code