Repository navigation
[model,test,docs] feat: add FLUX.3 adapter - #247
Open
Jayce-Ping wants to merge 4 commits into
Open
Jayce-Ping wants to merge 4 commits into
Jayce-Ping wants to merge 4 commits into
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.
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.encode_promptdefaults to the officialmax_sequence_length=8192, so preprocessing no longer truncates long captions at 512 tokens.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.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 pinVerification
CPU (final head)
pytest tests-> 3000 passed, 50 skipped, 0 failed with the submodule installed editable (pip install -e ./diffusers).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. The final head differs only in narrowing the 8192 default toencode_prompt(the four other entry points it also touched keep their previous defaults) and dropping the test that came with it. Training is unaffected: prompts are encoded during preprocessing throughencode_prompt, rollouts reuse the cached embeddings, and the parity script passesmax_sequence_lengthexplicitly.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, bf16 autocast + gradient checkpointing)
A per-rank probe on
GRPOTrainerrecorded every replay: max |exp(new_log_prob − old_log_prob) − 1| = 0 for every on-policy replay across all 21 GRPO runs (T2I/I2I, base/distilled, every backend, including the 384 first-step replays of the shipped example). 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.