Add support for FLUX.1-Krea-dev model (#241)

This commit is contained in:
Filip Strand 2025-08-03 00:41:00 +02:00 committed by GitHub
parent b8498c59db
commit beee9b9509
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 207 additions and 151 deletions

View File

@ -48,6 +48,5 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
TEST_PYPI_API_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }}
run: | run: |
python -m mflux.release.release_script python -m mflux.release.release_script

View File

@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.10.0] - 2025-08-02
# MFLUX v.0.10.0 Release Notes
### 🎨 Model Improvements
- **FLUX.1 Krea [dev] Support!**
### ✨ New Features
- **5-bit Quantization Support**: Added support for 5-bit quantization as a new option alongside existing 3, 4, 6, and 8-bit quantization levels
### 🔧 Improvements
- **Enhanced Default Inference Steps**: Increased default inference steps for dev models from 14 to 25 for improved image quality
- **Multiple Model Aliases Support**: Improved model configuration system to properly support multiple aliases per model, making model selection more flexible and robust
### 🔧 Technical Requirements
- **MLX Compatibility**: This release assumes MLX 0.27.0 and upwards for optimal performance and compatibility
- **MLX Compatibility for test**: Fix MLX version to 0.27.1 for image generation tests
- **Non-strict Weight Updates**: Explicitly added non-strict mode (`strict=False`) for weight updates to maintain compatibility with later MLX versions that enforce stricter weight validation by default
### 👩‍💻 Developer Experience
- **Streamlined Release Process**: Removed TestPyPi publishing step from release workflow for simplified deployment
---
## [0.9.6] - 2025-07-20 ## [0.9.6] - 2025-07-20
# MFLUX v.0.9.6 Release Notes # MFLUX v.0.9.6 Release Notes

View File

@ -93,7 +93,7 @@ check: ensure-ruff
.PHONY: test .PHONY: test
test: ensure-pytest test: ensure-pytest
# 🏗️ Running tests... # 🏗️ Running tests...
uv pip install mlx==0.26.1 # Install pinned MLX version specifically for testing uv pip install mlx==0.27.1 # Install pinned MLX version specifically for testing
$(PYTHON) -m pytest $(PYTHON) -m pytest
# ✅ Tests completed # ✅ Tests completed

View File

@ -186,8 +186,8 @@ from mflux.config.config import Config
# Load the model # Load the model
flux = Flux1.from_name( flux = Flux1.from_name(
model_name="schnell", # "schnell" or "dev" model_name="schnell", # "schnell", "dev", or "krea-dev"
quantize=8, # 4 or 8 quantize=8, # 3, 4, 5, 6, or 8
) )
# Generate an image # Generate an image
@ -195,7 +195,7 @@ image = flux.generate_image(
seed=2, seed=2,
prompt="Luxury food photograph", prompt="Luxury food photograph",
config=Config( config=Config(
num_inference_steps=2, # "schnell" works well with 2-4 steps, "dev" works well with 20-25 steps num_inference_steps=2, # "schnell" works well with 2-4 steps, "dev" and "krea-dev" work well with 20-25 steps
height=1024, height=1024,
width=1024, width=1024,
) )
@ -214,6 +214,26 @@ For more advanced Python usage and additional configuration options, you can exp
🔒 [FLUX.1-dev currently requires granted access to its Huggingface repo. For troubleshooting, see the issue tracker](https://github.com/filipstrand/mflux/issues/14) 🔒 🔒 [FLUX.1-dev currently requires granted access to its Huggingface repo. For troubleshooting, see the issue tracker](https://github.com/filipstrand/mflux/issues/14) 🔒
#### 🎨 FLUX.1 Krea [dev]: Enhanced Photorealism
MFLUX now supports **FLUX.1 Krea [dev]**, an 'opinionated' text-to-image model developed in collaboration with [Krea AI](https://krea.ai). This model overcomes the oversaturated 'AI look' commonly found in generated images, achieving exceptional photorealism with distinctive aesthetics.
This model can be used where the `dev` model is used, and it is available as `krea-dev` in MFLUX (also supports the alias `dev-krea`), this includes dreambooth fine-tuning.
![image](src/mflux/assets/krea_dev_example.jpg)
```sh
mflux-generate \
--model krea-dev \
--prompt "A photo of a dog" \
--steps 25 \
--seed 2674888 \
-q 8 \
--height 1024 \
--width 1024
```
*Learn more about FLUX.1 Krea [dev] in the [official announcement](https://bfl.ai/announcements/flux-1-krea-dev).*
#### 📜 Full list of Command-Line Arguments #### 📜 Full list of Command-Line Arguments
<details> <details>
@ -226,9 +246,9 @@ For more advanced Python usage and additional configuration options, you can exp
- **`--prompt`** (required, `str`): Text description of the image to generate. Use `-` to read the prompt from stdin (e.g., `echo "A beautiful sunset" | mflux-generate --prompt -`). - **`--prompt`** (required, `str`): Text description of the image to generate. Use `-` to read the prompt from stdin (e.g., `echo "A beautiful sunset" | mflux-generate --prompt -`).
- **`--model`** or **`-m`** (required, `str`): Model to use for generation. Can be one of the official models (`"schnell"` or `"dev"`) or a HuggingFace repository ID for a compatible third-party model (e.g., `"Freepik/flux.1-lite-8B-alpha"`). - **`--model`** or **`-m`** (required, `str`): Model to use for generation. Can be one of the official models (`"schnell"`, `"dev"`, or `"krea-dev"`) or a HuggingFace repository ID for a compatible third-party model (e.g., `"Freepik/flux.1-lite-8B-alpha"`).
- **`--base-model`** (optional, `str`, default: `None`): Specifies which base architecture a third-party model is derived from (`"schnell"` or `"dev"`). Required when using third-party models from HuggingFace. - **`--base-model`** (optional, `str`, default: `None`): Specifies which base architecture a third-party model is derived from (`"schnell"`, `"dev"`, or `"krea-dev"`). Required when using third-party models from HuggingFace.
- **`--output`** (optional, `str`, default: `"image.png"`): Output image filename. If `--seed` or `--auto-seeds` establishes multiple seed values, the output filename will automatically be modified to include the seed value (e.g., `image_seed_42.png`). - **`--output`** (optional, `str`, default: `"image.png"`): Output image filename. If `--seed` or `--auto-seeds` establishes multiple seed values, the output filename will automatically be modified to include the seed value (e.g., `image_seed_42.png`).
@ -246,7 +266,7 @@ For more advanced Python usage and additional configuration options, you can exp
- **`--path`** (optional, `str`, default: `None`): Path to a local model on disk. - **`--path`** (optional, `str`, default: `None`): Path to a local model on disk.
- **`--quantize`** or **`-q`** (optional, `int`, default: `None`): [Quantization](#%EF%B8%8F-quantization) (choose between `3`, `4`, `6`, or `8` bits). - **`--quantize`** or **`-q`** (optional, `int`, default: `None`): [Quantization](#%EF%B8%8F-quantization) (choose between `3`, `4`, `5`, `6`, or `8` bits).
- **`--lora-paths`** (optional, `[str]`, default: `None`): The paths to the [LoRA](#-LoRA) weights. - **`--lora-paths`** (optional, `[str]`, default: `None`): The paths to the [LoRA](#-LoRA) weights.
@ -698,7 +718,7 @@ Luxury food photograph of an italian Linguine pasta alle vongole dish with lots
### 🗜️ Quantization ### 🗜️ Quantization
MFLUX supports running FLUX in 3, 4, 6, or 8-bit quantized mode. Running a quantized version can greatly speed up the MFLUX supports running FLUX in 3, 4, 5, 6, or 8-bit quantized mode. Running a quantized version can greatly speed up the
generation process and reduce the memory consumption by several gigabytes. [Quantized models also take up less disk space](#-size-comparisons-for-quantized-models). generation process and reduce the memory consumption by several gigabytes. [Quantized models also take up less disk space](#-size-comparisons-for-quantized-models).
```sh ```sh
@ -726,9 +746,9 @@ For systems with limited RAM, you can also use the `--low-ram` option which redu
The model sizes for both `schnell` and `dev` at various quantization levels are as follows: The model sizes for both `schnell` and `dev` at various quantization levels are as follows:
| 3 bit | 4 bit | 6 bit | 8 bit | Original (16 bit) | | 3 bit | 4 bit | 5 bit | 6 bit | 8 bit | Original (16 bit) |
|--------|--------|---------|---------|-------------------| |--------|--------|---------|---------|---------|-------------------|
| 7.52GB | 9.61GB | 13.81GB | 18.01GB | 33.73GB | | 7.52GB | 9.61GB | 11.71GB | 13.81GB | 18.01GB | 33.73GB |
#### 💾 Saving a quantized version to disk #### 💾 Saving a quantized version to disk

View File

@ -15,7 +15,7 @@ source-exclude = [
[project] [project]
name = "mflux" name = "mflux"
version = "0.9.6" version = "0.10.0"
description = "A MLX port of FLUX based on the Huggingface Diffusers implementation." description = "A MLX port of FLUX based on the Huggingface Diffusers implementation."
readme = "README.md" readme = "README.md"
keywords = ["diffusers", "flux", "mlx"] keywords = ["diffusers", "flux", "mlx"]
@ -26,7 +26,7 @@ requires-python = ">=3.10"
dependencies = [ dependencies = [
"huggingface-hub>=0.24.5,<1.0", "huggingface-hub>=0.24.5,<1.0",
"matplotlib>=3.9.2,<4.0", "matplotlib>=3.9.2,<4.0",
"mlx>=0.22.0,<=0.26.1", "mlx>=0.27.0,<0.28.0",
"numpy>=2.0.1,<3.0", "numpy>=2.0.1,<3.0",
"opencv-python>=4.10.0,<5.0", "opencv-python>=4.10.0,<5.0",
"piexif>=1.1.3,<2.0", "piexif>=1.1.3,<2.0",
@ -61,7 +61,7 @@ classifiers = [
dev = [ dev = [
"pytest>=8.3.0,<9.0", "pytest>=8.3.0,<9.0",
"pytest-timer>=1.0,<2.0", "pytest-timer>=1.0,<2.0",
"mlx==0.26.1", # Used ONLY during test runs to ensure deterministic test results "mlx==0.27.1", # Used ONLY during test runs to ensure deterministic test results
] ]
[project.urls] [project.urls]

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 KiB

View File

@ -24,7 +24,7 @@ This module provides ZSH shell completion support for all mflux CLI commands.
- **Auto-completion for all 15 mflux commands**: Generate, train, save, upscale, and more - **Auto-completion for all 15 mflux commands**: Generate, train, save, upscale, and more
- **Smart completions**: - **Smart completions**:
- Model names (dev, schnell, dev-fill, or HuggingFace repos) - Model names (dev, schnell, dev-fill, or HuggingFace repos)
- Quantization levels (3, 4, 6, 8) - Quantization levels (3, 4, 5, 6, 8)
- LoRA styles (storyboard, portrait, etc.) - LoRA styles (storyboard, portrait, etc.)
- File paths with native completion - File paths with native completion
- Common percentage values for battery limit - Common percentage values for battery limit

View File

@ -7,7 +7,7 @@ from mflux.error.error import InvalidBaseModel, ModelConfigError
class ModelConfig: class ModelConfig:
def __init__( def __init__(
self, self,
alias: str | None, aliases: list[str],
model_name: str, model_name: str,
base_model: str | None, base_model: str | None,
controlnet_model: str | None, controlnet_model: str | None,
@ -18,7 +18,7 @@ class ModelConfig:
requires_sigma_shift: bool, requires_sigma_shift: bool,
priority: int, priority: int,
): ):
self.alias = alias self.aliases = aliases
self.model_name = model_name self.model_name = model_name
self.base_model = base_model self.base_model = base_model
self.controlnet_model = controlnet_model self.controlnet_model = controlnet_model
@ -79,49 +79,55 @@ class ModelConfig:
def dev_fill_catvton() -> "ModelConfig": def dev_fill_catvton() -> "ModelConfig":
return AVAILABLE_MODELS["dev-fill-catvton"] return AVAILABLE_MODELS["dev-fill-catvton"]
@staticmethod
@lru_cache
def krea_dev() -> "ModelConfig":
return AVAILABLE_MODELS["krea-dev"]
def x_embedder_input_dim(self) -> int: def x_embedder_input_dim(self) -> int:
if self.alias and "dev-fill" in self.alias: if "Fill" in self.model_name:
return 384 return 384
if self.alias == "dev-depth": if "Depth" in self.model_name:
return 128 return 128
else: else:
return 64 return 64
def is_canny(self) -> bool: def is_canny(self) -> bool:
return self.alias == "dev-controlnet-canny" or self.alias == "schnell-controlnet-canny" return self.controlnet_model is not None and "Canny" in self.controlnet_model
@staticmethod @staticmethod
def from_name( def from_name(
model_name: str, model_name: str,
base_model: Literal["dev", "schnell", "dev-fill"] | None = None, base_model: Literal["dev", "schnell", "krea-dev"] | None = None,
) -> "ModelConfig": ) -> "ModelConfig":
# 0. Get all base models (where base_model is None) sorted by priority # 0. Get all base models (where base_model is None) sorted by priority
base_models = sorted( base_models = sorted(
[model for model in AVAILABLE_MODELS.values() if model.base_model is None], key=lambda x: x.priority [model for model in AVAILABLE_MODELS.values() if model.base_model is None], key=lambda x: x.priority
) )
# 1. Check if model_name matches any base model's alias or full name # 1. Check if model_name matches any base model's aliases or full name
for base in base_models: for base in base_models:
if model_name in (base.alias, base.model_name): if model_name == base.model_name or model_name in base.aliases:
return base return base
# 2. Validate explicit base_model # 2. Validate explicit base_model
allowed_names = [] allowed_names = []
for base in base_models: for base in base_models:
allowed_names.extend([base.alias, base.model_name]) allowed_names.extend(base.aliases + [base.model_name])
if base_model and base_model not in allowed_names: if base_model and base_model not in allowed_names:
raise InvalidBaseModel(f"Invalid base_model. Choose one of {allowed_names}") raise InvalidBaseModel(f"Invalid base_model. Choose one of {allowed_names}")
# 3. Determine the base model (explicit or inferred) # 3. Determine the base model (explicit or inferred)
if base_model: if base_model:
# Find by explicit base_model name # Find by explicit base_model name (check all aliases)
default_base = next((b for b in base_models if base_model in (b.alias, b.model_name)), None) default_base = next((b for b in base_models if base_model == b.model_name or base_model in b.aliases), None)
else: else:
# Infer from model_name substring - prefer longer matches (more specific) # Infer from model_name substring - prefer longer matches (more specific)
matching_bases = [b for b in base_models if b.alias and b.alias in model_name] matching_bases = [(b, alias) for b in base_models for alias in b.aliases if alias and alias in model_name]
if matching_bases: if matching_bases:
# Sort by alias length descending, then by priority ascending # Sort by alias length descending, then by priority ascending
default_base = sorted(matching_bases, key=lambda x: (-len(x.alias), x.priority))[0] default_base = sorted(matching_bases, key=lambda x: (-len(x[1]), x[0].priority))[0][0]
else: else:
default_base = None default_base = None
if not default_base: if not default_base:
@ -129,7 +135,7 @@ class ModelConfig:
# 4. Construct the config # 4. Construct the config
return ModelConfig( return ModelConfig(
alias=default_base.alias, aliases=default_base.aliases,
model_name=model_name, model_name=model_name,
base_model=default_base.model_name, base_model=default_base.model_name,
controlnet_model=default_base.controlnet_model, controlnet_model=default_base.controlnet_model,
@ -144,7 +150,7 @@ class ModelConfig:
AVAILABLE_MODELS = { AVAILABLE_MODELS = {
"dev": ModelConfig( "dev": ModelConfig(
alias="dev", aliases=["dev"],
model_name="black-forest-labs/FLUX.1-dev", model_name="black-forest-labs/FLUX.1-dev",
base_model=None, base_model=None,
controlnet_model=None, controlnet_model=None,
@ -156,7 +162,7 @@ AVAILABLE_MODELS = {
priority=0, priority=0,
), ),
"schnell": ModelConfig( "schnell": ModelConfig(
alias="schnell", aliases=["schnell"],
model_name="black-forest-labs/FLUX.1-schnell", model_name="black-forest-labs/FLUX.1-schnell",
base_model=None, base_model=None,
controlnet_model=None, controlnet_model=None,
@ -168,7 +174,7 @@ AVAILABLE_MODELS = {
priority=1, priority=1,
), ),
"dev-kontext": ModelConfig( "dev-kontext": ModelConfig(
alias="dev-kontext", aliases=["dev-kontext"],
model_name="black-forest-labs/FLUX.1-Kontext-dev", model_name="black-forest-labs/FLUX.1-Kontext-dev",
base_model=None, base_model=None,
controlnet_model=None, controlnet_model=None,
@ -180,7 +186,7 @@ AVAILABLE_MODELS = {
priority=2, priority=2,
), ),
"dev-fill": ModelConfig( "dev-fill": ModelConfig(
alias="dev-fill", aliases=["dev-fill"],
model_name="black-forest-labs/FLUX.1-Fill-dev", model_name="black-forest-labs/FLUX.1-Fill-dev",
base_model=None, base_model=None,
controlnet_model=None, controlnet_model=None,
@ -192,7 +198,7 @@ AVAILABLE_MODELS = {
priority=3, priority=3,
), ),
"dev-redux": ModelConfig( "dev-redux": ModelConfig(
alias="dev-redux", aliases=["dev-redux"],
model_name="black-forest-labs/FLUX.1-Redux-dev", model_name="black-forest-labs/FLUX.1-Redux-dev",
base_model=None, base_model=None,
controlnet_model=None, controlnet_model=None,
@ -204,7 +210,7 @@ AVAILABLE_MODELS = {
priority=4, priority=4,
), ),
"dev-depth": ModelConfig( "dev-depth": ModelConfig(
alias="dev-depth", aliases=["dev-depth"],
model_name="black-forest-labs/FLUX.1-Depth-dev", model_name="black-forest-labs/FLUX.1-Depth-dev",
base_model=None, base_model=None,
controlnet_model=None, controlnet_model=None,
@ -216,7 +222,7 @@ AVAILABLE_MODELS = {
priority=5, priority=5,
), ),
"dev-controlnet-canny": ModelConfig( "dev-controlnet-canny": ModelConfig(
alias="dev-controlnet-canny", aliases=["dev-controlnet-canny"],
model_name="black-forest-labs/FLUX.1-dev", model_name="black-forest-labs/FLUX.1-dev",
base_model=None, base_model=None,
controlnet_model="InstantX/FLUX.1-dev-Controlnet-Canny", controlnet_model="InstantX/FLUX.1-dev-Controlnet-Canny",
@ -228,7 +234,7 @@ AVAILABLE_MODELS = {
priority=6, priority=6,
), ),
"schnell-controlnet-canny": ModelConfig( "schnell-controlnet-canny": ModelConfig(
alias="schnell-controlnet-canny", aliases=["schnell-controlnet-canny"],
model_name="black-forest-labs/FLUX.1-schnell", model_name="black-forest-labs/FLUX.1-schnell",
base_model=None, base_model=None,
controlnet_model="InstantX/FLUX.1-dev-Controlnet-Canny", controlnet_model="InstantX/FLUX.1-dev-Controlnet-Canny",
@ -240,7 +246,7 @@ AVAILABLE_MODELS = {
priority=7, priority=7,
), ),
"dev-controlnet-upscaler": ModelConfig( "dev-controlnet-upscaler": ModelConfig(
alias="dev-controlnet-upscaler", aliases=["dev-controlnet-upscaler"],
model_name="black-forest-labs/FLUX.1-dev", model_name="black-forest-labs/FLUX.1-dev",
base_model=None, base_model=None,
controlnet_model="jasperai/Flux.1-dev-Controlnet-Upscaler", controlnet_model="jasperai/Flux.1-dev-Controlnet-Upscaler",
@ -252,7 +258,7 @@ AVAILABLE_MODELS = {
priority=8, priority=8,
), ),
"dev-fill-catvton": ModelConfig( "dev-fill-catvton": ModelConfig(
alias="dev-fill-catvton", aliases=["dev-fill-catvton"],
model_name="black-forest-labs/FLUX.1-Fill-dev", model_name="black-forest-labs/FLUX.1-Fill-dev",
base_model=None, base_model=None,
controlnet_model=None, controlnet_model=None,
@ -263,4 +269,16 @@ AVAILABLE_MODELS = {
requires_sigma_shift=False, # Not sure why, but produced better results this way... requires_sigma_shift=False, # Not sure why, but produced better results this way...
priority=9, priority=9,
), ),
"krea-dev": ModelConfig(
aliases=["krea-dev", "dev-krea"],
model_name="black-forest-labs/FLUX.1-Krea-dev",
base_model=None,
controlnet_model=None,
custom_transformer_model=None,
num_train_steps=1000,
max_sequence_length=512,
supports_guidance=True,
requires_sigma_shift=True,
priority=10,
),
} }

View File

@ -18,7 +18,7 @@ class DepthProInitializer:
WeightHandlerDepthPro.reshape_transposed_convolution_weights(depth_pro_weights) WeightHandlerDepthPro.reshape_transposed_convolution_weights(depth_pro_weights)
# 2. Assign the weights to the model # 2. Assign the weights to the model
depth_pro_model.update(depth_pro_weights.weights) depth_pro_model.update(depth_pro_weights.weights, strict=False)
# 3. Optionally quantize the model # 3. Optionally quantize the model
if quantize: if quantize:

View File

@ -2,7 +2,6 @@ import shutil
import sys import sys
import time import time
from pathlib import Path from pathlib import Path
from typing import Optional
import requests import requests
from twine.commands import upload from twine.commands import upload
@ -34,9 +33,9 @@ class PyPIPublisher:
) )
@staticmethod @staticmethod
def version_exists_on_pypi(package_name: str, version: str, test_pypi: bool = False) -> bool: def version_exists_on_pypi(package_name: str, version: str) -> bool:
print("🔍 Checking if version already exists on PyPI...") print("🔍 Checking if version already exists on PyPI...")
repo_url = "https://test.pypi.org/pypi" if test_pypi else "https://pypi.org/pypi" repo_url = "https://pypi.org/pypi"
url = f"{repo_url}/{package_name}/json" url = f"{repo_url}/{package_name}/json"
for attempt in range(3): for attempt in range(3):
@ -77,21 +76,6 @@ class PyPIPublisher:
raise RuntimeError("Unexpected error in version_exists_on_pypi") raise RuntimeError("Unexpected error in version_exists_on_pypi")
@staticmethod
def publish_to_test_pypi(test_pypi_token: Optional[str], package_name: str, version: str) -> None:
if not test_pypi_token:
print("⚠️ Test PyPI token not provided, skipping Test PyPI upload")
return
PyPIPublisher._upload_to_pypi(
token=test_pypi_token,
repository="testpypi",
display_name="Test PyPI",
package_name=package_name,
version=version,
optional=True,
)
@staticmethod @staticmethod
def publish_to_pypi(pypi_token: str, package_name: str, version: str) -> None: def publish_to_pypi(pypi_token: str, package_name: str, version: str) -> None:
PyPIPublisher._upload_to_pypi( PyPIPublisher._upload_to_pypi(

View File

@ -1,5 +1,3 @@
from typing import Optional
import requests import requests
from mflux.release.changelog_parser import ChangelogParser from mflux.release.changelog_parser import ChangelogParser
@ -15,7 +13,6 @@ class ReleaseManager:
def create_release( def create_release(
github_token: str, github_token: str,
pypi_token: str, pypi_token: str,
test_pypi_token: Optional[str] = None,
github_repo: str = "filipstrand/mflux", github_repo: str = "filipstrand/mflux",
package_name: str = "mflux", package_name: str = "mflux",
) -> None: ) -> None:
@ -40,7 +37,6 @@ class ReleaseManager:
# 4. Handle PyPI publishing FIRST (before creating git artifacts) # 4. Handle PyPI publishing FIRST (before creating git artifacts)
if ReleaseManager._should_publish_to_pypi(git_tag_exists, github_release_exists, package_name, version): if ReleaseManager._should_publish_to_pypi(git_tag_exists, github_release_exists, package_name, version):
PyPIPublisher.build_and_verify_package() PyPIPublisher.build_and_verify_package()
PyPIPublisher.publish_to_test_pypi(test_pypi_token, package_name, version)
PyPIPublisher.publish_to_pypi(pypi_token, package_name, version) PyPIPublisher.publish_to_pypi(pypi_token, package_name, version)
# 5. Create git tag if needed # 5. Create git tag if needed
@ -76,7 +72,7 @@ class ReleaseManager:
# Check if version already exists on PyPI # Check if version already exists on PyPI
try: try:
if PyPIPublisher.version_exists_on_pypi(package_name, version, test_pypi=False): if PyPIPublisher.version_exists_on_pypi(package_name, version):
return False return False
except (requests.RequestException, ValueError, OSError) as e: except (requests.RequestException, ValueError, OSError) as e:
print(f"❌ Failed to check PyPI version: {e}") print(f"❌ Failed to check PyPI version: {e}")

View File

@ -11,14 +11,12 @@ def main():
parser = argparse.ArgumentParser(description="MFLUX Release Management") parser = argparse.ArgumentParser(description="MFLUX Release Management")
parser.add_argument("--github-token", help="GitHub token") parser.add_argument("--github-token", help="GitHub token")
parser.add_argument("--pypi-token", help="PyPI API token") parser.add_argument("--pypi-token", help="PyPI API token")
parser.add_argument("--test-pypi-token", help="Test PyPI API token (optional)")
args = parser.parse_args() args = parser.parse_args()
try: try:
ReleaseManager.create_release( ReleaseManager.create_release(
github_token=args.github_token or os.getenv("GITHUB_TOKEN"), github_token=args.github_token or os.getenv("GITHUB_TOKEN"),
pypi_token=args.pypi_token or os.getenv("PYPI_API_TOKEN"), pypi_token=args.pypi_token or os.getenv("PYPI_API_TOKEN"),
test_pypi_token=args.test_pypi_token or os.getenv("TEST_PYPI_API_TOKEN"),
) )
except (ValueError, FileNotFoundError, requests.RequestException) as e: except (ValueError, FileNotFoundError, requests.RequestException) as e:
print(f"❌ Release failed: {e}") print(f"❌ Release failed: {e}")

View File

@ -289,7 +289,7 @@ class CommandLineParser(argparse.ArgumentParser):
self.error("Either --prompt or --prompt-file argument is required, or 'prompt' required in metadata config file") self.error("Either --prompt or --prompt-file argument is required, or 'prompt' required in metadata config file")
if self.supports_image_generation and namespace.steps is None: if self.supports_image_generation and namespace.steps is None:
namespace.steps = ui_defaults.MODEL_INFERENCE_STEPS.get(namespace.model, 14) namespace.steps = ui_defaults.MODEL_INFERENCE_STEPS.get(namespace.model, 25)
# In-context edit specific validations # In-context edit specific validations
if getattr(self, 'supports_in_context_edit', False): if getattr(self, 'supports_in_context_edit', False):

View File

@ -17,16 +17,13 @@ GUIDANCE_SCALE_KONTEXT = 2.5
HEIGHT, WIDTH = 1024, 1024 HEIGHT, WIDTH = 1024, 1024
MAX_PIXELS_WARNING_THRESHOLD = 2048 * 2048 MAX_PIXELS_WARNING_THRESHOLD = 2048 * 2048
IMAGE_STRENGTH = 0.4 IMAGE_STRENGTH = 0.4
MODEL_CHOICES = ["dev", "schnell", "dev-kontext", "dev-fill"] MODEL_CHOICES = ["dev", "schnell", "krea-dev"]
MODEL_INFERENCE_STEPS = { MODEL_INFERENCE_STEPS = {
"dev": 14, "dev": 25,
"dev-fill": 14, "krea-dev": 25,
"dev-depth": 14,
"dev-redux": 14,
"dev-kontext": 14,
"schnell": 4, "schnell": 4,
} }
QUANTIZE_CHOICES = [3, 4, 6, 8] QUANTIZE_CHOICES = [3, 5, 4, 6, 8]
def _migrate_legacy_cache(new_cache_dir: Path) -> None: def _migrate_legacy_cache(new_cache_dir: Path) -> None:

View File

@ -57,19 +57,19 @@ class WeightUtil:
transformer_controlnet: nn.Module, transformer_controlnet: nn.Module,
) -> int | None: ) -> int | None:
if weights.meta_data.quantization_level is None and quantize_arg is None: if weights.meta_data.quantization_level is None and quantize_arg is None:
transformer_controlnet.update(weights.controlnet_transformer) transformer_controlnet.update(weights.controlnet_transformer, strict=False)
return None return None
if weights.meta_data.quantization_level is None and quantize_arg is not None: if weights.meta_data.quantization_level is None and quantize_arg is not None:
bits = quantize_arg bits = quantize_arg
transformer_controlnet.update(weights.controlnet_transformer) transformer_controlnet.update(weights.controlnet_transformer, strict=False)
QuantizationUtil.quantize_controlnet(bits, weights, transformer_controlnet) QuantizationUtil.quantize_controlnet(bits, weights, transformer_controlnet)
return bits return bits
if weights.meta_data.quantization_level is not None: if weights.meta_data.quantization_level is not None:
bits = weights.meta_data.quantization_level bits = weights.meta_data.quantization_level
QuantizationUtil.quantize_controlnet(bits, weights, transformer_controlnet) QuantizationUtil.quantize_controlnet(bits, weights, transformer_controlnet)
transformer_controlnet.update(weights.controlnet_transformer) transformer_controlnet.update(weights.controlnet_transformer, strict=False)
return bits return bits
@staticmethod @staticmethod
@ -80,10 +80,10 @@ class WeightUtil:
t5_text_encoder: nn.Module, t5_text_encoder: nn.Module,
clip_text_encoder: nn.Module, clip_text_encoder: nn.Module,
): ):
vae.update(weights.vae) vae.update(weights.vae, strict=False)
transformer.update(weights.transformer) transformer.update(weights.transformer, strict=False)
t5_text_encoder.update(weights.t5_encoder) t5_text_encoder.update(weights.t5_encoder, strict=False)
clip_text_encoder.update(weights.clip_encoder) clip_text_encoder.update(weights.clip_encoder, strict=False)
@staticmethod @staticmethod
def _set_redux_model_weights( def _set_redux_model_weights(
@ -91,8 +91,8 @@ class WeightUtil:
redux_encoder: nn.Module, redux_encoder: nn.Module,
siglip_vision_transformer: nn.Module, siglip_vision_transformer: nn.Module,
): ):
redux_encoder.update(weights.redux_encoder) redux_encoder.update(weights.redux_encoder, strict=False)
siglip_vision_transformer.update(weights.siglip["vision_model"]) siglip_vision_transformer.update(weights.siglip["vision_model"], strict=False)
@staticmethod @staticmethod
def set_redux_weights_and_quantize( def set_redux_weights_and_quantize(

View File

@ -410,7 +410,7 @@ def test_steps_arg(mflux_generate_parser, mflux_generate_minimal_argv, base_meta
# test user default value for dev # test user default value for dev
with patch("sys.argv", mflux_generate_minimal_argv + ["--model", "dev"]): with patch("sys.argv", mflux_generate_minimal_argv + ["--model", "dev"]):
args = mflux_generate_parser.parse_args() args = mflux_generate_parser.parse_args()
assert args.steps == 14 assert args.steps == 25
# test user default value for schnell # test user default value for schnell
with patch("sys.argv", mflux_generate_minimal_argv + ["--model", "schnell"]): with patch("sys.argv", mflux_generate_minimal_argv + ["--model", "schnell"]):

View File

@ -57,8 +57,8 @@ class TestImageGenerator:
def test_image_generation_dev_image_to_image(self): def test_image_generation_dev_image_to_image(self):
ImageGeneratorTestHelper.assert_matches_reference_image( ImageGeneratorTestHelper.assert_matches_reference_image(
reference_image_path="reference_dev_image_to_image_result.png", reference_image_path="reference_dev_image_to_image.png",
output_image_path="output_dev_image_to_image_result.png", output_image_path="output_dev_image_to_image.png",
image_strength=0.4, image_strength=0.4,
model_config=ModelConfig.dev(), model_config=ModelConfig.dev(),
steps=8, steps=8,

View File

@ -3,13 +3,11 @@ from tests.image_generation.helpers.image_generation_controlnet_test_helper impo
class TestImageGeneratorControlnet: class TestImageGeneratorControlnet:
CONTROLNET_REFERENCE_FILENAME = "controlnet_reference.png"
def test_image_generation_schnell_controlnet(self): def test_image_generation_schnell_controlnet(self):
ImageGeneratorControlnetTestHelper.assert_matches_reference_image( ImageGeneratorControlnetTestHelper.assert_matches_reference_image(
reference_image_path="reference_controlnet_schnell.png", reference_image_path="reference_controlnet_schnell.png",
output_image_path="output_controlnet_schnell.png", output_image_path="output_controlnet_schnell.png",
controlnet_image_path=TestImageGeneratorControlnet.CONTROLNET_REFERENCE_FILENAME, controlnet_image_path="controlnet_reference.png",
model_config=ModelConfig.schnell_controlnet_canny(), model_config=ModelConfig.schnell_controlnet_canny(),
steps=2, steps=2,
seed=43, seed=43,
@ -23,7 +21,7 @@ class TestImageGeneratorControlnet:
ImageGeneratorControlnetTestHelper.assert_matches_reference_image( ImageGeneratorControlnetTestHelper.assert_matches_reference_image(
reference_image_path="reference_controlnet_dev.png", reference_image_path="reference_controlnet_dev.png",
output_image_path="output_controlnet_dev.png", output_image_path="output_controlnet_dev.png",
controlnet_image_path=TestImageGeneratorControlnet.CONTROLNET_REFERENCE_FILENAME, controlnet_image_path="controlnet_reference.png",
model_config=ModelConfig.dev_controlnet_canny(), model_config=ModelConfig.dev_controlnet_canny(),
steps=15, steps=15,
seed=42, seed=42,
@ -37,7 +35,7 @@ class TestImageGeneratorControlnet:
ImageGeneratorControlnetTestHelper.assert_matches_reference_image( ImageGeneratorControlnetTestHelper.assert_matches_reference_image(
reference_image_path="reference_controlnet_dev_lora.png", reference_image_path="reference_controlnet_dev_lora.png",
output_image_path="output_controlnet_dev_lora.png", output_image_path="output_controlnet_dev_lora.png",
controlnet_image_path=TestImageGeneratorControlnet.CONTROLNET_REFERENCE_FILENAME, controlnet_image_path="controlnet_reference.png",
model_config=ModelConfig.dev_controlnet_canny(), model_config=ModelConfig.dev_controlnet_canny(),
steps=15, steps=15,
seed=43, seed=43,
@ -52,7 +50,7 @@ class TestImageGeneratorControlnet:
def test_image_upscaling(self): def test_image_upscaling(self):
ImageGeneratorControlnetTestHelper.assert_matches_reference_image( ImageGeneratorControlnetTestHelper.assert_matches_reference_image(
reference_image_path="reference_upscaled.png", reference_image_path="reference_upscaled.png",
output_image_path="output_upscaler.png", output_image_path="output_upscaled.png",
controlnet_image_path="low_res.jpg", controlnet_image_path="low_res.jpg",
model_config=ModelConfig.dev_controlnet_upscaler(), model_config=ModelConfig.dev_controlnet_upscaler(),
steps=20, steps=20,

View File

@ -3,13 +3,11 @@ from tests.image_generation.helpers.image_generation_depth_test_helper import Im
class TestImageGeneratorDepth: class TestImageGeneratorDepth:
SOURCE_IMAGE_FILENAME = "reference_controlnet_dev_lora.png"
def test_image_generation_with_reference_image(self): def test_image_generation_with_reference_image(self):
ImageGeneratorDepthTestHelper.assert_matches_reference_image( ImageGeneratorDepthTestHelper.assert_matches_reference_image(
reference_image_path="reference_depth_dev_from_image.png", reference_image_path="reference_depth_dev_from_image.png",
output_image_path="output_depth_dev_from_image.png", output_image_path="output_depth_dev_from_image.png",
image_path=TestImageGeneratorDepth.SOURCE_IMAGE_FILENAME, image_path="reference_controlnet_dev_lora.png",
model_config=ModelConfig.dev_depth(), model_config=ModelConfig.dev_depth(),
steps=15, steps=15,
seed=42, seed=42,

View File

@ -3,19 +3,16 @@ from tests.image_generation.helpers.image_generation_fill_test_helper import Ima
class TestImageGeneratorFill: class TestImageGeneratorFill:
SOURCE_IMAGE_FILENAME = "reference_dev_image_to_image_result.png"
MASK_IMAGE_FILENAME = "mask.png"
def test_fill(self): def test_fill(self):
ImageGeneratorFillTestHelper.assert_matches_reference_image( ImageGeneratorFillTestHelper.assert_matches_reference_image(
reference_image_path="reference_dev_image_to_image_result_inpaint.png", reference_image_path="reference_dev_image_to_image_fill.png",
output_image_path="output_dev_image_to_image_result_inpaint.png", output_image_path="output_dev_image_to_image_fill.png",
model_config=ModelConfig.dev_fill(), model_config=ModelConfig.dev_fill(),
steps=15, steps=15,
seed=42, seed=42,
height=341, height=341,
width=768, width=768,
prompt="A burger and crispy golden french fries next to it", prompt="A burger and crispy golden french fries next to it",
image_path=TestImageGeneratorFill.SOURCE_IMAGE_FILENAME, image_path="reference_dev_image_to_image.png",
masked_image_path=TestImageGeneratorFill.MASK_IMAGE_FILENAME, masked_image_path="mask.png",
) )

View File

@ -6,8 +6,8 @@ from tests.image_generation.helpers.image_generation_in_context_test_helper impo
class TestImageGeneratorInContext: class TestImageGeneratorInContext:
def test_in_context_lora_identity(self): def test_in_context_lora_identity(self):
ImageGeneratorInContextTestHelper.assert_matches_reference_image( ImageGeneratorInContextTestHelper.assert_matches_reference_image(
reference_image_path="ic_lora_reference_in_context_identity.png", reference_image_path="reference_ic_lora_identity.png",
output_image_path="output_ic_lora_reference_in_context_identity.png", output_image_path="output_ic_lora_identity.png",
model_config=ModelConfig.dev(), model_config=ModelConfig.dev(),
steps=25, steps=25,
seed=42, seed=42,
@ -20,7 +20,7 @@ class TestImageGeneratorInContext:
def test_ic_edit_with_instruction(self): def test_ic_edit_with_instruction(self):
ImageGeneratorICEditTestHelper.assert_matches_reference_image( ImageGeneratorICEditTestHelper.assert_matches_reference_image(
reference_image_path="ic_edit_reference.png", reference_image_path="reference_ic_edit.png",
output_image_path="output_ic_edit.png", output_image_path="output_ic_edit.png",
steps=20, steps=20,
seed=2799665, seed=2799665,

View File

@ -3,8 +3,6 @@ from tests.image_generation.helpers.image_generation_redux_test_helper import Im
class TestImageGeneratorRedux: class TestImageGeneratorRedux:
SOURCE_IMAGE_FILENAME = "reference_dev_image_to_image_result.png"
def test_image_generation_redux(self): def test_image_generation_redux(self):
ImageGeneratorReduxTestHelper.assert_matches_reference_image( ImageGeneratorReduxTestHelper.assert_matches_reference_image(
reference_image_path="reference_redux_dev.png", reference_image_path="reference_redux_dev.png",
@ -15,5 +13,5 @@ class TestImageGeneratorRedux:
height=341, height=341,
width=768, width=768,
prompt="A delicious burger", prompt="A delicious burger",
redux_image_path=TestImageGeneratorRedux.SOURCE_IMAGE_FILENAME, redux_image_path="reference_dev_image_to_image.png",
) )

View File

@ -6,8 +6,7 @@ from mflux.error.error import InvalidBaseModel, ModelConfigError
def test_bfl_dev(): def test_bfl_dev():
model = ModelConfig.from_name("dev") model = ModelConfig.from_name("dev")
assert model.alias == "dev" assert model.model_name == "black-forest-labs/FLUX.1-dev"
assert model.model_name.startswith("black-forest-labs/")
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is True assert model.supports_guidance is True
@ -16,8 +15,34 @@ def test_bfl_dev():
def test_bfl_dev_full_name(): def test_bfl_dev_full_name():
model = ModelConfig.from_name("black-forest-labs/FLUX.1-dev") model = ModelConfig.from_name("black-forest-labs/FLUX.1-dev")
assert model.alias == "dev" assert model.model_name == "black-forest-labs/FLUX.1-dev"
assert model.model_name.startswith("black-forest-labs/") assert model.max_sequence_length == 512
assert model.num_train_steps == 1000
assert model.supports_guidance is True
assert model.requires_sigma_shift is True
def test_bfl_krea_dev():
model = ModelConfig.from_name("krea-dev")
assert model.model_name == "black-forest-labs/FLUX.1-Krea-dev"
assert model.max_sequence_length == 512
assert model.num_train_steps == 1000
assert model.supports_guidance is True
assert model.requires_sigma_shift is True
def test_bfl_dev_krea_alias():
model = ModelConfig.from_name("dev-krea")
assert model.model_name == "black-forest-labs/FLUX.1-Krea-dev"
assert model.max_sequence_length == 512
assert model.num_train_steps == 1000
assert model.supports_guidance is True
assert model.requires_sigma_shift is True
def test_bfl_krea_dev_full_name():
model = ModelConfig.from_name("black-forest-labs/FLUX.1-Krea-dev")
assert model.model_name == "black-forest-labs/FLUX.1-Krea-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is True assert model.supports_guidance is True
@ -26,8 +51,7 @@ def test_bfl_dev_full_name():
def test_bfl_schnell(): def test_bfl_schnell():
model = ModelConfig.from_name("schnell") model = ModelConfig.from_name("schnell")
assert model.alias == "schnell" assert model.model_name == "black-forest-labs/FLUX.1-schnell"
assert model.model_name.startswith("black-forest-labs/")
assert model.max_sequence_length == 256 assert model.max_sequence_length == 256
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is False assert model.supports_guidance is False
@ -36,8 +60,7 @@ def test_bfl_schnell():
def test_bfl_schnell_full_name(): def test_bfl_schnell_full_name():
model = ModelConfig.from_name("black-forest-labs/FLUX.1-schnell") model = ModelConfig.from_name("black-forest-labs/FLUX.1-schnell")
assert model.alias == "schnell" assert model.model_name == "black-forest-labs/FLUX.1-schnell"
assert model.model_name.startswith("black-forest-labs/")
assert model.max_sequence_length == 256 assert model.max_sequence_length == 256
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is False assert model.supports_guidance is False
@ -46,7 +69,6 @@ def test_bfl_schnell_full_name():
def test_bfl_dev_fill(): def test_bfl_dev_fill():
model = ModelConfig.from_name("dev-fill") model = ModelConfig.from_name("dev-fill")
assert model.alias == "dev-fill"
assert model.model_name == "black-forest-labs/FLUX.1-Fill-dev" assert model.model_name == "black-forest-labs/FLUX.1-Fill-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
@ -56,7 +78,6 @@ def test_bfl_dev_fill():
def test_bfl_dev_fill_full_name(): def test_bfl_dev_fill_full_name():
model = ModelConfig.from_name("black-forest-labs/FLUX.1-Fill-dev") model = ModelConfig.from_name("black-forest-labs/FLUX.1-Fill-dev")
assert model.alias == "dev-fill"
assert model.model_name == "black-forest-labs/FLUX.1-Fill-dev" assert model.model_name == "black-forest-labs/FLUX.1-Fill-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
@ -66,7 +87,6 @@ def test_bfl_dev_fill_full_name():
def test_bfl_dev_depth(): def test_bfl_dev_depth():
model = ModelConfig.from_name("dev-depth") model = ModelConfig.from_name("dev-depth")
assert model.alias == "dev-depth"
assert model.model_name == "black-forest-labs/FLUX.1-Depth-dev" assert model.model_name == "black-forest-labs/FLUX.1-Depth-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
@ -76,7 +96,6 @@ def test_bfl_dev_depth():
def test_bfl_dev_depth_full_name(): def test_bfl_dev_depth_full_name():
model = ModelConfig.from_name("black-forest-labs/FLUX.1-Depth-dev") model = ModelConfig.from_name("black-forest-labs/FLUX.1-Depth-dev")
assert model.alias == "dev-depth"
assert model.model_name == "black-forest-labs/FLUX.1-Depth-dev" assert model.model_name == "black-forest-labs/FLUX.1-Depth-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
@ -86,7 +105,6 @@ def test_bfl_dev_depth_full_name():
def test_bfl_dev_redux(): def test_bfl_dev_redux():
model = ModelConfig.from_name("dev-redux") model = ModelConfig.from_name("dev-redux")
assert model.alias == "dev-redux"
assert model.model_name == "black-forest-labs/FLUX.1-Redux-dev" assert model.model_name == "black-forest-labs/FLUX.1-Redux-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
@ -96,7 +114,6 @@ def test_bfl_dev_redux():
def test_bfl_dev_redux_full_name(): def test_bfl_dev_redux_full_name():
model = ModelConfig.from_name("black-forest-labs/FLUX.1-Redux-dev") model = ModelConfig.from_name("black-forest-labs/FLUX.1-Redux-dev")
assert model.alias == "dev-redux"
assert model.model_name == "black-forest-labs/FLUX.1-Redux-dev" assert model.model_name == "black-forest-labs/FLUX.1-Redux-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
@ -106,7 +123,6 @@ def test_bfl_dev_redux_full_name():
def test_bfl_dev_controlnet_canny(): def test_bfl_dev_controlnet_canny():
model = ModelConfig.from_name("dev-controlnet-canny") model = ModelConfig.from_name("dev-controlnet-canny")
assert model.alias == "dev-controlnet-canny"
assert model.model_name == "black-forest-labs/FLUX.1-dev" assert model.model_name == "black-forest-labs/FLUX.1-dev"
assert model.controlnet_model == "InstantX/FLUX.1-dev-Controlnet-Canny" assert model.controlnet_model == "InstantX/FLUX.1-dev-Controlnet-Canny"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
@ -118,7 +134,6 @@ def test_bfl_dev_controlnet_canny():
def test_bfl_schnell_controlnet_canny(): def test_bfl_schnell_controlnet_canny():
model = ModelConfig.from_name("schnell-controlnet-canny") model = ModelConfig.from_name("schnell-controlnet-canny")
assert model.alias == "schnell-controlnet-canny"
assert model.model_name == "black-forest-labs/FLUX.1-schnell" assert model.model_name == "black-forest-labs/FLUX.1-schnell"
assert model.controlnet_model == "InstantX/FLUX.1-dev-Controlnet-Canny" assert model.controlnet_model == "InstantX/FLUX.1-dev-Controlnet-Canny"
assert model.max_sequence_length == 256 assert model.max_sequence_length == 256
@ -130,7 +145,6 @@ def test_bfl_schnell_controlnet_canny():
def test_bfl_dev_controlnet_upscaler(): def test_bfl_dev_controlnet_upscaler():
model = ModelConfig.from_name("dev-controlnet-upscaler") model = ModelConfig.from_name("dev-controlnet-upscaler")
assert model.alias == "dev-controlnet-upscaler"
assert model.model_name == "black-forest-labs/FLUX.1-dev" assert model.model_name == "black-forest-labs/FLUX.1-dev"
assert model.controlnet_model == "jasperai/Flux.1-dev-Controlnet-Upscaler" assert model.controlnet_model == "jasperai/Flux.1-dev-Controlnet-Upscaler"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
@ -142,7 +156,7 @@ def test_bfl_dev_controlnet_upscaler():
def test_community_dev_fill_implicit_base_model(): def test_community_dev_fill_implicit_base_model():
model = ModelConfig.from_name("acme-lab/some-dev-fill-model") model = ModelConfig.from_name("acme-lab/some-dev-fill-model")
assert model.alias == "dev-fill" assert model.base_model == "black-forest-labs/FLUX.1-Fill-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is True assert model.supports_guidance is True
@ -151,7 +165,7 @@ def test_community_dev_fill_implicit_base_model():
def test_community_dev_fill_explicit_base_model(): def test_community_dev_fill_explicit_base_model():
model = ModelConfig.from_name("acme-lab/some-model", base_model="dev-fill") model = ModelConfig.from_name("acme-lab/some-model", base_model="dev-fill")
assert model.alias == "dev-fill" assert model.base_model == "black-forest-labs/FLUX.1-Fill-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is True assert model.supports_guidance is True
@ -160,7 +174,6 @@ def test_community_dev_fill_explicit_base_model():
def test_implicit_base_model_prefers_dev_fill_over_dev(): def test_implicit_base_model_prefers_dev_fill_over_dev():
model = ModelConfig.from_name("acme-lab/dev-fill-based-model") model = ModelConfig.from_name("acme-lab/dev-fill-based-model")
assert model.alias == "dev-fill"
assert model.base_model == "black-forest-labs/FLUX.1-Fill-dev" assert model.base_model == "black-forest-labs/FLUX.1-Fill-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
@ -169,7 +182,7 @@ def test_implicit_base_model_prefers_dev_fill_over_dev():
def test_community_dev_implicit_base_model(): def test_community_dev_implicit_base_model():
model = ModelConfig.from_name("acme-lab/some-awesome-dev-model") model = ModelConfig.from_name("acme-lab/some-awesome-dev-model")
assert model.alias == "dev" assert model.base_model == "black-forest-labs/FLUX.1-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is True assert model.supports_guidance is True
@ -178,7 +191,7 @@ def test_community_dev_implicit_base_model():
def test_community_schnell_implicit_base_model(): def test_community_schnell_implicit_base_model():
model = ModelConfig.from_name("acme-lab/some-quick-schnell-model") model = ModelConfig.from_name("acme-lab/some-quick-schnell-model")
assert model.alias == "schnell" assert model.base_model == "black-forest-labs/FLUX.1-schnell"
assert model.max_sequence_length == 256 assert model.max_sequence_length == 256
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000
assert model.supports_guidance is False assert model.supports_guidance is False
@ -187,7 +200,6 @@ def test_community_schnell_implicit_base_model():
def test_community_dev_explicit_base_model(): def test_community_dev_explicit_base_model():
model = ModelConfig.from_name("acme-lab/some-awesome-model", base_model="dev") model = ModelConfig.from_name("acme-lab/some-awesome-model", base_model="dev")
assert model.alias == "dev"
assert model.base_model == "black-forest-labs/FLUX.1-dev" assert model.base_model == "black-forest-labs/FLUX.1-dev"
assert model.max_sequence_length == 512 assert model.max_sequence_length == 512
assert model.num_train_steps == 1000 assert model.num_train_steps == 1000

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 KiB

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 KiB

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 KiB

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 268 KiB

53
uv.lock generated
View File

@ -712,7 +712,7 @@ wheels = [
[[package]] [[package]]
name = "mflux" name = "mflux"
version = "0.9.6" version = "0.10.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "huggingface-hub" }, { name = "huggingface-hub" },
@ -746,8 +746,8 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "huggingface-hub", specifier = ">=0.24.5,<1.0" }, { name = "huggingface-hub", specifier = ">=0.24.5,<1.0" },
{ name = "matplotlib", specifier = ">=3.9.2,<4.0" }, { name = "matplotlib", specifier = ">=3.9.2,<4.0" },
{ name = "mlx", specifier = ">=0.22.0,<=0.26.1" }, { name = "mlx", specifier = ">=0.27.0,<0.28.0" },
{ name = "mlx", marker = "extra == 'dev'", specifier = "==0.26.1" }, { name = "mlx", marker = "extra == 'dev'", specifier = "==0.27.1" },
{ name = "numpy", specifier = ">=2.0.1,<3.0" }, { name = "numpy", specifier = ">=2.0.1,<3.0" },
{ name = "opencv-python", specifier = ">=4.10.0,<5.0" }, { name = "opencv-python", specifier = ">=4.10.0,<5.0" },
{ name = "piexif", specifier = ">=1.1.3,<2.0" }, { name = "piexif", specifier = ">=1.1.3,<2.0" },
@ -770,25 +770,38 @@ provides-extras = ["dev"]
[[package]] [[package]]
name = "mlx" name = "mlx"
version = "0.26.1" version = "0.27.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mlx-metal", marker = "sys_platform == 'darwin'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/ed/07a7145c500914ed8ab704a7a3828a5134e85f8b4458e1fee887306867d6/mlx-0.27.1-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:a033b65fe46425ad5032867d5a71a556a5168108d89aa7092b457556c70d84fc", size = 557679, upload-time = "2025-07-25T22:55:31.942Z" },
{ url = "https://files.pythonhosted.org/packages/aa/ae/b3e46904ea04b5badb77195169880733f09976a3dfc81c954b99f9adbbc4/mlx-0.27.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:2836c6fd9803dc0c6cd06f204e31b3e0191e6c5b6bc8570b28661d926908cba3", size = 530556, upload-time = "2025-07-25T22:56:13.171Z" },
{ url = "https://files.pythonhosted.org/packages/6e/24/85f5a7dfcf0b4974aa43a2f6dc0d59dfc04e4a1051aacebd7e7810b823ff/mlx-0.27.1-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:8b0566054c46d84c470cc99cda2afc3914ad6c7808fbb724dc1ec235e5b2a98c", size = 530559, upload-time = "2025-07-25T22:55:39.055Z" },
{ url = "https://files.pythonhosted.org/packages/14/bd/91ad900837a3760ba056863c1350f0deabc3e462c3999b4d5ea875368177/mlx-0.27.1-cp310-cp310-manylinux_2_35_x86_64.whl", hash = "sha256:91ef93ce09900c9a8ca662cf34e3c39ab5af2762822ecd6b12fecae518be167f", size = 628183, upload-time = "2025-07-25T22:58:11.553Z" },
{ url = "https://files.pythonhosted.org/packages/ac/63/88cb9a5019bea21244385eacc4c64deb4d074a8bc3b4b6d2d97cdacf97a2/mlx-0.27.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:d2e5dedbfbcbe558e51a5c476ca6a18e307676f9e49854eb27e53778bc474699", size = 557582, upload-time = "2025-07-25T22:55:22.243Z" },
{ url = "https://files.pythonhosted.org/packages/fa/87/c2d7343e7b054481c52e23a190911c40aed4f8c77a8dfeda1f48d5cb520a/mlx-0.27.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:9f04b9778897a879c9ca22e5413dfa1efc192d86d7211b184e079efec49dfb8b", size = 530820, upload-time = "2025-07-25T22:55:34.121Z" },
{ url = "https://files.pythonhosted.org/packages/06/bf/20497ca7411028baa56372c20e94a3eaddac973155b415f08fc12592c2cf/mlx-0.27.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:01d794f9e390438ab4f942a18d9a8ca65bef10c2c2007ef38ca988d039d6d9d3", size = 530821, upload-time = "2025-07-25T22:55:28.847Z" },
{ url = "https://files.pythonhosted.org/packages/48/68/234a0d846576502ea1bf8ea478c764d87eec9042349e298d2aea58c4e8e9/mlx-0.27.1-cp311-cp311-manylinux_2_35_x86_64.whl", hash = "sha256:fae11432d0639789f1e172b19b35ac8987c8ab9716e55a23fc7a170d6545fc33", size = 628500, upload-time = "2025-07-25T22:57:59.556Z" },
{ url = "https://files.pythonhosted.org/packages/65/43/125102bbb2be6825880ae2dc8d8702f99cfa7753407f574457b36e422218/mlx-0.27.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:0c570c9afb57c697bd864504115be8a7c4de97f0b80557a597d496ee426a6812", size = 549869, upload-time = "2025-07-25T22:55:32.698Z" },
{ url = "https://files.pythonhosted.org/packages/f3/79/0bf681700fc8b165517e907f9ec777b5a5d628004a65a777148f68c6baa0/mlx-0.27.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ccff7bbbd9df302b26e79013ef6d0c3531c9ba5963ead521e2d85856811b86a0", size = 531671, upload-time = "2025-07-25T22:57:03.392Z" },
{ url = "https://files.pythonhosted.org/packages/ec/97/f1367b4892bef7f78e38737d3a28094e93124f11684a28a9e92ed5a13b2b/mlx-0.27.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9ccadaed449c07dfeae484620992b904c17dfea7564f8df63095c60eed3af02b", size = 531672, upload-time = "2025-07-25T22:55:35.779Z" },
{ url = "https://files.pythonhosted.org/packages/9c/0f/3ebec0806ed2c5ba8ba151394cd62a46b4d83ea347da33af91b86d8969d4/mlx-0.27.1-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:742c413e75605b71db69379a176da63e32ba19b9e9ad03b8763adbd1fcfcd394", size = 621661, upload-time = "2025-07-25T22:58:00.96Z" },
{ url = "https://files.pythonhosted.org/packages/86/f6/4324386b0764deb692e14a97282a348a9a938aa8b441bf8b6c7599f418d4/mlx-0.27.1-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:803669a28031766c2b0fe94c0a3bfd030184e706092f0a831b33620c1e2ef865", size = 549847, upload-time = "2025-07-25T22:55:17.581Z" },
{ url = "https://files.pythonhosted.org/packages/cf/4b/3194ccb03527a050c04d837d731a11599f8620e6ce16d3971798caae1d44/mlx-0.27.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e9649b3d86ce564000797384510c9d07af38a9ce2a07df8e2f7c6a3e0f0f059e", size = 531664, upload-time = "2025-07-25T22:56:02.928Z" },
{ url = "https://files.pythonhosted.org/packages/cc/57/a6e0d8dc6e7ba08a64d71fb89d743e77446040113ea1dbb7950be8f60f39/mlx-0.27.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:5c501ceec7c6aa2ea1c850dd4e14e679f5416a142264b9d5d405a4e0aeb991b2", size = 531663, upload-time = "2025-07-25T22:55:38.62Z" },
{ url = "https://files.pythonhosted.org/packages/48/96/6932e0fa866d08cdf77a3fc37cf624e4cac8960d2e297c3fd80afb49417d/mlx-0.27.1-cp313-cp313-manylinux_2_35_x86_64.whl", hash = "sha256:42bfbabdcd0b7aa1c94748c8963e66680a3cf3f599b01036db14e496bf276eb8", size = 621609, upload-time = "2025-07-25T22:58:21.477Z" },
]
[[package]]
name = "mlx-metal"
version = "0.27.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/91/9d/645600137d0b7d4689e081de1c62247dae448dfadd8ebdac0fea8c70da33/mlx-0.26.1-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:8f88900eaef76f8f23e957ef303f3664c273c73a504b4e57bb491e397d7a891e", size = 32399627, upload-time = "2025-06-04T01:02:30.075Z" }, { url = "https://files.pythonhosted.org/packages/8b/77/89fa3327011f018638c9e943e1edc081ce9ca0ed296fe64d6daf93c6ff51/mlx_metal-0.27.1-py3-none-macosx_13_0_arm64.whl", hash = "sha256:c66d9b1adb3c0ea19492fba6493f672bc7542e65dd65f7e2995918815fbeb907", size = 33523035, upload-time = "2025-07-25T22:58:02.533Z" },
{ url = "https://files.pythonhosted.org/packages/50/8c/132c081c62ff3352f62bd25c9fc4f654d959402ff94de7c269b53479978d/mlx-0.26.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:794b52f2ae52969aac8fa5c271f55a668c0f802cbacda70652033db9c11887c6", size = 31869876, upload-time = "2025-06-04T01:03:04.508Z" }, { url = "https://files.pythonhosted.org/packages/d7/a8/ac706ad6ce834834762d5146d791f77710efc896c13ef47fd7d672099056/mlx_metal-0.27.1-py3-none-macosx_14_0_arm64.whl", hash = "sha256:fe4415ddd242974d91c7ca0699cd01507d17da8a5ba304122ef137cdb5e7fff4", size = 32926383, upload-time = "2025-07-25T22:57:54.194Z" },
{ url = "https://files.pythonhosted.org/packages/1e/11/eba1bdbaa82b6178887ff22eeae5ab90ef6de73e5fdd518753b808bbbc21/mlx-0.26.1-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:030b79376da1564d21af24ecfaa4e049bad823adfd7572376e1f81bd0f08af90", size = 31870531, upload-time = "2025-06-04T01:03:15.67Z" }, { url = "https://files.pythonhosted.org/packages/78/77/6963681fb54ecaa0ae5de4209c15504a803a0edd1a33fd074e6c558fd5e0/mlx_metal-0.27.1-py3-none-macosx_15_0_arm64.whl", hash = "sha256:d025dea30bda8baa32c928cfa333eac64a5adc8d07656f8fc55072d99403ebc9", size = 32897065, upload-time = "2025-07-25T22:58:26.362Z" },
{ url = "https://files.pythonhosted.org/packages/8d/3a/415cf516daa5cc6f95ac953d894346fc69c013f0c2348665223b130fccab/mlx-0.26.1-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:70a286a1e9a5e9fb69914e552e563a8de33963a24496c8b69f3c084a88e7ef90", size = 10125716, upload-time = "2025-06-06T23:08:07.886Z" },
{ url = "https://files.pythonhosted.org/packages/63/93/97c0a149cba1ad3fbfb01be4c6b293b4cb9d11e41d4b54735048e3d671bd/mlx-0.26.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1973b46817db50328fe8f74b2faa860e761eb08f4230461bb299057a411c070b", size = 32399548, upload-time = "2025-06-04T01:02:34.868Z" },
{ url = "https://files.pythonhosted.org/packages/1d/4f/3d3b52f0040462ab53d66d65eefd062f577feb0647ec3aa916faa7f0282b/mlx-0.26.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:478abbe1de49cb1409a38c263bafe45e70e3c5b6cbefd4fc3f5c5b7db90d2908", size = 31870277, upload-time = "2025-06-04T01:01:29.932Z" },
{ url = "https://files.pythonhosted.org/packages/80/75/ea19b3f1e9db337a8ef5ceded8f95ae0ed636c66ab40473c6d9eaad5ce01/mlx-0.26.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:8d76d75d652f252f7887acebc6d1ef5d3aec16e72673332895584f1617c35151", size = 31871009, upload-time = "2025-06-04T01:03:09.963Z" },
{ url = "https://files.pythonhosted.org/packages/72/a7/b4611911540e15ffd5c5b039ff4a83f43ef64a7ebc51df499b4456815470/mlx-0.26.1-cp311-cp311-manylinux_2_31_x86_64.whl", hash = "sha256:1cedf67d32d21e23e1639116cc24b62b6cc95a5f3bb0aa0ad6833e2910eb802c", size = 10125854, upload-time = "2025-06-06T23:07:39.23Z" },
{ url = "https://files.pythonhosted.org/packages/73/3e/a099ca333bd9cd313c7792a18ad4dfc47b051957ba24edd59f3bcda87d8c/mlx-0.26.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:ebcc19c01d90b16462c8ff4255b00837bf45ce149d207170d95770b6699cc5ce", size = 32396393, upload-time = "2025-06-04T01:02:40.689Z" },
{ url = "https://files.pythonhosted.org/packages/d1/e0/91707351c0becd33c477a1eba1a0fc124b3838cc65e3d2bd06dfa0a0533e/mlx-0.26.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5eea5e025655a2b07c1573602e705013bd6d3ebdf432c39eb8eb29628c6df5a7", size = 31871294, upload-time = "2025-06-04T01:01:14.326Z" },
{ url = "https://files.pythonhosted.org/packages/8d/e6/d5759fb20faceac183cfd45ede5fa0484f972eecfa2a851f8dd24aa4ace2/mlx-0.26.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:3b93e1e2375222a7b0f65b4be3c4010855db2ee8c40cc39f8bf2820ff1652b06", size = 31871854, upload-time = "2025-06-04T01:03:14.003Z" },
{ url = "https://files.pythonhosted.org/packages/e0/98/a95ca1dfb3721d3dbdd197ab17e3bb79b695bf426e37939d71d24cca19ce/mlx-0.26.1-cp312-cp312-manylinux_2_31_x86_64.whl", hash = "sha256:07f2fdb11b908156a65823ed062682cad0b03325a4f11fb9a36ce5acfe7475e5", size = 10120549, upload-time = "2025-06-06T23:07:43.068Z" },
{ url = "https://files.pythonhosted.org/packages/a2/a7/871c451fe81274d37022a62f825c1dcd22b30e1f8bd2241f91d9f508c9b9/mlx-0.26.1-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:ccd8662abad0f1340326412d6051c116fcb5c923c4d2a25ba1277ae65ab140dd", size = 32396333, upload-time = "2025-06-04T01:02:29.963Z" },
{ url = "https://files.pythonhosted.org/packages/82/77/720bea5a67934b50372dfd5043864458f103743edcc7c30049e788ea3762/mlx-0.26.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0c113dd7c7ac13af6e39f0132d33a8dc78928e858ba8d18f8c89f8bfa694a358", size = 31871172, upload-time = "2025-06-04T01:03:05.075Z" },
{ url = "https://files.pythonhosted.org/packages/15/4f/83f67bc4fe012dffffd2d96d2767b83fee9b2d7d185611d554ac659cfa4d/mlx-0.26.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:2ec37131dbb06c0be78ce56b1731ddab6e56183012e7b83bea79b5329ef7d695", size = 31871791, upload-time = "2025-06-04T01:03:15.384Z" },
{ url = "https://files.pythonhosted.org/packages/4f/fb/4123952002fd91f096ba07ce797b6bb6a32cc7a89c988565e261559f77dd/mlx-0.26.1-cp313-cp313-manylinux_2_31_x86_64.whl", hash = "sha256:db96a53466d8efc6cf2a2918b2d4e29cbf9f25174c838fb3c380c8717a40752f", size = 10120515, upload-time = "2025-06-06T23:07:38.428Z" },
] ]
[[package]] [[package]]