Skip to content

Pin FLUX.2's revision and fix the partial-onload wedge in resident LTX - #67

Merged
TroyHernandez merged 7 commits into
mainfrom
feat/pinned-revision
Sep 15, 2026
Merged

TroyHernandez merged 7 commits into
mainfrom
feat/pinned-revision

Conversation

@TroyHernandez

@TroyHernandez TroyHernandez commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

feat/pinned-revision, 0.2.2.7 to 0.2.2.10. The gpuhost image has run this tree since 2026-09-14; merging makes the next build from main carry it.

  • Pin FLUX.2's hub revision, and give resident_load("ltx") its text encoder and tokenizer (0.2.2.8).
  • Stage the resident LTX prompt encode to the card instead of CPU (0.2.2.9).
  • Complete a partial staged onload instead of calling it resident: .staged_on() asks every staging pair, .staged_onload() is idempotent per pair, and encode_with_gemma3() arms its offload before the onload. This is the "mat2 is on cpu" wedge that took the ltx-2.3 entry down twice (0.2.2.10).
  • Staging compares the card, not only the device type, and resident_unload() drops the LTX text encoder.

Tests: test_staged_on.R (pure fakes), additions to test_resident.R and test_text_encode_device.R, and a CUDA partial round trip in test_staging.R.

Check status: both CI jobs pass the workflow, and both end R CMD check with Status: 2 WARNINGs, about vignettes/performance-levers.md having no built copy under inst/doc. main's last run (#66, 25d95f5) ends with the same two; r-ci builds with --no-build-vignettes by default and this workflow does not override it. This branch does not touch vignettes/.

Two things stood between the resident API and a host serving it.

`flux2_load_pipeline()` reached the hub at hfhub's default revision,
the branch `main` -- resolved through `refs/main` and then over the
network. A read-only bind of one snapshot carries neither, so the VAE,
the Qwen3 encoder and the tokenizer all failed to resolve inside a
container. `revision` now threads to all four `.flux2_cached` calls and
a branch name is refused rather than passed through.

`resident_load("ltx")` loaded a pipeline that could not generate.
`ltx23_load_pipeline()` does not load the text encoder and
`txt2vid_ltx2()` takes it per call, so the handle activated fine and
the first generation had no encoder. It now loads Gemma3 pinned on the
CPU and injects it, which also stops a serving caller re-reading 7.6 GB
of encoder for every clip.

The encoder is deliberately kept OUT of `staging`: that list is what
activation moves to the device, and the encoder belongs on the host.
Its pinned bytes are still counted, so the handle's declared host
footprint is the real one.
txt2vid_ltx2() chose the encode device with
`if (is.character(text_encoder)) device else "cpu"`, so a preloaded
encoder -- the resident/gpuhost path, resident_load("ltx", text_encoder
= ...) -- always encoded the prompt on CPU. That path loads the encoder
with pin = TRUE precisely so encode_with_gemma3() can DMA it onto the
card per encode and free it after, but the `else "cpu"` forced CPU and
left the pinned staging unused: every prompt paid the ~24 s CPU encode
instead of the ~7 s staged-GPU one, and on the gpuhost path that is once
per chunk.

The decision moves to .ltx23_text_encode_device(): a path loads onto the
asked-for device; a preloaded encoder that carries a `staging` set and
got a cuda request stages to the card; a bare CPU-resident object still
degrades to CPU, because a cuda request without staging would send the
tokens to the card while the weights sat on the host. Split out so the
rule is asserted without a GPU or a real encode
(test_text_encode_device.R, 8 cases), the way .resident_gen_args is.
The in-process backend is unaffected: it precomputes connector_embeds
and never reaches this branch.
The Gemma3 encoder's staged encode and the LTX pipeline's per-phase
onload both decided "already on the card" by probing the FIRST staging
pair. An onload that dies partway -- device memory runs out with most
of the encoder already copied -- leaves exactly that pair on the card
and the rest on the host, so every later call skipped the onload and
failed on the first matrix multiply with "mat2 is on cpu", on every
request, until the process ended. That is what took the gpuhost's
ltx-2.3 entry down for USA 20260912 on 2026-09-10: one failed encoder
onload at track 2, then the same refusal from every request after it,
with nothing left in the process that could move the encoder back.

Three changes. `.staged_on()` asks every pair, not the first one.
`.staged_onload()` is idempotent per pair: a resident tensor is left
where it is (no re-transfer over itself, which is what the first-pair
probe existed to avoid) and a half-copied component is completed
rather than restarted. And `encode_with_gemma3()` arms its offload
BEFORE the onload, so a failed transfer is undone on the way out and
the next encode starts from a clean host copy and reports the real
error. The LTX onload closure drops its own probe and lets the
per-pair rule decide; plain modules without staging keep the
`$parameters` probe they had.

Covered by test_staged_on.R (pure fakes, no torch) and a partial
round trip added to test_staging.R (CUDA).
Two findings from the 2026-09-11 review of the staging fix.

`.staged_on()` and `.staged_onload()` compared device TYPE only, so a
request for "cuda:1" counted a tensor on cuda:0 as resident, skipped
the transfer, and left the weights on the wrong card. The old first-pair
probe had the same blind spot; the new helper just made it explicit.
`.device_spec()` now carries the index when the caller named one, and
`.on_device()` requires it to match. Bare "cuda" still accepts any card.

`resident_unload()` cleared `staging` and set pinned_bytes to 0 but
never touched `res$text_encoder`, which resident_load("ltx") keeps
outside `staging` on purpose. An unloaded handle therefore held the
Gemma3 encoder's pinned buffers while reporting none. Unload now
offloads the encoder's staging and drops the encoder and tokenizer.

Both covered without a GPU: the card cases in test_staged_on.R and an
unload-with-encoder case in test_resident.R.
@TroyHernandez
TroyHernandez merged commit 518fde1 into main Sep 15, 2026
2 checks passed
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