Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Run the powerful [FLUX](https://blackforestlabs.ai/#get-flux), [Qwen Image](http
- [🦙 Qwen Models](#-qwen-models)
* [🖼️ Qwen Image](#%EF%B8%8F-qwen-image)
* [✏️ Qwen Image Edit](#%EF%B8%8F-qwen-image-edit)
* [Qwen Image Layered](#qwen-image-layered)
- [🌀 FIBO](#-fibo)
- [⚡ Z-Image](#-z-image)
- [🔌 LoRA](#-lora)
Expand Down Expand Up @@ -374,6 +375,35 @@ See the [Qwen Image](#-qwen-image) section for more details on this feature.

</details>

#### Qwen Image Layered Command-Line Arguments

<details>
<summary>Click to expand Qwen Image Layered arguments</summary>

The `mflux-generate-qwen-layered` command decomposes an input image into separate RGBA layers:

- **`--image`** (required, `str`): Path to the input image to decompose into layers.

- **`--layers`** (optional, `int`, default: `4`): Number of output layers to generate. Each layer will contain distinct visual elements from the source image.

- **`--steps`** (optional, `int`, default: `50`): Number of inference steps. More steps generally produce better results.

- **`--resolution`** (optional, `int`, default: `640`): Target resolution bucket (`640` or `1024`). The image will be resized to this resolution while maintaining aspect ratio.

- **`--guidance`** (optional, `float`, default: `4.0`): Guidance scale for the decomposition.

- **`--cfg-normalize`** (optional, flag): Enable CFG normalization for more stable guidance.

- **`--prompt`** (optional, `str`): Text description of the image (auto-generated if not provided).

- **`--negative-prompt`** (optional, `str`, default: `"blurry, bad quality"`): Negative prompt for quality guidance.

- **`--output-dir`** (optional, `str`, default: `"."`): Directory where layer images will be saved.

See the [Qwen Image Layered](#-qwen-image-layered) section for more details on this feature.

</details>

#### Qwen Image Edit Command-Line Arguments

<details>
Expand Down Expand Up @@ -1117,6 +1147,37 @@ mflux-generate-qwen-edit \

⚠️ *Note: The Qwen Image Edit model requires downloading the `Qwen/Qwen-Image-Edit-2509` model weights (~58GB for the full model, or use quantization for smaller sizes).*

#### Qwen Image Layered

**Qwen Image Layered** is a specialized image decomposition model that separates an input image into semantically disentangled RGBA layers. This enables powerful layer-based editing workflows where each layer can be independently manipulated and recomposed—similar to working with Photoshop layers but generated automatically from any image.

**Example: Using Pre-Quantized Models**

Pre-quantized models are available on HuggingFace and download automatically:

```sh
# Using 6-bit quantized model
mflux-generate-qwen-layered \
--image "input.png" \
--layers 4 \
--steps 50 \
--model-path zimengxiong/Qwen-Image-Layered-6bit
```

**Example: Quantizing from Full Model**

If you prefer to quantize on-the-fly from the full model:

```sh
mflux-generate-qwen-layered \
--image "input.png" \
--layers 4 \
--steps 50 \
--resolution 640 \
-q 6
```

This downloads the full BF16 model from `Qwen/Qwen-Image-Layered` and quantizes at runtime.
---

### 🌀 FIBO
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ mflux-generate-redux = "mflux.models.flux.cli.flux_generate_redux:main"
mflux-generate-kontext = "mflux.models.flux.cli.flux_generate_kontext:main"
mflux-generate-qwen = "mflux.models.qwen.cli.qwen_image_generate:main"
mflux-generate-qwen-edit = "mflux.models.qwen.cli.qwen_image_edit_generate:main"
mflux-generate-qwen-layered = "mflux.models.qwen_layered.cli.qwen_image_layered_generate:main"
mflux-generate-fibo = "mflux.models.fibo.cli.fibo_generate:main"
mflux-generate-z-image-turbo = "mflux.models.z_image.cli.z_image_turbo_generate:main"
mflux-refine-fibo = "mflux.models.fibo_vlm.cli.fibo_refine:main"
Expand Down
3 changes: 2 additions & 1 deletion src/mflux/cli/defaults/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
GUIDANCE_SCALE_KONTEXT = 2.5
HEIGHT, WIDTH = 1024, 1024
IMAGE_STRENGTH = 0.4
MODEL_CHOICES = ["dev", "schnell", "krea-dev", "dev-krea", "qwen", "fibo", "z-image-turbo"]
MODEL_CHOICES = ["dev", "schnell", "krea-dev", "dev-krea", "qwen", "qwen-image-layered", "fibo", "z-image-turbo"]
MODEL_INFERENCE_STEPS = {
"dev": 25,
"schnell": 4,
"krea-dev": 25,
"qwen": 20,
"qwen-image-layered": 50,
"fibo": 20,
"z-image-turbo": 9,
}
Expand Down
5 changes: 4 additions & 1 deletion src/mflux/models/common/cli/save.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from mflux.models.fibo.variants.txt2img.fibo import FIBO
from mflux.models.flux.variants.txt2img.flux import Flux1
from mflux.models.qwen.variants.txt2img.qwen_image import QwenImage
from mflux.models.qwen_layered.variants.i2l.qwen_image_layered import QwenImageLayered
from mflux.models.z_image.variants.turbo.z_image_turbo import ZImageTurbo


Expand All @@ -15,7 +16,9 @@ def main():

# 1. Determine model class based on model name
model_name_lower = args.model.lower()
if "qwen" in model_name_lower:
if "qwen" in model_name_lower and "layered" in model_name_lower:
model_class = QwenImageLayered
elif "qwen" in model_name_lower:
model_class = QwenImage
elif "fibo" in model_name_lower:
model_class = FIBO
Expand Down
17 changes: 17 additions & 0 deletions src/mflux/models/common/config/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ def qwen_image() -> "ModelConfig":
def qwen_image_edit() -> "ModelConfig":
return AVAILABLE_MODELS["qwen-image-edit"]

@staticmethod
@lru_cache
def qwen_image_layered() -> "ModelConfig":
return AVAILABLE_MODELS["qwen-image-layered"]

@staticmethod
@lru_cache
def fibo() -> "ModelConfig":
Expand Down Expand Up @@ -308,4 +313,16 @@ def from_name(
requires_sigma_shift=True,
priority=14,
),
"qwen-image-layered": ModelConfig(
aliases=["qwen-image-layered", "qwen-layered"],
model_name="Qwen/Qwen-Image-Layered",
base_model=None,
controlnet_model=None,
custom_transformer_model=None,
num_train_steps=None,
max_sequence_length=None,
supports_guidance=None,
requires_sigma_shift=None,
priority=15,
),
}
1 change: 1 addition & 0 deletions src/mflux/models/qwen_layered/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Qwen-Image-Layered model support for mflux
1 change: 1 addition & 0 deletions src/mflux/models/qwen_layered/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# CLI module
161 changes: 161 additions & 0 deletions src/mflux/models/qwen_layered/cli/qwen_image_layered_generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env python
"""CLI for Qwen-Image-Layered: Image decomposition into RGBA layers."""

import argparse
import sys
from pathlib import Path


def main():
parser = argparse.ArgumentParser(
description="Decompose an image into RGBA layers using Qwen-Image-Layered.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Basic decomposition into 4 layers
mflux-generate-qwen-layered --image input.png --layers 4

# With 6-bit quantization (recommended for 48GB Macs)
mflux-generate-qwen-layered --image input.png --layers 4 -q 6

# Custom settings
mflux-generate-qwen-layered --image input.png --layers 8 --steps 50 --resolution 640 -q 6
""",
)

# Required arguments
parser.add_argument(
"--image",
type=Path,
required=True,
help="Path to input image to decompose",
)

# Optional arguments
parser.add_argument(
"--layers",
type=int,
default=4,
help="Number of output layers (default: 4)",
)
parser.add_argument(
"--steps",
type=int,
default=50,
help="Number of inference steps (default: 50)",
)
parser.add_argument(
"--resolution",
type=int,
default=640,
choices=[640, 1024],
help="Target resolution bucket (default: 640)",
)
parser.add_argument(
"--guidance",
type=float,
default=4.0,
help="Guidance scale (default: 4.0)",
)
parser.add_argument(
"--seed",
type=int,
default=42,
help="Random seed (default: 42)",
)
parser.add_argument(
"--prompt",
type=str,
default=None,
help="Optional text prompt describing the image",
)
parser.add_argument(
"--negative-prompt",
type=str,
default=" ",
help="Optional negative prompt",
)
parser.add_argument(
"-q",
"--quantize",
type=int,
choices=[4, 6, 8],
default=None,
help="Quantization bits (4, 6, or 8). Recommended: 6 for 48GB Macs",
)
parser.add_argument(
"--model-path",
type=str,
default=None,
help="Path to local model weights (otherwise downloads from HuggingFace)",
)
parser.add_argument(
"--output",
type=str,
default="layer_{i}.png",
help="Output filename pattern. Use {i} for layer index (default: layer_{i}.png)",
)
parser.add_argument(
"--output-dir",
type=Path,
default=Path("."),
help="Output directory (default: current directory)",
)

args = parser.parse_args()

# Validate input
if not args.image.exists():
print(f"Error: Input image not found: {args.image}")
sys.exit(1)

# Import here to avoid slow startup for --help
from mflux.models.qwen_layered.variants.i2l.qwen_image_layered import QwenImageLayered

print("=" * 60)
print("Qwen-Image-Layered: Image Decomposition")
print("=" * 60)
print(f" Input: {args.image}")
print(f" Layers: {args.layers}")
print(f" Steps: {args.steps}")
print(f" Resolution: {args.resolution}")
print(f" Guidance: {args.guidance}")
print(f" Seed: {args.seed}")
print(f" Quantization: {args.quantize or 'BF16 (full precision)'}")
print("=" * 60)

# Initialize model
print("\nLoading model...")
model = QwenImageLayered(
quantize=args.quantize,
model_path=args.model_path,
)

# Run decomposition
print("\nStarting decomposition...")
layers = model.decompose(
seed=args.seed,
image_path=args.image,
num_layers=args.layers,
num_inference_steps=args.steps,
guidance=args.guidance,
resolution=args.resolution,
prompt=args.prompt,
negative_prompt=args.negative_prompt,
)

# Save output layers
args.output_dir.mkdir(parents=True, exist_ok=True)
print(f"\nSaving {len(layers)} layers to {args.output_dir}/")

for i, layer in enumerate(layers):
filename = args.output.format(i=i)
output_path = args.output_dir / filename
layer.save(output_path)
print(f" Saved {output_path}")

print("\nDone!")


if __name__ == "__main__":
main()
Loading