Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
c8f5dbf
Remove LTX-2.0 implementation ahead of clean-room LTX-2.3 rewrite
TroyHernandez Jul 3, 2026
d8f25df
License hygiene: attribution, ignores, CLAUDE.md provenance rewrite
TroyHernandez Jul 3, 2026
57e8f9e
Add LTX-2.3 single-file checkpoint reader
TroyHernandez Jul 3, 2026
e02340d
Port LTX-2.3 rotary position embeddings from diffusers
TroyHernandez Jul 3, 2026
6a72a88
Port LTX-2.3 audio-video DiT from diffusers
TroyHernandez Jul 3, 2026
6271f5c
Port LTX-2.3 text connectors from diffusers
TroyHernandez Jul 3, 2026
66e6aae
Port LTX-2.3 causal video VAE from diffusers
TroyHernandez Jul 3, 2026
ffbde8d
Port LTX-2.3 audio VAE decoder and BWE vocoder from diffusers
TroyHernandez Jul 3, 2026
11a5128
Add LTX-2.3 distilled text-to-video pipeline
TroyHernandez Jul 3, 2026
c823628
Add fp8 quantization, memory profiles, and download UX
TroyHernandez Jul 3, 2026
3f53bb9
Add spatial latent upsampler and two-stage generation
TroyHernandez Jul 3, 2026
3f48020
Add treesitR coverage report for the LTX-2.3 port
TroyHernandez Jul 3, 2026
2ea1bc9
Handle bfloat16 in the connector mask (torch_finfo fallback)
TroyHernandez Jul 3, 2026
68918fa
rformat + document
TroyHernandez Jul 3, 2026
484f7e9
Fix R CMD check warnings
TroyHernandez Jul 3, 2026
dfc9542
Phase-sequential component offloading
TroyHernandez Jul 3, 2026
57b4b1d
Add NF4 quantization with a GPU-resident transformer profile
TroyHernandez Jul 3, 2026
13295e0
Encode precision strategy in the LTX-2.3 memory profiles
TroyHernandez Jul 3, 2026
cbe326f
Reuse NF4 dequantization buffers to eliminate allocator churn
TroyHernandez Jul 3, 2026
597ac8c
Make NF4 dequantization fully in-place over persistent scratch
TroyHernandez Jul 3, 2026
eae326b
Add per-block VRAM tracing behind diffuseR.debug
TroyHernandez Jul 3, 2026
2a0abfc
Extend phase offloading to the transformer
TroyHernandez Jul 4, 2026
117b956
Adaptive attention chunking and per-step timing
TroyHernandez Jul 4, 2026
92c3b66
Buffered attention: score/context matrices in persistent scratch
TroyHernandez Jul 4, 2026
39cb7a4
Validated squeeze wrap-up: step timing, expandable segments, honest caps
TroyHernandez Jul 4, 2026
465aca5
rformat + document
TroyHernandez Jul 4, 2026
a131c58
Tiled VAE decoding and corrected allocator tuning
TroyHernandez Jul 4, 2026
1ae52c9
Document technique provenance in inst/REFERENCES.md
TroyHernandez Jul 4, 2026
264a734
In-place chunked tanh GELU for large feed-forward activations
TroyHernandez Jul 4, 2026
cd1140d
Default attn budget 1.5e8; raise NF4 profile cap to 1280x704
TroyHernandez Jul 4, 2026
b4429c2
rformat + document
TroyHernandez Jul 4, 2026
1a1a85b
Match HF hidden-state semantics in the Gemma3 encoder
TroyHernandez Jul 4, 2026
af5f811
Remove committed Python venv from inst/validation
TroyHernandez Jul 4, 2026
9f16a84
Fix R CMD check findings: Rd percent escape, codetools-visible fn
TroyHernandez Jul 4, 2026
e5b6966
Bump version to 0.1.0
TroyHernandez Jul 4, 2026
0012690
TorchScript-compiled NF4 block stack
TroyHernandez Jul 4, 2026
ea66862
Fix JIT stack for real-model layouts; 11.4x measured
TroyHernandez Jul 4, 2026
fbda48d
Reference the JIT block-stack provenance
TroyHernandez Jul 4, 2026
83dac5f
rformat + document
TroyHernandez Jul 4, 2026
1adcaff
JIT-traced VAE decode and audio chain
TroyHernandez Jul 7, 2026
d2859ca
Traced decode (opt-in), allocator findings, tune_gc VRAM fallback
TroyHernandez Jul 7, 2026
1a9010e
Apply the full mlverse allocator knob set; per-format allocator default
TroyHernandez Jul 7, 2026
903a9a0
FLUX RoPE, position ids, and latent pack/unpack (parity-tested)
TroyHernandez Jul 7, 2026
3c65d05
rformat + document
TroyHernandez Jul 7, 2026
4a15ea8
FLUX MMDiT blocks: adaLN variants, joint attention, double/single blo…
TroyHernandez Jul 7, 2026
7e72b25
rformat + document
TroyHernandez Jul 7, 2026
6f3d573
FLUX transformer assembly with parity fixture; tensor-method scalar adds
TroyHernandez Jul 7, 2026
34b7708
rformat + document
TroyHernandez Jul 7, 2026
dd58c71
FLUX checkpoint readers and NF4/fp8 quantize + format-dispatching loader
TroyHernandez Jul 7, 2026
14cf9c0
rformat + document
TroyHernandez Jul 7, 2026
6d6d0c1
Unigram (T5/SentencePiece) tokenizer with Viterbi segmentation
TroyHernandez Jul 7, 2026
8a805c3
rformat + document
TroyHernandez Jul 7, 2026
4c190e6
T5-v1.1 encoder: relative position bias, gated-GELU, sharded loader
TroyHernandez Jul 7, 2026
c5c8c95
rformat + document
TroyHernandez Jul 7, 2026
b0d7ea9
CLIP pooled output + safetensors loaders; parameterize VAE decoder ch…
TroyHernandez Jul 7, 2026
c7eb100
rformat + document
TroyHernandez Jul 7, 2026
e93e233
FLUX.1-schnell pipeline: loader, denoise loop, download flow, memory …
TroyHernandez Jul 7, 2026
1b3f119
rformat + document
TroyHernandez Jul 7, 2026
1831e2f
Check hygiene: unicode escapes, fixture metadata, flux_quantize outpu…
TroyHernandez Jul 7, 2026
f489e77
treesitR structural translation audit tool
TroyHernandez Jul 7, 2026
18dcf13
Tokenizer parity vs shipped artifact: prepend-always cases, empty-inp…
TroyHernandez Jul 7, 2026
9f43a3e
Quantized residents follow the compute dtype (fp32 on CPU)
TroyHernandez Jul 7, 2026
e1f8263
CLAUDE.md: FLUX.1-schnell section with measured numbers
TroyHernandez Jul 7, 2026
dc1ad95
FLUX.2 position ids, 4-axis RoPE reuse, latent patchify chain, empiri…
TroyHernandez Jul 7, 2026
165cc92
rformat + document
TroyHernandez Jul 7, 2026
fc14bba
FLUX.2 blocks: shared modulation, SwiGLU FF, parallel single block
TroyHernandez Jul 7, 2026
5b50182
rformat + document
TroyHernandez Jul 7, 2026
ae0a9b6
FLUX.2 transformer assembly; bias params on shared timestep/norm modules
TroyHernandez Jul 7, 2026
b66bec4
rformat + document
TroyHernandez Jul 7, 2026
45e397c
FLUX.2 quantization: family dispatch via config class, resident fp8
TroyHernandez Jul 7, 2026
99e2616
rformat + document
TroyHernandez Jul 7, 2026
f953418
Qwen2 byte-level BPE tokenizer with Qwen3 chat template
TroyHernandez Jul 7, 2026
222ce3d
rformat + document
TroyHernandez Jul 7, 2026
4616395
Qwen3 text encoder: GQA, q/k norms, padding mask, mid-stack states
TroyHernandez Jul 7, 2026
e591749
rformat + document
TroyHernandez Jul 7, 2026
0e1663f
FLUX.2 VAE decoder: post_quant_conv, BN latent statistics, decoder reuse
TroyHernandez Jul 7, 2026
0b63f36
FLUX.2 klein pipeline: loader, denoise loop, download flow, dispatcher
TroyHernandez Jul 7, 2026
997aa27
rformat + document
TroyHernandez Jul 7, 2026
7f0f8a3
Extend treesitR audit with FLUX.2 and Qwen3 pairings
TroyHernandez Jul 7, 2026
069d119
REFERENCES.md: FLUX.2 klein provenance
TroyHernandez Jul 7, 2026
5d640fb
Perf: integer-id BPE (gc 203ms->19ms), buffered fp8 dequant, allocato…
TroyHernandez Jul 7, 2026
872232d
CLAUDE.md: FLUX.2 klein section with measured numbers
TroyHernandez Jul 7, 2026
3491b7b
Merge main (FLUX.1 squash) into feature/flux2-klein
TroyHernandez Jul 7, 2026
0ab32cb
Bump version to 0.1.0.2
TroyHernandez Jul 7, 2026
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
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ See cornyverse CLAUDE.md for safetensors package setup (use cornball-ai fork unt

### Model Support
- [x] Add FLUX model support (FLUX.1-schnell, see below)
- [x] Add FLUX.2 support (klein-4B, see below)
- [ ] Add SD3 model support
- [ ] ControlNet integration

Expand Down Expand Up @@ -347,6 +348,31 @@ the shipped tokenizer_2 uses Metaspace prepend "always" (a spiece
conversion gives "never" - different ids for every prompt), and quantized
residents must follow the compute dtype (bf16 GPU / fp32 CPU).

### FLUX.2 Klein 4B (Complete)

Step-distilled FLUX.2 (4 steps, no CFG): 4B MMDiT (shared modulation,
SwiGLU, parallel single blocks) + Qwen3-4B text encoder (chat template,
mid-stack hidden states) + 32-channel `AutoencoderKLFlux2` (BatchNorm
latent stats) + FlowMatch with the BFL empirical dynamic shift.

```r
download_flux2_klein() # ungated, Apache-2.0; ~16 GB download,
# one-time fp8 quantize to a 3.9 GB artifact
txt2img_flux2("An astronaut riding a horse on Mars, photorealistic",
seed = 7) # or txt2img("...", model_name = "flux2")
```

Measured on the RTX 5060 Ti 16 GB (fp8 GPU-resident, Qwen3 bf16
phase-onloaded): 1024x1024 in ~48 s (peak 8.2 GB), 512x512 in ~40 s;
pipeline load 31 s. Cast census is exactly 104 weights.

Perf lesson that cost an afternoon: generation was 93.8% R garbage
collection until the tokenizer stopped holding 151k-binding
environments — R gc cost scales with live object count (12 vs 203 ms),
and torch's allocator callbacks run gc hundreds of times per
generation. Keep big lookup tables as atomic vectors (integer-id BPE
via findInterval), never as environments.

### LTX-2.3 Video Generation (clean-room rewrite in progress)

The original LTX-2.0 port was removed and is being replaced by a ground-up
Expand Down
2 changes: 1 addition & 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.1
Version: 0.1.0.2
Authors@R: c(
person("Troy", "Hernandez", email = "troy@cornball.ai", role = c("aut", "cre"),
comment = c(ORCID = "0009-0005-4248-604X")),
Expand Down
26 changes: 26 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ export(ddim_scheduler_step)
export(decode_bpe)
export(download_component)
export(download_flux1)
export(download_flux2_klein)
export(download_ltx2)
export(download_model)
export(encode_bpe)
export(encode_qwen)
export(encode_unigram)
export(encode_with_gemma3)
export(encode_with_qwen3)
export(encode_with_t5)
export(filename_from_prompt)
export(flowmatch_calculate_shift)
Expand All @@ -41,6 +44,23 @@ export(flux_quantize)
export(flux_single_block)
export(flux_transformer)
export(flux_unpack_latents)
export(flux2_bn_normalize)
export(flux2_double_block)
export(flux2_empirical_mu)
export(flux2_feed_forward)
export(flux2_is_quant_key)
export(flux2_load_pipeline)
export(flux2_modulation)
export(flux2_pack_latents)
export(flux2_parallel_self_attention)
export(flux2_patchify_latents)
export(flux2_prepare_latent_ids)
export(flux2_prepare_text_ids)
export(flux2_single_block)
export(flux2_transformer)
export(flux2_unpack_latents_with_ids)
export(flux2_unpatchify_latents)
export(flux2_vae_decoder)
export(gemma3_config_ltx2)
export(gemma3_text_model)
export(gemma3_tokenizer)
Expand All @@ -49,9 +69,11 @@ export(is_blackwell_gpu)
export(latents_to_video)
export(load_decoder_safetensors)
export(load_decoder_weights)
export(load_flux2_vae_decoder)
export(load_gemma3_text_encoder)
export(load_model_component)
export(load_pipeline)
export(load_qwen3_text_encoder)
export(load_t5_text_encoder)
export(load_text_encoder_safetensors)
export(load_text_encoder_weights)
Expand Down Expand Up @@ -144,6 +166,8 @@ export(offload_to_cpu)
export(post_quant_conv)
export(preprocess_image)
export(quant_conv)
export(qwen_bpe_tokenizer)
export(qwen3_encoder)
export(save_frames)
export(save_image)
export(save_video)
Expand All @@ -156,6 +180,7 @@ export(text_encoder2_native)
export(tokenize_gemma3)
export(txt2img)
export(txt2img_flux)
export(txt2img_flux2)
export(txt2img_sd21)
export(txt2img_sdxl)
export(txt2vid_ltx2)
Expand All @@ -171,6 +196,7 @@ export(write_wav)

S3method(print,bpe_tokenizer)
S3method(print,ltx23_checkpoint)
S3method(print,qwen_tokenizer)
S3method(print,unigram_tokenizer)

importFrom(utils,head)
40 changes: 40 additions & 0 deletions R/checkpoint_flux.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ flux_is_quant_key <- function(key) {
grepl(.flux_quant_cast_pattern, key)
}

# FLUX.2 cast set: block linears, the three shared modulation
# projections, and the context embedder (7680 x 3072). Everything else
# (x_embedder, timestep MLP, norm_out, q/k norms) stays in the resident
# dtype. Full klein-4B census: 5 double x 12 + 20 single x 2 + 3
# modulations + context_embedder = 104 cast weights, ~3.9B of 4B params.
.flux2_quant_cast_pattern <- paste0(
"^(",
"transformer_blocks\\.[0-9]+\\.(",
"attn\\.(to_q|to_k|to_v|add_q_proj|add_k_proj|add_v_proj|to_out\\.0|to_add_out)",
"|ff\\.(linear_in|linear_out)|ff_context\\.(linear_in|linear_out)",
")",
"|single_transformer_blocks\\.[0-9]+\\.attn\\.(to_qkv_mlp_proj|to_out)",
"|double_stream_modulation_img\\.linear",
"|double_stream_modulation_txt\\.linear",
"|single_stream_modulation\\.linear",
"|context_embedder",
")\\.weight$"
)

#' Test whether a FLUX.2 key is in the quantization cast set
#'
#' @param key Character vector of parameter names (diffusers-style).
#'
#' @return Logical vector.
#'
#' @export
flux2_is_quant_key <- function(key) {
grepl(.flux2_quant_cast_pattern, key)
}

# Model family from a diffusers transformer config
.flux_family <- function(config) {
cls <- config$`_class_name` %||% "FluxTransformer2DModel"
if (identical(cls, "Flux2Transformer2DModel")) {
"flux2"
} else {
"flux1"
}
}

#' Open a FLUX transformer checkpoint directory
#'
#' Opens a diffusers-layout transformer directory lazily (headers only).
Expand Down
163 changes: 163 additions & 0 deletions R/dit_flux2.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#' FLUX.2 Transformer (MMDiT)
#'
#' Fresh R port of Flux2Transformer2DModel from the diffusers reference
#' implementation (Apache-2.0,
#' src/diffusers/models/transformers/transformer_flux2.py). Defaults are
#' the klein-4B configuration (5 double + 20 single blocks). Guidance
#' embeddings (FLUX.2-dev) are not implemented; klein is step-distilled
#' with \code{guidance_embeds = false}. Timestep conditioning has no
#' pooled-text component, and the three modulation projections are
#' shared across all blocks.
#'
#' @name dit_flux2
NULL

# Timestep-only conditioning: sinusoid(256) -> MLP, bias-free. Matches
# diffusers Flux2TimestepGuidanceEmbeddings state-dict names (klein has
# no guidance_embedder).
flux2_time_guidance_embed <- torch::nn_module(
"flux2_time_guidance_embed",
initialize = function(embedding_dim, in_channels = 256L) {
self$in_channels <- in_channels
self$timestep_embedder <- ltx23_timestep_embedding(in_channels,
embedding_dim, bias = FALSE)
},
forward = function(timestep) {
proj <- ltx23_get_timestep_embedding(timestep, self$in_channels,
flip_sin_to_cos = TRUE, downscale_freq_shift = 0)
self$timestep_embedder(proj$to(dtype = self$timestep_embedder$linear_1$weight$dtype))
}
)

#' FLUX.2 transformer model
#'
#' Shared modulation computed once per forward; double blocks over
#' separate text/image streams, then single (parallel) blocks over the
#' concatenated [text; image] sequence. Rotary embeddings are
#' precomputed by the caller with \code{\link{flux_pos_embed}}
#' (\code{axes_dim = c(32, 32, 32, 32)}, \code{theta = 2000}) over the
#' concatenated [text; image] 4-axis position ids.
#'
#' @param in_channels Integer. Packed latent channels (128).
#' @param num_layers Integer. Double-stream block count (klein-4B: 5).
#' @param num_single_layers Integer. Single-stream block count (20).
#' @param attention_head_dim Integer. Per-head dimension.
#' @param num_attention_heads Integer. Attention heads.
#' @param joint_attention_dim Integer. Text embedding dim (7680).
#' @param mlp_ratio Numeric. Feed-forward multiplier (3.0).
#' @param timestep_guidance_channels Integer. Sinusoid width (256).
#' @param axes_dims_rope Integer vector. Per-axis rotary dims.
#' @param rope_theta Numeric. Rotary base frequency (2000).
#' @param eps Numeric. Norm epsilon.
#' @param out_channels Integer or NULL. Defaults to \code{in_channels}.
#'
#' @return Module whose forward(hidden_states, encoder_hidden_states,
#' timestep, image_rotary_emb) returns the predicted velocity for the
#' image tokens [B, S_img, out_channels]. \code{timestep} is in sigma
#' space (0-1); it is scaled by 1000 internally.
#'
#' @export
flux2_transformer <- torch::nn_module(
"flux2_transformer",
initialize = function(in_channels = 128L,
num_layers = 5L,
num_single_layers = 20L,
attention_head_dim = 128L,
num_attention_heads = 24L,
joint_attention_dim = 7680L,
mlp_ratio = 3.0,
timestep_guidance_channels = 256L,
axes_dims_rope = c(32L, 32L, 32L, 32L),
rope_theta = 2000,
eps = 1e-6,
out_channels = NULL) {
inner_dim <- num_attention_heads * attention_head_dim
self$inner_dim <- inner_dim
self$axes_dims_rope <- as.integer(axes_dims_rope)
self$rope_theta <- rope_theta
self$out_channels <- as.integer(out_channels %||% in_channels)

self$time_guidance_embed <- flux2_time_guidance_embed(inner_dim,
as.integer(timestep_guidance_channels))
self$double_stream_modulation_img <- flux2_modulation(inner_dim, 2L)
self$double_stream_modulation_txt <- flux2_modulation(inner_dim, 2L)
self$single_stream_modulation <- flux2_modulation(inner_dim, 1L)

self$x_embedder <- torch::nn_linear(in_channels, inner_dim, bias = FALSE)
self$context_embedder <- torch::nn_linear(joint_attention_dim,
inner_dim, bias = FALSE)

self$transformer_blocks <- torch::nn_module_list(
lapply(seq_len(num_layers), function(i) {
flux2_double_block(inner_dim, num_attention_heads,
attention_head_dim, mlp_ratio = mlp_ratio,
eps = eps)
})
)
self$single_transformer_blocks <- torch::nn_module_list(
lapply(seq_len(num_single_layers), function(i) {
flux2_single_block(inner_dim, num_attention_heads,
attention_head_dim, mlp_ratio = mlp_ratio,
eps = eps)
})
)

self$norm_out <- flux_ada_layer_norm_continuous(inner_dim, inner_dim,
bias = FALSE)
self$proj_out <- torch::nn_linear(inner_dim, self$out_channels,
bias = FALSE)
},
forward = function(hidden_states, encoder_hidden_states, timestep,
image_rotary_emb, chunk_size = NULL) {
hidden_states <- self$x_embedder(hidden_states)
timestep <- timestep$to(dtype = hidden_states$dtype)$mul(1000)
temb <- self$time_guidance_embed(timestep)

mod_img <- self$double_stream_modulation_img(temb)
mod_txt <- self$double_stream_modulation_txt(temb)
mod_single <- self$single_stream_modulation(temb)

encoder_hidden_states <- self$context_embedder(encoder_hidden_states)

block_gc <- isTRUE(getOption("diffuseR.block_gc"))
for (i in seq_along(self$transformer_blocks)) {
res <- self$transformer_blocks[[i]](
hidden_states = hidden_states,
encoder_hidden_states = encoder_hidden_states,
temb_mod_img = mod_img,
temb_mod_txt = mod_txt,
image_rotary_emb = image_rotary_emb,
chunk_size = chunk_size
)
encoder_hidden_states <- res[[1]]
hidden_states <- res[[2]]
if (block_gc) {
gc(verbose = FALSE)
}
}

txt_len <- encoder_hidden_states$shape[2]
hidden_states <- torch::torch_cat(
list(encoder_hidden_states, hidden_states),
dim = 2L
)
for (i in seq_along(self$single_transformer_blocks)) {
hidden_states <- self$single_transformer_blocks[[i]](
hidden_states = hidden_states,
temb_mod = mod_single,
image_rotary_emb = image_rotary_emb,
chunk_size = chunk_size
)
if (block_gc) {
gc(verbose = FALSE)
}
}
hidden_states <- hidden_states$narrow(
2L, txt_len + 1L,
hidden_states$shape[2] - txt_len
)

hidden_states <- self$norm_out(hidden_states, temb)
self$proj_out(hidden_states)
}
)
Loading
Loading