Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
^inst/validation/\.venv$
^ref$
^tasks$
^inst/tinytest/fixtures$
4 changes: 3 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: diffuseR
Title: Functional Interface to Diffusion Models in R
Version: 0.1.0.16
Version: 0.2.0
Authors@R: c(
person("Troy", "Hernandez", email = "troy@cornball.ai", role = c("aut", "cre"),
comment = c(ORCID = "0009-0005-4248-604X")),
Expand Down Expand Up @@ -30,5 +30,7 @@ Suggests:
av,
hfhub,
safetensors,
simplermarkdown,
tinytest
VignetteBuilder: simplermarkdown
RoxygenNote: 7.3.3
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export(scheduler_add_noise)
export(sd_pipeline_from_safetensors)
export(sdxl_memory_profile)
export(sdxl_pipeline_from_safetensors)
export(serve)
export(t5_encoder)
export(text_encoder_native)
export(text_encoder_native_from_safetensors)
Expand Down
325 changes: 115 additions & 210 deletions NEWS.md

Large diffs are not rendered by default.

19 changes: 13 additions & 6 deletions R/download_component.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,23 @@ download_component <- function(model_name = "sd21", component,
device = "cpu", overwrite = FALSE,
show_progress = TRUE) {
filename <- paste0(component, "-", device, ".pt")
if (!requireNamespace("hfhub", quietly = TRUE)) {
stop("Package 'hfhub' is required. Install with: install.packages('hfhub')")
}
repo_id <- paste0("cornball-ai/", model_name, "-R")

cached <- !overwrite && !is.null(tryCatch(
hfhub::hub_download(repo_id, filename, repo_type = "dataset",
local_files_only = TRUE),
error = function(e) NULL))
if (!cached &&
!.ltx23_consent(paste0(model_name, " ", component, " (", filename, ")"))) {
stop("Download cancelled.", call. = FALSE)
}

if (overwrite) {
# Force download (bypass cache)
if (!requireNamespace("hfhub", quietly = TRUE)) {
stop("Package 'hfhub' is required. Install with: install.packages('hfhub')")
}
repo_id <- paste0("cornball-ai/", model_name, "-R")
return(hfhub::hub_download(repo_id, filename, repo_type = "dataset",
force_download = TRUE))
}

hf_download_pt(model_name, filename, download = TRUE)
}
8 changes: 2 additions & 6 deletions R/download_flux.R
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ download_flux1 <- function(quantize = TRUE, precision = c("nf4", "fp8"),
local_files_only = TRUE),
error = function(e) NULL
)
if (is.null(cached) && !have_artifact) {
if (is.null(cached) || !.hub_all_cached(.flux1_repo, .flux1_transformer_files)) {
free <- .ltx23_disk_free_gb(path.expand("~"))
if (!is.na(free) && free < 45) {
warning(sprintf(
Expand Down Expand Up @@ -148,11 +148,7 @@ download_flux1 <- function(quantize = TRUE, precision = c("nf4", "fp8"),

if (text_encoders) {
files <- c(.flux1_support_files, .flux1_t5_files)
have_t5 <- !is.null(tryCatch(
hfhub::hub_download(.flux1_repo, .flux1_t5_files[[3]],
local_files_only = TRUE),
error = function(e) NULL
))
have_t5 <- .hub_all_cached(.flux1_repo, files)
if (!have_t5) {
ok <- .ltx23_consent(
"the FLUX text encoders, VAE, and tokenizer (~10 GB)"
Expand Down
8 changes: 2 additions & 6 deletions R/download_flux2.R
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ download_flux2_klein <- function(quantize = TRUE,
local_files_only = TRUE),
error = function(e) NULL
)
if (is.null(cached) && !have_artifact) {
if (is.null(cached) || !.hub_all_cached(.flux2_repo, .flux2_transformer_files)) {
free <- .ltx23_disk_free_gb(path.expand("~"))
if (!is.na(free) && free < 25) {
warning(sprintf(
Expand Down Expand Up @@ -119,11 +119,7 @@ download_flux2_klein <- function(quantize = TRUE,
}

if (text_encoders) {
have_te <- !is.null(tryCatch(
hfhub::hub_download(.flux2_repo, .flux2_support_files[[5]],
local_files_only = TRUE),
error = function(e) NULL
))
have_te <- .hub_all_cached(.flux2_repo, .flux2_support_files)
if (!have_te) {
ok <- .ltx23_consent(
"the Qwen3-4B text encoder, tokenizer, and VAE (~8.3 GB)"
Expand Down
23 changes: 17 additions & 6 deletions R/download_ltx23.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ download_ltx2 <- function(quantize = TRUE,
local_files_only = TRUE),
error = function(e) NULL
)
if (is.null(cached) && !have_fp8) {
if (is.null(cached)) {
free <- .ltx23_disk_free_gb(path.expand("~"))
if (!is.na(free) && free < 75) {
warning(sprintf(
Expand Down Expand Up @@ -122,11 +122,7 @@ download_ltx2 <- function(quantize = TRUE,
"tokenizer/tokenizer.json", "tokenizer/tokenizer_config.json",
"tokenizer/special_tokens_map.json"
)
have_te <- !is.null(tryCatch(
hfhub::hub_download(.ltx23_text_encoder_repo, te_files[[2]],
local_files_only = TRUE),
error = function(e) NULL
))
have_te <- .hub_all_cached(.ltx23_text_encoder_repo, te_files)
if (!have_te) {
ok <- .ltx23_consent(
"the Gemma3 text encoder and tokenizer (~25 GB, Lightricks/LTX-2)"
Expand All @@ -147,3 +143,18 @@ download_ltx2 <- function(quantize = TRUE,

invisible(result)
}

# TRUE when every file is already in the local hub cache, i.e. no
# network fetch will happen. Consent gates key on this - never on a
# single sentinel file, and never on artifact presence (a completed
# artifact must not license re-downloading sources).
.hub_all_cached <- function(repo, files, repo_type = NULL) {
all(vapply(files, function(f) {
args <- list(repo, f, local_files_only = TRUE)
if (!is.null(repo_type)) {
args$repo_type <- repo_type
}
!is.null(tryCatch(do.call(hfhub::hub_download, args),
error = function(e) NULL))
}, logical(1)))
}
14 changes: 5 additions & 9 deletions R/download_model.R
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,12 @@ download_model <- function(model_name = "sd21",

needs_download <- !already_available | overwrite

# Interactive consent when files actually need downloading
# Consent when files actually need downloading: interactive prompt,
# or the diffuseR.consent option in non-interactive sessions
if (download_models && any(needs_download)) {
if (interactive()) {
ans <- utils::askYesNo(
paste0("Download '", model_name, "' model files from HuggingFace?"),
default = TRUE
)
if (!isTRUE(ans)) {
stop("Download cancelled.", call. = FALSE)
}
if (!.ltx23_consent(paste0("'", model_name,
"' model files from HuggingFace"))) {
stop("Download cancelled.", call. = FALSE)
}
}

Expand Down
8 changes: 2 additions & 6 deletions R/download_zimage.R
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ download_zimage_turbo <- function(quantize = TRUE,
local_files_only = TRUE),
error = function(e) NULL
)
if (is.null(cached) && !have_artifact) {
if (is.null(cached) || !.hub_all_cached(.zimage_repo, .zimage_transformer_files)) {
free <- .ltx23_disk_free_gb(path.expand("~"))
if (!is.na(free) && free < 35) {
warning(sprintf(
Expand Down Expand Up @@ -125,11 +125,7 @@ download_zimage_turbo <- function(quantize = TRUE,
}

if (text_encoders) {
have_te <- !is.null(tryCatch(
hfhub::hub_download(.zimage_repo, .zimage_support_files[[5]],
local_files_only = TRUE),
error = function(e) NULL
))
have_te <- .hub_all_cached(.zimage_repo, .zimage_support_files)
if (!have_te) {
ok <- .ltx23_consent(
"the Qwen3-4B text encoder, tokenizer, and VAE (~8.2 GB)"
Expand Down
10 changes: 7 additions & 3 deletions R/fp8_ltx23.R
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ ltx23_fp8_linear <- torch::nn_module(
#' original key names plus a manifest for skip-if-exists.
#'
#' @param checkpoint_path Source .safetensors (46 GB bf16 single file).
#' @param output_dir Output directory for shards + manifest.
#' @param output_dir Output directory for shards + manifest; NULL (the
#' default) resolves under tools::R_user_dir("diffuseR", "data").
#' @param shard_bytes Numeric. Target shard size in bytes. The default
#' 1.9e9 keeps every shard under the 2^31-byte (~2.15 GB) ceiling that
#' stock CRAN safetensors can read. Pass a larger value (e.g. 4e9) only
Expand All @@ -115,10 +116,13 @@ ltx23_fp8_linear <- torch::nn_module(
#' @return Invisibly, the manifest list.
#'
#' @export
ltx23_quantize_fp8 <- function(checkpoint_path,
output_dir = file.path(tools::R_user_dir("diffuseR", "data"), "ltx2.3-fp8"),
ltx23_quantize_fp8 <- function(checkpoint_path, output_dir = NULL,
shard_bytes = 1.9e9, force = FALSE,
verbose = TRUE) {
if (is.null(output_dir)) {
output_dir <- file.path(tools::R_user_dir("diffuseR", "data"),
"ltx2.3-fp8")
}
manifest_path <- file.path(output_dir, "manifest.json")
if (!force && file.exists(manifest_path)) {
manifest <- jsonlite::fromJSON(manifest_path)
Expand Down
10 changes: 7 additions & 3 deletions R/nf4_ltx23.R
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ ltx23_nf4_linear <- torch::nn_module(
#' \code{format = "nf4"}.
#'
#' @param checkpoint_path Source .safetensors (bf16 single file).
#' @param output_dir Output directory for shards + manifest.
#' @param output_dir Output directory for shards + manifest; NULL (the
#' default) resolves under tools::R_user_dir("diffuseR", "data").
#' @param shard_bytes Numeric. Target shard size in bytes. The default
#' 1.9e9 keeps every shard under the 2^31-byte (~2.15 GB) ceiling that
#' stock CRAN safetensors can read. Pass a larger value (e.g. 4e9) only
Expand All @@ -262,10 +263,13 @@ ltx23_nf4_linear <- torch::nn_module(
#' @return Invisibly, the manifest list.
#'
#' @export
ltx23_quantize_nf4 <- function(checkpoint_path,
output_dir = file.path(tools::R_user_dir("diffuseR", "data"), "ltx2.3-nf4"),
ltx23_quantize_nf4 <- function(checkpoint_path, output_dir = NULL,
shard_bytes = 1.9e9, force = FALSE,
verbose = TRUE) {
if (is.null(output_dir)) {
output_dir <- file.path(tools::R_user_dir("diffuseR", "data"),
"ltx2.3-nf4")
}
manifest_path <- file.path(output_dir, "manifest.json")
if (!force && file.exists(manifest_path)) {
manifest <- jsonlite::fromJSON(manifest_path)
Expand Down
23 changes: 13 additions & 10 deletions R/recommend.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
#' model. The policy:
#'
#' \itemize{
#' \item nf4 is the default tier. Its weights are packed uint8 plus
#' float32 blocks in sub-2 GB shards, which every safetensors reads,
#' so it always loads.
#' \item nf4 is the default tier for the quantized families (flux1,
#' flux2, zimage, ltx). Its weights are packed uint8 plus float32
#' blocks in sub-2 GB shards, which every safetensors reads, so it
#' always loads. The SD models ship no quantized weights; their
#' floor is fp16 with placement varying by VRAM.
#' \item When the card has room for a higher-quality tier (fp8 or
#' bf16) AND the installed safetensors can \emph{read} that dtype
#' (\code{\link{.st_can_read}}), that tier is recommended instead.
Expand Down Expand Up @@ -161,8 +163,8 @@ recommend <- function(model = c("sd21", "sdxl", "flux1", "flux2", "zimage",
# order of magnitude, not the byte.
.pinned_set_gb <- function(model, precision) {
sets <- list(
sd21 = c(fp16 = 3, nf4 = 2),
sdxl = c(fp16 = 8, nf4 = 4),
sd21 = c(fp16 = 3),
sdxl = c(fp16 = 8),
flux1 = c(bf16 = 43, fp8 = 31, nf4 = 26), # DiT + T5 fp32 host copy
flux2 = c(bf16 = 17, fp8 = 12, nf4 = 10), # DiT + Qwen3 bf16
zimage = c(bf16 = 21, fp8 = 14, nf4 = 12), # DiT + Qwen3 bf16
Expand Down Expand Up @@ -213,18 +215,19 @@ recommend <- function(model = c("sd21", "sdxl", "flux1", "flux2", "zimage",
)
}

# SD-family ladder: fp16 for cards that fit the full model, nf4 default
# for tighter cards, cpu otherwise. Both dtypes are CRAN-readable, so no
# fork gate. Device maps reuse the auto_devices strategy builder.
# SD-family ladder: the SD models ship no quantized weights, so every
# tier is fp16 - what varies is placement (all-GPU, UNet-only-GPU, or
# CPU) and the area budget. Device maps reuse the auto_devices
# strategy builder.
.sd_tiers <- function(model, fp16_vram, nf4_vram, max_fp16, max_nf4, max_cpu) {
list(
list(precision = "fp16", min_vram = fp16_vram, needs = NULL,
devices = .build_fallback_devices(model, "full_gpu"),
offload = FALSE, max_pixels = max_fp16),
list(precision = "nf4", min_vram = nf4_vram, needs = NULL,
list(precision = "fp16", min_vram = nf4_vram, needs = NULL,
devices = .build_fallback_devices(model, "unet_gpu"),
offload = TRUE, max_pixels = max_nf4),
list(precision = "nf4", min_vram = 0, needs = NULL, cpu = TRUE,
list(precision = "fp16", min_vram = 0, needs = NULL, cpu = TRUE,
devices = .build_fallback_devices(model, "cpu_only"),
offload = FALSE, max_pixels = max_cpu)
)
Expand Down
6 changes: 1 addition & 5 deletions R/sd_pipeline_safetensors.R
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,7 @@ download_sd21 <- function(verbose = TRUE) {
if (!requireNamespace("hfhub", quietly = TRUE)) {
stop("The hfhub package is required to download model weights.")
}
have <- !is.null(tryCatch(
hfhub::hub_download(.sd21_repo, .sd21_files[[1]],
repo_type = "dataset", local_files_only = TRUE),
error = function(e) NULL
))
have <- .hub_all_cached(.sd21_repo, .sd21_files, repo_type = "dataset")
if (!have) {
ok <- .ltx23_consent(
"Stable Diffusion 2.1 UNet + VAE + CLIP text encoder (~2.5 GB)"
Expand Down
10 changes: 6 additions & 4 deletions R/sdxl_pipeline_safetensors.R
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,7 @@ download_sdxl <- function(verbose = TRUE) {
if (!requireNamespace("hfhub", quietly = TRUE)) {
stop("The hfhub package is required to download model weights.")
}
have <- !is.null(tryCatch(
hfhub::hub_download(.sdxl_repo, .sdxl_files[[1]],
repo_type = "dataset", local_files_only = TRUE),
error = function(e) NULL))
have <- .hub_all_cached(.sdxl_repo, .sdxl_files, repo_type = "dataset")
if (!have) {
ok <- .ltx23_consent(
"Stable Diffusion XL UNet + VAE + 2 CLIP text encoders (~7 GB)")
Expand All @@ -180,6 +177,11 @@ download_sdxl <- function(verbose = TRUE) {
index_path <- paths[[length(.sdxl_files)]]
idx <- jsonlite::fromJSON(index_path, simplifyVector = TRUE)
shards <- unique(unlist(idx$weight_map))
shard_files <- paste0("diffusers/unet/", shards)
if (!.hub_all_cached(.sdxl_repo, shard_files, repo_type = "dataset") &&
!.ltx23_consent("the remaining SDXL UNet shards")) {
stop("Download cancelled.", call. = FALSE)
}
for (s in shards) {
hfhub::hub_download(.sdxl_repo, paste0("diffusers/unet/", s),
repo_type = "dataset")
Expand Down
Loading
Loading