Repository navigation
[model,test,docs] feat: add FLUX.3 adapter - #247
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
…limit Flow-Factory exposes no max_sequence_length setting, so dataset preprocessing encoded prompts with the encode_prompt default of 512 tokens, while the official FLUX.3 text encoder step defaults to 8192. A 999-token caption produced a 512-token window instead of the official 1040-token one: the prompt was truncated, and the window was no longer a multiple of the 80-token pad recipe.
…EADME table check Two existing tests failed once flux3 was registered: - test_offline_smoke_profiles requires every registered model type to appear in an offline smoke profile. Add flux3 to text_to_image, image_to_image, and multi_image_to_image, matching flux2-klein and the adapter's within-type multi-reference contract. - test_sensenova_docs locates the combined T2I/I2I README table by its rowspan, which grew from 9 to 11 with the two FLUX.3 rows.
…e 7ecb1e1b pin The FLUX.3 change moves the shared Diffusers submodule from 80c7ed262 to 7ecb1e1b. That pin raises the submodule's floor to Python 3.11 and huggingface-hub 1.32, and Qwen-Image 2.1 still named the old commit in its install error. - Point QWEN_IMAGE_21_DIFFUSERS_COMMIT at 7ecb1e1b. The functions it documents as copied (calculate_dimensions, retrieve_latents, _get_qwen_prompt_embeds, _pack_latents, _unpack_latents) are unchanged between the two commits, and the released checkpoint sets no sample_sigmas, so Qwen-Image 2.1 sampling is unaffected. - State the Python 3.11 requirement in the README install note and in dependencies.md.
Jayce-Ping
force-pushed
the
feat/flux3
branch
from
October 17, 2026 13:07
0d5ddf7 to
26c7956
Compare
…92 tokens encode_prompt already defaults to the official 8192-token limit, but inference, _inference, and the Qwen3-VL helpers kept their own 512 default and pass it to encode_prompt explicitly, so raw-prompt rollouts (for example with enable_preprocess: false) truncated long captions to 512 tokens. Share one DEFAULT_MAX_SEQUENCE_LENGTH across every entry point, run the window tests on the default, and cover the inference path.
This was referenced Oct 17, 2026
… size 1 for exact replay With per_device_batch_size > 1 the on-policy log-probs did not match the rollout: an exact probe found up to 7e-6 differences in 421 of 768 replays with FLUX.3-upsampled GenEval prompts and 140 of 768 with PickScore captions. inference() and forward() each ran a batch batched when all its prompts shared one 80-token window count and per sample otherwise. Group-contiguous rollout packs repeat one prompt and ran at batch size 8, while the shuffled optimize micro-batches mix window counts and replayed the same samples at batch size 1, and bf16 GEMMs of different shapes round differently. Both phases now run each sample's transformer forward at batch size 1, so a sample's result no longer depends on its batch, and _inference handles exactly one sample. A regression test regroups rollout packs into new micro-batches and requires bit-identical log-probs from batch size 1 transformer calls. The train-inference consistency notes record the failure mode.
…r pack-dependent adapters train.shuffle_samples permuted individual samples, so an adapter whose batched forward depends on batch composition replayed samples in micro-batches that differed from their rollout packs, and the on-policy ratio was no longer exactly 1. Bagel could only warn and ask for shuffle_samples: false, and FLUX.3 ran every sample alone, which cost about 13% rollout and 15% optimization time at per_device_batch_size 8. For adapters that declare requires_preserved_replay_batch_composition, the generation loop now records the rollout packs and the optimize loop permutes whole packs, keeping each pack's samples and order. It fails fast when a pack does not match per_device_batch_size or the samples changed after rollout; reward overlap shares that validation. Batch-invariant adapters still shuffle individual samples with the same permutation, and shuffle_samples: false still keeps the rollout order. FLUX.3 declares the flag and goes back to batched execution for packs whose prompts share one 80-token window count. Bagel's warning is dropped, and the help text, guidance, and train-inference consistency notes describe the semantics.
Jayce-Ping
added a commit
that referenced
this pull request
Oct 18, 2026
Resolve the README news conflict with the FLUX.3 adapter (#247) by keeping both entries, newest first.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add
Flux3Adapter(model_type: "flux3") supporting Black Forest Labs' newly released FLUX.3 Image Klein family (black-forest-labs/flux-3-image-klein-baseandblack-forest-labs/flux-3-image-klein) fromhuggingface/diffusers@7ecb1e1b4fe500be7fb7381a7bb79a24d7fa3941(huggingface/diffusers#15046).Key Changes
diffusers): Update the bundleddiffuserssubmodule to7ecb1e1b4fe500be7fb7381a7bb79a24d7fa3941(Flux3Transformer2DModel,Flux3ModularPipeline,Flux3DistilledModularPipeline,Flux3ImageProcessor). The new pin requires Python 3.11+ andhuggingface-hub>=1.32; the README install note and.agents/knowledge/dependencies.mdsay so, andQWEN_IMAGE_21_DIFFUSERS_COMMITnow names the same pin (the Qwen-Image 2.1 functions it documents as copied are unchanged between the two commits).Flux3Adapter):Flux3ModularPipeline/Flux3DistilledModularPipelinewithModularPipelineRuntime, rewrite local snapshotComponentSpecpaths when loading from disk, and declarecomponent_load_dtype_defaults = {"transformers": torch.bfloat16, "text_encoders": torch.bfloat16, "vae": torch.float32}.supports_fsdp2_cpu_efficient_loading = Trueandddp_find_unused_parameters = True(required becausemode_blocks.image_condreceives no gradient during text-only T2I steps)._gradient_checkpointing_units()to returntxt_mode_blocks->mode_blocks.image->mode_blocks.image_cond->single_transformer_blocksin exact forward execution order.position_ids = 1, and 8-layer hidden-state stacking(4, 8, 12, 16, 20, 24, 28, 32)->20480channels. Every text-encoding entry point (encode_prompt,inference,_inference, and the Qwen3-VL helpers) defaults to the officialmax_sequence_length=8192, so neither preprocessing nor raw-prompt rollouts truncate long captions.AutoencoderKLFlux2(sample_mode="argmax"),image_rope_scale = 0.5, and negativelcoordinates-(1 + idx)(-1, -2, ...).flow_map=True(is_distilled) to passtarget_timestep = t_next / 1000and narrow_resolve_pipeline_io_contract()toNegativePromptPolicy.UNSUPPORTED.img_ids,txt_ids, andimage_cond_idsto 2D(seq_len, 4)forFlux3PosEmbed.requires_preserved_replay_batch_composition, and the optimize loop replays whole rollout packs (next item).BaseTrainer):train.shuffle_samplesnow shuffles whole rollout packs, keeping each pack's samples and order, for adapters that declarerequires_preserved_replay_batch_composition(Bagel, FLUX.3), so every micro-batch replays one rollout pack. Batch-invariant adapters still shuffle individual samples with the same permutation, andshuffle_samples: falsestill keeps the rollout order. The generation loop records the rollout packs of these adapters, and the ordering fails fast when a pack does not matchper_device_batch_sizeor the samples changed after rollout; reward overlap shares that validation. Bagel's warning asking forshuffle_samples: falseis removed, and the help text,guidance/workflow.md,guidance/algorithms.md, and the train-inference consistency notes describe the semantics.src/flow_factory/models/flux/_output.pywithencode_flux3_output_images(sample_mode="sample"),prepare_flux3_condition_latents, and standalone 2x2 patchify/unpatchify helpers.offline_training_forward_overrides = MappingProxyType({"guidance_scale": 1.0}).flux3in the offline smoke catalog (text_to_image,image_to_image,multi_image_to_image).examples/grpo/lora/flux3/default.yamland updateREADME.md,examples/README.md,guidance/datasets.md, and.agents/knowledge/architecture.md.Fixes from GPU validation
[model] fix: default FLUX.3 encode_prompt to the official 8192-token limit[dataset,test] fix: register flux3 in the offline smoke catalog and README table checktests/dataset/test_offline_smoke_profiles.pyandtests/docs/test_sensenova_docs.pyfailed onceflux3was registered (both pass onmain).[model,docs,agents] fix: align Diffusers submodule references with the 7ecb1e1b pinAfter validation,
[model,test] fix: default every FLUX.3 entry point to the official 8192 tokensextends the 8192 default toinference,_inference, and the Qwen3-VL helpers. They still defaulted to 512 and passed it toencode_prompt, so raw-prompt rollouts (for example withenable_preprocess: false) truncated long captions.[model] docs: describe the FLUX.3 tokenizer without reward decodingdrops the tokenizer docstring's reference to reward-sideprompt_idsdecoding, which #249 removes; it matches #249's wording for Qwen-Image 2.1.Fix from training validation: exact on-policy replay at
per_device_batch_size > 1A 32-GPU GRPO run of the GenEval example (
per_device_batch_size: 8, from #248) logged on-policy ratios that rounded to 1.0000 but hadratio_stdup to 1e-4, while Qwen-Image 2.1 under the same trainer logged exactly 1.inference()andforward()each ran a batch batched when all its prompts shared one 80-token window count and per sample otherwise. Group-contiguous rollout packs repeat one prompt and ran at batch size 8, while the shuffled optimize micro-batches mix window counts and replayed the same samples at batch size 1; bf16 GEMMs of different shapes round differently. The batched path only runs atper_device_batch_size > 1, which no earlier gate exercised: every GRPO gate config inheritedper_device_batch_size: 1from the shipped example.[model,test,agents] fix: run every FLUX.3 sample at transformer batch size 1 for exact replayper_device_batch_size: 8. Superseded by the next commit.[trainer,model,test,docs,agents] feat: shuffle whole rollout packs for pack-dependent adaptersrequires_preserved_replay_batch_composition; the optimize loop shuffles the rollout packs of such adapters as whole units, so each micro-batch reproduces its rollout pack.Verification
CPU
pytest tests-> 3000 passed, 50 skipped, 0 failed with the submodule installed editable (pip install -e ./diffusers).pytest tests-> 3009 passed, 50 skipped, 0 failed. Merged with [prompt_enhance,model,cli,docs] feat: move prompt enhancement into ff-prompt-enhance #248 and [reward,trainer] fix: gather prompt strings for groupwise rewards #249, the full suite gives 3042 passed, 50 skipped, 0 failed.tests/trainers/test_optimize_sample_order.pychecks the ordering against the exacttorch.randpermoracle: batch-invariant adapters keep the previous per-sample permutation, pack-dependent adapters get a permutation of whole rollout packs (the case that fails on the previous trainer), single-sample packs reduce to the per-sample order,shuffle_samples: falsekeeps the rollout order, missing or mismatched packs and reordered samples fail fast, and the generation loop records packs only for pack-dependent adapters.test_rollout_packs_replay_exactly_through_their_rollout_pathrequires bit-identical replay of a uniform pack (batch size 2 in both phases) and of a mixed pack (batch size 1).scripts/validate_agent_harness.py,scripts/audit_docstrings.py(0 errors), and Black/isort on every changed Python file pass.GPU (8x H20 97,871 MiB, driver 535.247.01)
Environment: Python 3.12, torch 2.10.0+cu129, transformers 5.14.1, accelerate 1.14.0, peft 0.20.0, deepspeed 0.18.3, huggingface-hub 1.32.0, diffusers
0.41.0.dev0= submodule7ecb1e1b. Weights:flux-3-image-klein-base@3f7fa5eeandflux-3-image-klein@33733762.Tested commit:
516f9653. Like the tested commit, the final head defaults all five text-encoding entry points to 8192, now through a sharedDEFAULT_MAX_SEQUENCE_LENGTH, and its test covers both theencode_promptand raw-promptinferencepaths. The parity script passesmax_sequence_lengthexplicitly, so its results do not depend on the default.1. Real-weight 4-layer parity (1 GPU per variant; T2I and 2-reference I2I at 1024², base 28 steps CFG 3.5, distilled 16 steps)
shift=6.93, no dynamic shifting), VAE scale, image-processor, and transformer configs identical; every transformer / Qwen3-VL / VAE weight bit-equal, including the 86 fp32norm_q/norm_ktensors; prompt embeddings (80- and 160-token windows), a 999-token prompt at the new default (1040-token window), and the 8016 reference-image tokens are all bit-exact.latent_storage_dtype: null) and makes the reference scheduler step with Flow-Factory's fp32 Euler arithmetic; everything else runs the official modular blocks.vae.dtype. The trainer never encodes conditions under autocast.dt * vin bf16), the first step already differs by one bf16 ulp and the trajectories drift: PSNR 37.1 / 43.6 dB (base T2I / +autocast), 53.4 / 43.8 dB (distilled T2I), 20.0 / 27.3 dB (base I2I), 30.1 / 29.9 dB (distilled I2I). Images keep the same subject and composition; the 28-step CFG trajectory amplifies the ulp differences. These settings are framework-wide, not FLUX.3-specific.2. On-policy rollout/replay (real GRPO trainer)
A per-rank probe on the GRPO trainer compares every replayed log-prob with the stored rollout log-prob bit for bit, records the batch size of every FLUX.3
_forwardcall, and checks that every optimize micro-batch is one whole rollout pack. Each run keeps the defaultshuffle_samples: trueand takes one optimizer step per epoch, so every replay is on-policy. Each ran 2 epochs on 8 GPUs with ZeRO-2 unless noted,per_device_batch_size: 8, 512², 10 steps, CPS, a PickScore reward, and 16 prompts x 8 per epoch; the second epoch rolls out after a real update. The final code was validated at0b6b9b17, which differs fromcf12271cby one CPU test.ebac917a)d7994e1f)cf12271c)shuffle_samples: falseflux-3-image-kleinratio_stdup to 1e-4 in the 32-GPU runratio_min=1 ratio_max=1 ratio_std=0on the first step; the second step replays pre-update rollouts by design.save_model_only: false,resume_type: statefromcheckpoint-1, the GenEval-prompt run above): under DDP and ZeRO-2 the first resumed epoch reproduces the source run exactly: the same pack order on every rank, bit-equal rollout and replay log-probs, and all 40 logged train metrics equal.shuffle_samples: false(27.8 s, 15.8 s); the per-sample commit took 31.5 s and 18.1 s.Before this fix, the 21 GRPO gate runs (T2I/I2I, base/distilled, every backend, all at
per_device_batch_size: 1, including the 384 first-step replays of the shipped example) already gave max |exp(new_log_prob − old_log_prob) − 1| = 0. The example's second gradient step per epoch reuses pre-update rollouts by design and stays withinclip_range(max |ratio − 1| = 1.3e-5, clip fraction 0).norm_q/norm_kstay fp32 under DDP and FSDP2 (see the ZeRO-2 note below). Under DDP, LoRA gradients reach all four block streams in I2I; in T2Imode_blocks.image_condcorrectly receives none.3. Backend x algorithm smoke matrix (2 GPUs each unless noted; all exit 0, losses finite)
fsdp_cpu_ram_efficient_loading: trueflux-3-image-klein, T2I and I2Iexamples/grpo/lora/flux3/default.yamlas shipped (8 GPUs, ZeRO-2, eval at 1024² on 128 prompts, 1 epoch)4. Checkpoint round-trip
save_model_only: true: the checkpoint reloaded into a fresh real-weight adapter matches all 516/516 saved LoRA tensors bit-exactly, and aresume_type: loraGRPO run trains on-policy from it.save_model_only: false, resuming fromcheckpoint-1withresume_type: state(DDP, ZeRO-2, FSDP2 for T2I; DDP for I2I): the first resumed epoch reproduces the source run exactly (all logged metrics and per-rank rollout/replay log-probs bit-equal), so weights, RNG, and sampler state are restored. Under DDP and FSDP2, the optimizer state before the first resumed step is bit-equal to the source run's; under ZeRO-2 (where the step runs insideaccelerator.backward), the Adam step counter continues from the checkpoint. Later epochs differ by design: two fresh runs from the same seed already diverge after the first optimizer step (non-deterministic GPU backward), as a control run showed.Known framework-level behavior (not introduced here)
DeepSpeedEnginecasts the whole module withmodule.bfloat16(), so Diffusers fp32 islands (norm_q/norm_khere; Wan and MiniMax H3 have the same) become bf16 andtorch.rms_normfalls back from the fused kernel with a dtype-mismatch warning. Rollout and replay stay consistent (ratio exactly 1). Restoring fp32 islands after DeepSpeed preparation belongs in the trainer and is left for a separate PR.