From a2624bf37479d14c9860732f09ffe1a6dcd5510b Mon Sep 17 00:00:00 2001 From: TroyHernandez Date: Wed, 28 Oct 2026 22:17:19 -0500 Subject: [PATCH 1/2] recommend("ltx25"): nf4 with block streaming for LTX-2.5 LTX-2.5 has only the nf4 artifact, so the 10-14 GB fp8 tier of recommend("ltx") pointed at nothing. The new ltx25 ladder is nf4 on every GPU tier and carries the ltx23_memory_profile fields (resident_gb, stream_text, split_text, resident_from). Staging stays pinned on the GPU tiers. recommend("ltx") (LTX-2.3) is unchanged. --- R/recommend.R | 70 ++++++++++++++++++++++++++++++---- inst/tinytest/test_recommend.R | 39 +++++++++++++++++++ man/recommend.Rd | 27 +++++++++++-- 3 files changed, 126 insertions(+), 10 deletions(-) diff --git a/R/recommend.R b/R/recommend.R index 7cc4209..e708829 100644 --- a/R/recommend.R +++ b/R/recommend.R @@ -25,6 +25,18 @@ #' pipeline uses \code{\link{ltx23_memory_profile}} for frame-aware #' placement. #' +#' \code{"ltx"} is LTX-2.3, which has fp8 and nf4 artifacts: 10-14 GB +#' cards get fp8 streamed per linear. \code{"ltx25"} is LTX-2.5, which +#' has only the nf4 artifact: below 14 GB it keeps part of the nf4 +#' transformer resident and streams the rest, and the result carries +#' the \code{\link{ltx23_memory_profile}} fields that configure that +#' (\code{resident_gb}, \code{stream_text}, \code{split_text}, +#' \code{resident_from}). Its staging is always pinned on the GPU +#' tiers: block streaming from pageable memory is slow, and the +#' host-RAM fields already bound the footprint (measured at 768x512x121 +#' on an 8 GB card: 20.1 GB high-water with resident host copies, 16.0 +#' GB with \code{resident_from = "disk"}). +#' #' The pinning decision: phase-swapped weights are page-locked host #' copies (see \code{\link{staging}}) that transfer at DMA rate - #' but pinned pages are unswappable, so on small-RAM machines they turn @@ -38,7 +50,8 @@ #' components statically and do not phase-swap, so \code{pin} is inert #' for them. \code{options(diffuseR.pin_staging)} is the global switch. #' -#' @param model "sd21", "sdxl", "flux1", "flux2", "zimage", or "ltx". +#' @param model "sd21", "sdxl", "flux1", "flux2", "zimage", "ltx" +#' (LTX-2.3), or "ltx25" (LTX-2.5). #' @param vram_gb Numeric or NULL. Free VRAM in GB; auto-detected via #' nvidia-smi when NULL. #' @param st_caps NULL or a named logical list with \code{bfloat16} @@ -55,7 +68,10 @@ #' copies), \code{pinned_set_gb} (estimated pinned bytes), #' \code{host_ram_gb}, \code{fork_suggested} (logical: the installed #' safetensors cannot read the tier the card could otherwise run), and -#' \code{note} (the suggestion string, or NULL). The field name is +#' \code{note} (the suggestion string, or NULL). For \code{"ltx25"} it +#' also has \code{resident_gb}, \code{stream_text}, \code{split_text} +#' and \code{resident_from} (see \code{\link{ltx23_memory_profile}}). +#' The \code{fork_suggested} field name is #' historical -- the remedy used to be the cornball-ai fork and is now #' \code{install.packages("safetensors")}, since the fixes reached CRAN #' in 0.3.0 -- and is kept because it is part of the returned contract. @@ -77,8 +93,13 @@ #' #' # Auto-detect VRAM and probe the installed safetensors #' str(recommend("flux2")) +#' +#' # LTX-2.5 on an 8 GB card with 24 GB of RAM: nf4, 3.6 GB resident, +#' # text phase split off +#' recommend("ltx25", vram_gb = 7.6, host_ram_gb = 24)[c("precision", +#' "resident_gb", "split_text", "resident_from")] recommend <- function(model = c("sd21", "sdxl", "flux1", "flux2", "zimage", - "ltx"), + "ltx", "ltx25"), vram_gb = NULL, st_caps = NULL, host_ram_gb = NULL) { model <- match.arg(model) if (is.null(vram_gb)) { @@ -124,10 +145,14 @@ recommend <- function(model = c("sd21", "sdxl", "flux1", "flux2", "zimage", host_ram_gb <- .detect_host_ram() } pinned_set <- .pinned_set_gb(model, chosen$precision) - pin <- .pin_decision(model, chosen$precision, host_ram_gb, - cpu = isTRUE(chosen$cpu)) + if (model == "ltx25") { + pin <- !isTRUE(chosen$cpu) + } else { + pin <- .pin_decision(model, chosen$precision, host_ram_gb, + cpu = isTRUE(chosen$cpu)) + } - list( + out <- list( model = model, precision = chosen$precision, devices = chosen$devices, @@ -147,6 +172,18 @@ recommend <- function(model = c("sd21", "sdxl", "flux1", "flux2", "zimage", .bf16_note(model, chosen$precision) } ) + if (model == "ltx25") { + if (isTRUE(chosen$cpu)) { + prof <- list(resident_gb = Inf, stream_text = FALSE, + split_text = FALSE, resident_from = "host") + } else { + prof <- ltx23_memory_profile(vram_gb, host_ram_gb = host_ram_gb) + } + out[c("resident_gb", "stream_text", "split_text", "resident_from")] <- + prof[c("resident_gb", "stream_text", "split_text", + "resident_from")] + } + out } # bf16 is not a built artifact: it is the unquantized source the @@ -200,7 +237,8 @@ recommend <- function(model = c("sd21", "sdxl", "flux1", "flux2", "zimage", flux2 = c(bf16 = 17, fp8 = 12, nf4 = 10), # DiT + Qwen3 bf16 zimage = c(bf16 = 21, fp8 = 14, nf4 = 12), # DiT + Qwen3 bf16 flux3 = c(bf16 = 20, fp8 = 15, nf4 = 12), # DiT + Qwen3-VL text bf16 - ltx = c(fp8 = 34, nf4 = 28) # checkpoint + Gemma3 NF4 + ltx = c(fp8 = 34, nf4 = 28), # checkpoint + Gemma3 NF4 + ltx25 = c(nf4 = 29) # nf4-full checkpoint + Gemma 4 NF4 ) v <- unname(sets[[model]][precision]) if (length(v) != 1L || is.na(v)) { @@ -361,6 +399,24 @@ recommend <- function(model = c("sd21", "sdxl", "flux1", "flux2", "zimage", cpu = TRUE, devices = .dev_flux(FALSE), offload = FALSE, max_pixels = px(512), text_device = "cpu", attn_chunk = NULL) + ), + # LTX-2.5 has only the nf4 artifact. Below 14 GB the transformer + # keeps resident_gb on the GPU and streams the rest (see + # ltx23_memory_profile); 768x512x121 renders in ~84 s on 8 and + # 12 GB, as on 16 GB. + ltx25 = list( + list(precision = "nf4", min_vram = 14, needs = NULL, + devices = .dev_flux(TRUE), offload = TRUE, + max_pixels = px(1280), text_device = "cpu", + attn_chunk = NULL), + list(precision = "nf4", min_vram = 8, needs = NULL, + devices = .dev_flux(TRUE), offload = TRUE, + max_pixels = 768L * 512L, text_device = "cuda", + attn_chunk = NULL), + list(precision = "nf4", min_vram = 0, needs = NULL, + cpu = TRUE, devices = .dev_flux(FALSE), + offload = FALSE, max_pixels = px(512), + text_device = "cpu", attn_chunk = NULL) ) ) } diff --git a/inst/tinytest/test_recommend.R b/inst/tinytest/test_recommend.R index 1aa5eba..9f060bf 100644 --- a/inst/tinytest/test_recommend.R +++ b/inst/tinytest/test_recommend.R @@ -275,5 +275,44 @@ expect_error(diffuseR:::.flux_source_dir("nosuch"), pattern = "No bf16 source") # recommend("ltx") is nf4 on any card >= 14 GB, so download_ltx2() has # to be able to build nf4, not just fp8. expect_equal(recommend("ltx", vram_gb = 16)$precision, "nf4") + +# --- LTX-2.5: nf4 only, streamed below 14 GB -------------------------------------- + +# LTX-2.3 still streams fp8 on 10-14 GB cards; LTX-2.5 has no fp8 +# artifact, so every GPU tier is nf4 and small cards stream blocks +fork <- list(bfloat16 = TRUE, float8_e4m3fn = TRUE) +expect_equal(recommend("ltx", 12, fork, host_ram_gb = 64)$precision, "fp8") +for (v in c(7.6, 12, 16, 24)) { + expect_equal(recommend("ltx25", v, fork, host_ram_gb = 64)$precision, "nf4") + expect_false(recommend("ltx25", v, fork, host_ram_gb = 64)$fork_suggested) +} +r16 <- recommend("ltx25", 16, cran, host_ram_gb = 64) +expect_equal(r16$resident_gb, Inf) +expect_false(r16$stream_text) +expect_equal(r16$text_device, "cpu") +r12 <- recommend("ltx25", 12, cran, host_ram_gb = 64) +expect_equal(r12$resident_gb, 8) +expect_false(r12$stream_text) +expect_true(r12$offload) +r8 <- recommend("ltx25", 7.6, cran, host_ram_gb = 64) +expect_equal(r8$resident_gb, 3.6) +expect_true(r8$stream_text) +expect_equal(r8$text_device, "cuda") +expect_equal(r8$max_pixels, 768L * 512L) +# Host RAM decides the text-phase split and the resident host copy; +# staging stays pinned on GPU tiers even below the 2x pinned-set rule +expect_false(r8$split_text) +expect_equal(r8$resident_from, "host") +low <- recommend("ltx25", 7.6, cran, host_ram_gb = 20) +expect_true(low$split_text) +expect_equal(low$resident_from, "disk") +expect_true(low$pin) +cpu <- recommend("ltx25", 0, cran, host_ram_gb = 64) +expect_equal(cpu$devices$transformer, "cpu") +expect_false(cpu$pin) +expect_false(cpu$split_text) +expect_equal(cpu$resident_from, "host") +# Other models do not grow the LTX fields +expect_false("resident_gb" %in% names(recommend("ltx", 16, cran))) expect_true("precision" %in% names(formals(download_ltx2))) expect_equal(eval(formals(download_ltx2)$precision), c("nf4", "fp8")) diff --git a/man/recommend.Rd b/man/recommend.Rd index 95d08b4..1923785 100644 --- a/man/recommend.Rd +++ b/man/recommend.Rd @@ -4,14 +4,15 @@ \title{Recommend a precision and device configuration for a model} \usage{ recommend( - model = c("sd21", "sdxl", "flux1", "flux2", "zimage", "ltx"), + model = c("sd21", "sdxl", "flux1", "flux2", "zimage", "ltx", "ltx25"), vram_gb = NULL, st_caps = NULL, host_ram_gb = NULL ) } \arguments{ -\item{model}{"sd21", "sdxl", "flux1", "flux2", "zimage", or "ltx".} +\item{model}{"sd21", "sdxl", "flux1", "flux2", "zimage", "ltx" +(LTX-2.3), or "ltx25" (LTX-2.5).} \item{vram_gb}{Numeric or NULL. Free VRAM in GB; auto-detected via nvidia-smi when NULL.} @@ -32,7 +33,10 @@ A list with \code{model}, \code{precision}, \code{devices} copies), \code{pinned_set_gb} (estimated pinned bytes), \code{host_ram_gb}, \code{fork_suggested} (logical: the installed safetensors cannot read the tier the card could otherwise run), and - \code{note} (the suggestion string, or NULL). The field name is + \code{note} (the suggestion string, or NULL). For \code{"ltx25"} it + also has \code{resident_gb}, \code{stream_text}, \code{split_text} + and \code{resident_from} (see \code{\link{ltx23_memory_profile}}). + The \code{fork_suggested} field name is historical -- the remedy used to be the cornball-ai fork and is now \code{install.packages("safetensors")}, since the fixes reached CRAN in 0.3.0 -- and is kept because it is part of the returned contract. @@ -64,6 +68,18 @@ elsewhere. Video sizing for \code{"ltx"} is coarse here; the LTX pipeline uses \code{\link{ltx23_memory_profile}} for frame-aware placement. +\code{"ltx"} is LTX-2.3, which has fp8 and nf4 artifacts: 10-14 GB +cards get fp8 streamed per linear. \code{"ltx25"} is LTX-2.5, which +has only the nf4 artifact: below 14 GB it keeps part of the nf4 +transformer resident and streams the rest, and the result carries +the \code{\link{ltx23_memory_profile}} fields that configure that +(\code{resident_gb}, \code{stream_text}, \code{split_text}, +\code{resident_from}). Its staging is always pinned on the GPU +tiers: block streaming from pageable memory is slow, and the +host-RAM fields already bound the footprint (measured at 768x512x121 +on an 8 GB card: 20.1 GB high-water with resident host copies, 16.0 +GB with \code{resident_from = "disk"}). + The pinning decision: phase-swapped weights are page-locked host copies (see \code{\link{staging}}) that transfer at DMA rate - but pinned pages are unswappable, so on small-RAM machines they turn @@ -93,4 +109,9 @@ recommend("flux1", vram_gb = 16, # Auto-detect VRAM and probe the installed safetensors str(recommend("flux2")) + +# LTX-2.5 on an 8 GB card with 24 GB of RAM: nf4, 3.6 GB resident, +# text phase split off +recommend("ltx25", vram_gb = 7.6, host_ram_gb = 24)[c("precision", + "resident_gb", "split_text", "resident_from")] } From 2b3917f2ff6097d9af1dfbd2cc6fc2153afaeec0 Mon Sep 17 00:00:00 2001 From: TroyHernandez Date: Wed, 28 Oct 2026 22:17:32 -0500 Subject: [PATCH 2/2] Bump version to 0.2.2.19 --- DESCRIPTION | 2 +- NEWS.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 465b631..2389ece 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: diffuseR Title: Functional Interface to Diffusion Models in R -Version: 0.2.2.18 +Version: 0.2.2.19 Authors@R: c( person("Troy", "Hernandez", email = "troy@cornball.ai", role = c("aut", "cre"), comment = c(ORCID = "0009-0005-4248-604X")), diff --git a/NEWS.md b/NEWS.md index 0e1e362..54ff755 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,13 @@ +# diffuseR 0.2.2.19 + +* `recommend("ltx25")` covers LTX-2.5. `recommend("ltx")` (LTX-2.3) + still prescribes streamed fp8 on 10-14 GB cards, which LTX-2.5 has no + artifact for. The `"ltx25"` ladder is nf4 on every GPU tier: resident + at 14 GB and up, streamed blocks below (768x512 cap). The result adds + the `ltx23_memory_profile()` fields `resident_gb`, `stream_text`, + `split_text` and `resident_from`, and keeps staging pinned on the GPU + tiers. + # diffuseR 0.2.2.18 * **LTX-2.5 on 8 and 12 GB cards.** The NF4 transformer can keep part