diff --git a/NEWS.md b/NEWS.md index 128313e..c78e13c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -22,7 +22,6 @@ License and FLUX.1-schnell is gated, so hosting would cover half the catalog and leave two models on a different workflow. - * Model residency: `resident_load()`, `resident_activate()`, `resident_deactivate()`, `resident_generate()`, `resident_status()` and `resident_unload()` keep a pipeline's weights page-locked on the @@ -54,8 +53,9 @@ Addressing the CRAN review of the 0.2.0 submission: for the FLUX, FLUX.2, Z-Image, LTX-2.3 and Gemma3 ports. * Examples: 14 of the 23 `\dontrun{}` blocks now run during check, and were rewritten to be self-contained instead of referencing undefined - objects. The 9 that remain need model weights on disk and are - itemised in `cran-comments.md`. + objects. The 10 that remain need model weights on disk and are + itemised in `cran-comments.md` (the nine left from that pass, plus + `resident_load()`, added below). * `ddim_scheduler_create()` was uncallable at its documented defaults: `beta_schedule` was never passed through `match.arg()`, so `switch()` errored on the length-3 default, and the `device` default was a diff --git a/inst/tinytest/test_bf16_source.R b/inst/tinytest/test_bf16_source.R new file mode 100644 index 0000000..bea9cba --- /dev/null +++ b/inst/tinytest/test_bf16_source.R @@ -0,0 +1,67 @@ +# The bf16 tier reads the unquantized HuggingFace source instead of a +# built artifact, so the risk it carries is cache layout: does +# .flux_source_dir() land on a directory the loader can actually open, +# and is what it finds really bf16? +# +# Scope, deliberately: this checks resolution, on-disk dtype, and that +# flux_open_checkpoint() reports the "full" format that routes to the +# bf16 load path. It does NOT run a forward pass. bf16 names the storage +# dtype, and reading it is CPU work, but *computing* in bf16 is GPU-only +# here -- txt2img_flux2() upcasts to float32 when device == "cpu" -- and +# materializing the 7.8 GB transformer is not a test-suite job. +# +# at_home() gates the whole file: R CMD check machines have no such +# cache. + +library(tinytest) +library(diffuseR) + +if (!at_home()) { + exit_file("bf16 source checks need a populated hfhub cache") +} +if (!requireNamespace("safetensors", quietly = TRUE)) { + exit_file("safetensors not installed") +} + +src <- tryCatch(diffuseR:::.flux_source_dir("flux2"), error = function(e) NULL) +if (is.null(src)) { + exit_file("FLUX.2 bf16 source not in the hfhub cache") +} + +# --- resolution lands somewhere the loader can use -------------------------------- + +expect_true(dir.exists(src)) +expect_true(file.exists(file.path(src, "config.json"))) +expect_true(length(list.files(src, pattern = "[.]safetensors$")) > 0L) + +# The precision router must send bf16 here and NOT to "bf16", +# which is what it did before and why the tier was unreachable. +prefix <- file.path(tools::R_user_dir("diffuseR", "data"), "flux2-klein-4b-") +expect_equal(diffuseR:::.flux_model_dir("flux2", "bf16", prefix), src) +expect_equal(diffuseR:::.flux_model_dir("flux2", "nf4", prefix), + paste0(prefix, "nf4")) + +# --- what is on disk is actually bf16 --------------------------------------------- + +# Read only the safetensors JSON header (8-byte little-endian length, +# then that many bytes), so this stays cheap and loads no tensors. +shard <- list.files(src, pattern = "[.]safetensors$", full.names = TRUE)[1] +con <- file(shard, "rb") +n <- readBin(con, "integer", n = 1L, size = 8L, endian = "little") +hdr <- rawToChar(readBin(con, "raw", n = n)) +close(con) +meta <- jsonlite::fromJSON(hdr) +meta <- meta[names(meta) != "__metadata__"] +dtypes <- unique(vapply(meta, function(x) x$dtype, character(1))) +expect_equal(dtypes, "BF16") + +# --- the checkpoint opens and reports the full (unquantized) format --------------- + +ckpt <- flux_open_checkpoint(src) +expect_inherits(ckpt, "ltx23_checkpoint") +# flux_load_transformer() branches on `format %||% "full"`; a source +# checkpoint carries no format, which is what selects the bf16 path. +expect_null(ckpt$format) +expect_true(length(ckpt$keys) > 0L) +expect_equal(length(ckpt$keys), length(meta)) +expect_equal(ckpt$config[["_class_name"]], "Flux2Transformer2DModel") diff --git a/inst/tinytest/test_recommend.R b/inst/tinytest/test_recommend.R index 1398723..ac8f318 100644 --- a/inst/tinytest/test_recommend.R +++ b/inst/tinytest/test_recommend.R @@ -216,4 +216,3 @@ expect_error(diffuseR:::.flux_source_dir("nosuch"), pattern = "No bf16 source") expect_equal(recommend("ltx", vram_gb = 16)$precision, "nf4") expect_true("precision" %in% names(formals(download_ltx2))) expect_equal(eval(formals(download_ltx2)$precision), c("nf4", "fp8")) -