Skip to content

Activate the LTX allocator gates and pinned staging - #37

Merged
TroyHernandez merged 5 commits into
mainfrom
perf/ltx-gc-gates
Jul 20, 2026
Merged

Activate the LTX allocator gates and pinned staging#37
TroyHernandez merged 5 commits into
mainfrom
perf/ltx-gc-gates

Conversation

@TroyHernandez

Copy link
Copy Markdown
Contributor

Summary

Two pieces of existing LTX memory machinery were built, measured, and then never actually engaged. This PR turns both on.

  • ltx23_tune_gc() was inert by construction: it set the four allocator gate options one call after torch::cuda_is_available() had started torch — and start_torch() reads those options exactly once at init. Options now land before the first torch call in the function, the three CUDA gates are additionally pushed into the live allocator (the .flux_gc_gates pattern, so the function works however late it runs), and .onLoad defaults torch.threshold_call_gc, which has no live setter. User-set options win everywhere.
  • diffuseR.pin_staging now defaults to TRUE for phase offload: page-locked host copies make onload direct DMA and offload a pointer swap. The docstring's old numbers (2.7 s/render, break-even after 11 renders) were stale; re-measured post byte-LUT it breaks even on the second render. Silent per-component fallback if page-locking fails; options(diffuseR.pin_staging = FALSE) opts out under host memory pressure.

Measurements

768x512x49, NF4, seed 42, RTX 5060 Ti; 3 renders per configuration, one process each:

Configuration Load Warm render Denoise
main (inert gates, pageable) 53.2 s 64.2–65.6 s 26.7–26.8 s
gates only 53.8 s 61.9–62.2 s 25.7 s
pinning only 62.2 s 57.0–58.6 s 26.3 s
gates + pinning (this PR) 77.6 s¹ 54.0–54.3 s 25.7 s

¹ Single sample; the separate A/Bs predict ~63 s. Page-lock time at load shows run-to-run variance.

A week ago the same render was 89–96 s; with the byte-LUT dequant (#35) and this PR it's ~54 s warm.

Honest negative: the July 17 attribution of a ~12 s load-time gc storm to the inert threshold_call_gc did not reproduce — load time is unchanged by the gates fix (those gcs were cheap). The .onLoad default stays because it measurably cuts gc counts and is the only point where that option can land.

Validation

  • Full local suite: 889 results, all green, including 10 new tinytest results for ltx23_tune_gc option semantics (defaults, user override, reserved-rate clamps).
  • End-to-end GPU A/B benches above; denoise and decode times are unchanged by pinning (the win is pure transfer), matching the mechanism.

ltx23_tune_gc set the four allocator gate options one call AFTER
torch::cuda_is_available() started torch - and start_torch reads them
exactly once at init, so every option it set was inert by
construction. Measured cost of the inert gates: ~5% of render wall
and a ~550-gc / ~12 s storm across pipeline load.

Options now land before the first torch call in the function, the
three CUDA gates are additionally pushed into the live allocator
(the .flux_gc_gates pattern, so the function works however late it
runs), and .onLoad defaults torch.threshold_call_gc - which has no
live setter - so torch reads it at init whenever diffuseR loads
before the first torch op. User-set options win everywhere;
tinytest covers defaults, user override, and the [0.20, 0.92]
reserved-rate clamps.
Measured post byte-LUT (768x512x49 NF4, 3-render A/B): warm renders
64-66 s pageable vs 57-59 s pinned (~7 s/render, all of it transfer
time - denoise and decode identical), page-locking +9 s at load.
Break-even on the second render, ~2 s net cost for a single-render
session. The docstring's stale numbers (2.7 s/render, break-even 11)
predate the byte-LUT denoise and the current phase-transition
behavior. Existing per-component silent fallback covers page-locking
failure; options(diffuseR.pin_staging = FALSE) opts out.
@TroyHernandez
TroyHernandez merged commit 1e9f6a8 into main Jul 20, 2026
2 checks passed
@TroyHernandez
TroyHernandez deleted the perf/ltx-gc-gates branch July 20, 2026 19:44
TroyHernandez added a commit that referenced this pull request Jul 20, 2026
* Untiled VAE decode when it fits, drop per-tile gc

The video decode phase measured 12.3 s in-render. Profiling (VAE
only, synthetic latents): the tiled decode itself was 4.35 s of which
1.89 s was the explicit per-tile gc(), and a single untiled forward is
2.67 s - tiling exists to bound activation VRAM, not for correctness,
and at decode time the transformer is phase-offloaded so the VRAM is
actually free.

decode() now runs one full-latent forward when the estimated
activation cost fits the card: cost is linear in output pixel-frames
(~1 GB + ~350 B/pxf measured across 2.5M-19.3M pxf; the rule uses
360 B/pxf + 15% headroom against total minus allocated).
options(diffuseR.vae_untiled = TRUE/FALSE) forces either path; auto
applies only on CUDA, CPU keeps tiled behavior. The per-tile explicit
gc() goes away: with the allocator gates live (#37) the dead-handle
storm it guarded against no longer occurs (measured 3.8 s vs 4.2 s,
storm-free, VRAM peak unchanged).

In-render (768x512x49 NF4, pinned, seed 42): video decode phase
12.3 -> 1.7-1.8 s; warm render 54 -> 44.2-44.5 s. Tests: dispatch
parity for forced-untiled vs reference, forced-tiled vs tiled, and
auto-on-CPU staying tiled.

* rformat + document

* Bump version to 0.1.0.12
TroyHernandez added a commit that referenced this pull request Jul 22, 2026
…41)

* Pin host memory via torch_empty_strided, not the deprecated overload

pin_staging defaulting on (#37) made every pipeline load print two
libtorch deprecation warnings per pinned tensor - thousands of lines -
because this torch build's Tensor$pin_memory() binding requires the
deprecated device argument (omitting it errors with 'Expected a
torch_device'). torch_empty_strided is the one creation op exposing
pin_memory, so .ltx23_pin_host allocates pinned storage with
contiguous strides and copies in; the noisy device-arg call remains as
a suppressWarnings fallback for builds where that path fails.

GPU validation (is-it-actually-pinned via transfer-rate check) pending
- the card is occupied by a live render session; code-only change,
nothing installed.

* Bump version to 0.1.0.15

* Onload idempotency: probe the staging pair, never degrade to a re-onload

* connector_embeds seam: precomputed text-connector outputs skip the per-call connectors phase

* Pinned Gemma3 staging: pin= on the loaders, staged swap in encode_with_gemma3

* rformat + document
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