Skip to content

Release 0.19.0

Latest

Choose a tag to compare

@github-actions github-actions released this 18 Aug 04:08
· 1 commit to main since this release
4d74513

⚠️ Behavior Changes

  • A metadata sidecar no longer stacks its LoRAs on top of the command line's: --config-from-metadata restored lora_paths/lora_scales by concatenation, while every other key it restores defers to explicitly given arguments. The sidecar's adapters therefore always applied and no spelling could replace them — --config-from-metadata prior.metadata.json --lora mine.safetensors 0.9 ran both. Since only one of the two lists usually came from the command line, the counts then disagreed and resolve_scales padded or truncated the difference behind a warning: --lora-paths cli.safetensors left the new adapter with no scale of its own, so the sidecar's went to the sidecar's adapter and the new one was padded to 1.0, and --lora-scales 0.8 on a one-adapter sidecar generated at the sidecar's scale with the 0.8 discarded. --lora/--lora-paths now replaces the sidecar's adapters rather than adding to them, --lora-paths with no values re-runs without any, and --lora-scales on its own keeps the sidecar's adapters and applies the new strengths. Anything scripted around the stacking behaviour has to name every adapter it wants. Sidecars also record each adapter's resolved absolute path, so one carried between machines failed on a path that was nowhere on the command line — and failed even when the user named their own copy, since the dead path was merged in and resolved anyway; that now works, and when there is no replacement the error names the sidecar it came from and both ways out. (#577, #618)
  • --guidance and --scheduler now take effect on mflux-upscale-controlnet: both were registered by the parser and then never passed to generate_image, so whatever you typed was replaced by Flux1Controlnet's own defaults. They are forwarded now, which means an invocation that passed either flag produces a different image than it did before. Omitting --guidance still gives the 4.0 the upscaler has always run at, rather than ui_defaults.GUIDANCE_SCALE (3.5), so nothing changes for a command that never set it. (#577)
  • Abbreviated long options are rejected: every mflux CLI now requires flags to be written in full, so --prom no longer stands in for --prompt. Option provision is detected by scanning argv, which cannot see abbreviations, and an abbreviation that is unambiguous today starts resolving elsewhere the moment a new flag is added. Anything scripted with a shortened flag needs the full spelling. (#499)

✨ Improvements

  • twine removed from mflux's dependencies: it only existed to support the release script, but every pip install mflux pulled in twine plus ~24 transitive packages (cryptography, keyring, rich, readme-renderer, ...). The release script's token-mode upload now shells out to uv publish--check-url replicates twine's skip_existing, the token travels via UV_PUBLISH_TOKEN so it never appears in logged commands, and the transient-retry semantics are unchanged — while twine check dist/* survives via uv tool run twine in an ephemeral environment. CI's trusted-publishing path is untouched (pypa/gh-action-pypi-publish still uploads). Also fixes the script invoking uv as python -m uv, which requires the uv PyPI package that the release environment does not install; it now calls the uv binary from PATH. Adds a just release recipe that triggers the release workflow via gh. (#644)
  • Type checking migrated from mypy to astral's ty (pinned ty==0.0.72 in dev deps, pre-commit and CI): full-project checks in ~0.4s vs mypy's 2–9s. Pre-existing violations are baselined as ignored rules in [tool.ty.rules] (with counts) to be re-enabled and fixed piecemeal; new code must pass all non-baselined rules. Adds just typecheck. (#597)
  • Quieter LoRA loading: stacking a second adapter printed a 🔀 line for every layer it fused — hundreds of lines between the file's own 🔧 line and its ✅ Applied to N layers summary, which already says how many landed. Those three per-layer messages are now logger.debug, like the resolution rules' tracing. (#577)
  • Silently dropped CLI options now warn: a command that accepts an option it cannot honour says so at parse time instead of ignoring it, for example --guidance on a guidance-distilled model. Adds mflux-capabilities, a machine-readable dump of every image-generating command with its options, defaults, types and the status of each option (honored, ignored, conditional or rejected), in JSON, YAML or Markdown. (#499)

✨ Improvements

  • --vae-tiling and --vae-tile-size flags: Restore user-facing control over tiled VAE decoding, decoupled from --low-ram (previously the only way to enable it). --vae-tiling enables tiled decode with the default 512px tiles; --vae-tile-size 256 shrinks the tiles to further reduce peak decode memory and implies --vae-tiling. Both compose with --low-ram, whose implicit tiling defaults they override. The original --vae-tiling/--vae-tiling-split flags were removed in the Z-Image refactor (#284); this restores the capability on top of the generalized VAETiler. (#311, #407)

🐛 Bug Fixes

  • Saved FLUX ControlNet weights were ignored on load: FluxInitializer.init_controlnet always fetched the ControlNet component from the remote repo, never consulting the model_path it honors for every other component. Reloading a checkpoint written by mflux-save silently swapped its ControlNet weights for the hub's — a -q save came back with that component unquantized, a fine-tune was discarded, and an otherwise complete local checkpoint still wanted the network. Only reachable since #607 made mflux-save actually write the ControlNet component. A model_path holding transformer_controlnet/*.safetensors now loads it from disk, the branch ZImageInitializer already had; a builtin name or a directory without that component keeps the hub path. (#610, #642)
  • A repo id shared by two registry entries resolved to the ControlNet variant: several roots share a model_name with a ControlNet derivative, and the resolver returned the first match in priority order — which put the Z-Image ControlNet ahead of plain turbo, so --model Tongyi-MAI/Z-Image-Turbo built a ControlNet config in every resolver caller (the generate CLIs and mflux-save dispatch alike). Ties on a bare repo id now prefer the base entry, on the rule that a derived variant is always addressed by its own key or alias; keys and aliases resolve exactly as before. (#609, #641)
  • Failed LoRAs were reported as applied: every step between resolving an adapter and folding it into the weights had a path that printed a warning and carried on, and the run then announced ✅ All LoRA weights applied successfully. An adapter that could not be resolved was dropped from the list — generating from the untouched base model, and, with several adapters, shifting the scales onto the wrong ones as resolve_scales padded the shortened list back to length. A file that was missing or unreadable at load time printed and returned. A target layer the model does not have, or one that turned out not to be linear, printed per layer and left the adapter applied to only part of the model. And baking caught every exception — including a base-weight/delta shape mismatch — printed ⚠️ and returned the layer unbaked, so mflux-save wrote a checkpoint advertised as merged that held the original base weights, and ordinary generation (which bakes by default) quietly ran without the adapter. All of these now raise. Bake errors name the layer they failed at (blocks.0.attn.wq), and mflux-save bakes every component before it writes a single file, so a mismatch aborts instead of leaving half a checkpoint on disk. Anything scripted around a mistyped or missing adapter path now exits instead of generating an image without it. (#577)
  • boogu and FIBO accepted --lora and then discarded it: mflux-generate-boogu, mflux-generate-fibo and mflux-generate-fibo-edit all declared the LoRA flags, so the parser resolved every adapter — downloading it from HuggingFace when the name was a repo id — and the initializers then dropped it on the floor, while mflux-capabilities reported "lora": true and nothing warned at any point. mflux implements no LoRA mapping for either architecture, so the flags are gone rather than ignored: the three CLIs no longer take them, matching mflux-generate-lens, and --help and the capabilities dump now agree with the code. BooguInitializer/FIBOInitializer and the BooguImage/FIBO/FIBOEdit constructors lost their unused lora_paths/lora_scales/bake_lora parameters so the Python API stops accepting an adapter it ignores, and mflux-save --model boogu --lora ..., which filtered the same kwargs away by signature and wrote an unmodified checkpoint, now exits with a message. (#577, #615)
  • --model silently ignored on 4 CLIs: mflux-generate-krea2, mflux-generate-z-image-turbo, mflux-generate-ernie-image and mflux-generate-ernie-image-turbo accepted --model and then constructed a hard-coded config, so --model dev silently ran Krea-2-Turbo (etc.) while mflux-capabilities reported the option as honored. Each now validates --model through a shared ConfigResolution.resolve_restricted (the lens pattern): a builtin registry name must be an alias of the CLI's own model, anything else errors. Local checkpoint paths and HuggingFace repo ids — which parse_args routes through model_path — are untouched: they keep the CLI's own config and load weights from the path, as they always have. Validation compares registry entries by identity rather than model_name, so --model z-image-controlnet — whose entry shares the Tongyi-MAI/Z-Image-Turbo repo id — is correctly rejected by the plain turbo CLI; lens_generate's inline check, which compared model_name, now uses the same helper. (#577, #614)
  • mflux-completions crashed on every invocation: the mflux-upscale-controlnet parser added its LoRA arguments twice, so every build path died with argparse.ArgumentError: conflicting option string: --lora-style (exit 1) before writing a single completion. A regression test now builds the parser for every command mflux-completions knows about. (#577, #613)
  • Qwen Image Edit conditioning resolution: Encode the transformer's image-conditioning latents at the edit target resolution, not the vision-language conditioning resolution (≈384px by area), preventing patchy/tiled artifacts in edit outputs. This changes edit output at every quantization level, not only -q 4. (#420)
  • Qwen Image Edit default dimensions: Preserve the first input image dimensions by default; explicit --width/--height values or scale factors such as 2x still opt into resizing. (#420)
  • Qwen Image Edit CLI scheduler: Forward --scheduler to the Qwen edit pipeline (previously ignored). (#420)
  • Inferred model configs lost their settings: ConfigResolution rebuilt inferred configs field by field, so anything resolved from a local path or variant name (/models/qwen-image-edit-q4) silently fell back to generic defaults. It now carries every field and rewrites only identity. This restores the Qwen edit sigma schedule (0.9/8192/0.02 rather than 1.15/4096/None), ERNIE's LoRA training guidance, and supports_kv_cache on flux2-klein-9b-kv. (#420)
  • Default --steps ignored the model on 10 CLIs: every image CLI that picks its model in main() rather than at parse time — boogu, krea2, lens, z-image, z-image-turbo, flux2, flux2-edit, qwen, qwen-edit, ideogram4 — resolved --steps against a None model and silently inherited FLUX.1-dev's 25. That is 6x the work on a 4-step distillation (boogu, lens, klein) and half the work on a 50-step base model (z-image). The step table is now keyed by canonical model rather than by alias and resolved through AVAILABLE_MODELS, so --model klein-4b and --model flux2-klein-4b no longer disagree, and a CLI declares its own model to the parser via add_model_arguments(default_model=...). Krea 2's DEFAULT_STEPS = 8 fallback, unreachable because the parser always supplied 25, is gone. Explicit --steps is unaffected. (#580)
  • --model <name> failed for most built-in models: the names the CLI accepted were a hand-maintained list (ui_defaults.MODEL_CHOICES) that had drifted from AVAILABLE_MODELS, and anything missing from it was treated as a local checkpoint directory — mflux-generate-lens --model lens-turbo died with Model not found: 'lens-turbo' on the canonical name of the only model that CLI runs. 16 registry keys and roughly 40 aliases were affected, among them krea-2-raw, qwen-image, and every klein-*, boogu-*, zimage* and fiboedit* spelling. The accepted names are now derived from the registry, so a new model or alias needs no second edit. --base-model likewise dropped its stale argparse choices= list, which rejected valid names such as qwen-image; it is now validated against ConfigResolution's own list of root models, and the check also covers a base_model restored from a metadata sidecar. (#577)
  • mflux-save wrote checkpoints under the wrong architecture: the model class was chosen by a substring chain over the raw --model string ending in else: Flux1, so every name it had not been taught was saved as a FLUX.1 model — lens, lens-turbo, klein-4b/9b/9b-kv and seedvr2* all were — while fibo-edit* was saved by the txt2img FIBO class and z-image-controlnet* by plain ZImage. Nothing raised; the mismatch surfaced later as an unloadable checkpoint. Dispatch now goes through the registry (ConfigResolution.resolve_key), so canonical keys, aliases, HuggingFace repo ids and --base-model spellings all land on the class that owns those weights, an unrecognisable name exits with a message instead of defaulting to Flux1, and the two models with no save path (Lens, SeedVR2) say so. Every rejection also lists the models mflux-save can write, which --help does not show — it lists models that cannot be saved. A drift test fails if a model is added to AVAILABLE_MODELS without a save class. Two consequences worth noting: --model dev-controlnet-canny/dev-controlnet-upscaler/schnell-controlnet-canny now save through Flux1Controlnet (ControlNet weights included) rather than as a bare Flux.1, and mflux-save --model boogu no longer dies with a TypeError on bake_lora, which BooguImage does not accept. (#607)
  • ERNIE multi-frame latents: pack_latents indexed the VAE's temporal axis away, so anything but a single frame was silently discarded; it now fails loudly. (#577)
  • ernie-image-turbo guidance contract: the model declared supports_guidance: true while its CLI exits on any guidance other than 1.0, contradicting itself in the mflux-capabilities dump. (#578)
  • Partial Krea 2 Turbo downloads passed as complete: a cached snapshot was checked against its download patterns only when the model needed no subdirectory. Krea 2 Turbo keeps its transformer at the repo root as turbo.safetensors, so a download that fetched vae/ and text_encoder/ and then stopped reported itself complete. Hugging Face's repair download never ran, and the load died later with Missing specified weight files in <snapshot>: ['turbo.safetensors'], which reads like a corrupt cache rather than an interrupted transfer. Root-level *.safetensors patterns are now checked in the subdirectory branch too. (#577, #593)
  • Saved checkpoints loaded whatever was in the directory: mflux-save writes a model.safetensors.index.json naming every shard, and the reload globbed the directory instead of reading it. Saving -q 4 over an existing -q 8 checkpoint in the same folder left the q8 tail behind, and those stale tensors overwrote the ones just written while the metadata still reported 4. A checkpoint missing a shard the index names loaded the rest and came up short. The index now decides which shards get read, and a named shard that is not on disk raises with the filenames. Checkpoints with no index, or with a damaged one, keep loading from the directory. (#577)
  • Metadata sidecars did not restore dimensions, the negative prompt or init images: every run embeds height, width, negative_prompt and image_paths, but --config-from-metadata read none of them back, so a sidecar-only rerun of a 1536x768 generation came out at the 1024x1024 default with the negative prompt dropped — a different image on any CFG model, not just a differently sized one. The edit CLIs could not rerun a sidecar at all: --image-paths was required=True, which argparse enforces before the restore block runs, so mflux-generate-qwen-edit --config-from-metadata prior.metadata.json exited 2 demanding images the sidecar was carrying. All four keys are restored now, each deferring to the command line option by option, and --image-paths is required after the restore rather than during parsing. A sidecar's init image that does not exist on this machine now errors at parse time naming the sidecar, instead of loading the whole model first and dying on a bare FileNotFoundError. (#577, #634)
  • The upscale commands were missing from mflux-capabilities: COMMAND_PREFIXES covered mflux-generate* and mflux-concept* only, so neither mflux-upscale-controlnet nor mflux-upscale-seedvr2 appeared in the dump — which is how the --metadata bug above went unnoticed by the contract checks that exist to catch exactly that. Both are now published with full coverage, which meant adopting the build_parser() convention and declaring what their hardcoded configs cannot honour: --base-model is ignored by both, and on mflux-upscale-controlnet --negative-prompt is ignored while --model is conditional (a path or repo id loads weights, a built-in model name does nothing, since the config is always dev-controlnet-upscaler). Both commands now warn at parse time when an ignored option is passed. (#577)
  • mflux-upscale-seedvr2 --metadata never wrote the sidecar: the CLI accepted the flag and then saved without forwarding it, so the upscale ran to completion and no JSON appeared. Its sibling mflux-upscale-controlnet passes export_json_metadata on the equivalent line. (#577)
  • Ideogram 4 quantization: mflux-save -q now actually quantizes Ideogram 4. Every weight-bearing linear in the model is an Fp8Linear, which defined no to_quantized and whose components were marked skip_quantization, so -q 4 wrote an FP8 checkpoint stamped quantization_level: 4 and only the VAE was touched. Adds Fp8Linear.to_quantized, drops the skip flags, derives bits and group size from the stored shapes when rebuilding a saved checkpoint instead of assuming q8/group-64, and rebuilds quantized embeddings as embeddings rather than linears. -q 8 is 26 GB and -q 4 is 14 GB, both visually indistinguishable from FP8. (#559)

📝 Documentation

  • Ideogram 4 gated weights: Document the HF_TOKEN / hf auth login step alongside the existing note that access must be approved on the model card — authenticating is the half people miss, and without it an approved account still fails with a bare 401/403. Adds a quantization section covering mflux-save -q and loading a saved checkpoint with --model-path. (#559)

🧰 DX & Maintenance

  • Dev workflow: Makefile replaced by justfile: make <target> is gone; use just <target> instead (just install, just lint, just format, just check, just test-fast, …). Running bare just (or just --list) shows all recipes with descriptions. Semantics follow the post-#576 Makefile: uv sync installs and ruff is pinned from the pyproject.toml dev dependency. The CI lint job also lints the justfile itself via just --fmt --check, using the pinned just-setup composite action. Requires just ≥ 1.50 locally. (#590)