Rename QwenImageEditPlus to QwenImageEdit (#277)
Co-authored-by: Filip Strand <filip@Host-022.local>
This commit is contained in:
parent
bb5392ef2d
commit
d4005efac0
@ -84,7 +84,7 @@ mflux-generate-fill = "mflux.generate_fill:main"
|
|||||||
mflux-generate-depth = "mflux.generate_depth:main"
|
mflux-generate-depth = "mflux.generate_depth:main"
|
||||||
mflux-generate-redux = "mflux.generate_redux:main"
|
mflux-generate-redux = "mflux.generate_redux:main"
|
||||||
mflux-generate-kontext = "mflux.generate_kontext:main"
|
mflux-generate-kontext = "mflux.generate_kontext:main"
|
||||||
mflux-generate-qwen-edit-plus = "mflux.generate_qwen_edit_plus:main"
|
mflux-generate-qwen-edit = "mflux.generate_qwen_edit:main"
|
||||||
mflux-concept = "mflux.concept:main"
|
mflux-concept = "mflux.concept:main"
|
||||||
mflux-concept-from-image = "mflux.concept_from_image:main"
|
mflux-concept-from-image = "mflux.concept_from_image:main"
|
||||||
mflux-save = "mflux.save:main"
|
mflux-save = "mflux.save:main"
|
||||||
|
|||||||
@ -91,8 +91,8 @@ class ModelConfig:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def qwen_image_edit_plus() -> "ModelConfig":
|
def qwen_image_edit() -> "ModelConfig":
|
||||||
return AVAILABLE_MODELS["qwen-image-edit-plus"]
|
return AVAILABLE_MODELS["qwen-image-edit"]
|
||||||
|
|
||||||
def x_embedder_input_dim(self) -> int:
|
def x_embedder_input_dim(self) -> int:
|
||||||
if "Fill" in self.model_name:
|
if "Fill" in self.model_name:
|
||||||
@ -307,8 +307,8 @@ AVAILABLE_MODELS = {
|
|||||||
requires_sigma_shift=None,
|
requires_sigma_shift=None,
|
||||||
priority=11,
|
priority=11,
|
||||||
),
|
),
|
||||||
"qwen-image-edit-plus": ModelConfig(
|
"qwen-image-edit": ModelConfig(
|
||||||
aliases=["qwen-image-edit-plus", "qwen-edit-plus", "qwen-edit-2509"],
|
aliases=["qwen-image-edit", "qwen-edit", "qwen-edit-plus", "qwen-edit-2509"],
|
||||||
model_name="Qwen/Qwen-Image-Edit-2509",
|
model_name="Qwen/Qwen-Image-Edit-2509",
|
||||||
base_model=None,
|
base_model=None,
|
||||||
controlnet_model=None,
|
controlnet_model=None,
|
||||||
@ -317,6 +317,6 @@ AVAILABLE_MODELS = {
|
|||||||
max_sequence_length=None,
|
max_sequence_length=None,
|
||||||
supports_guidance=None,
|
supports_guidance=None,
|
||||||
requires_sigma_shift=None,
|
requires_sigma_shift=None,
|
||||||
priority=13,
|
priority=12,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
from mflux.callbacks.callback_manager import CallbackManager
|
from mflux.callbacks.callback_manager import CallbackManager
|
||||||
from mflux.config.config import Config
|
from mflux.config.config import Config
|
||||||
from mflux.error.exceptions import PromptFileReadError, StopImageGenerationException
|
from mflux.error.exceptions import PromptFileReadError, StopImageGenerationException
|
||||||
from mflux.models.qwen.variants.edit.qwen_image_edit_plus import QwenImageEditPlus
|
from mflux.models.qwen.variants.edit.qwen_image_edit import QwenImageEdit
|
||||||
from mflux.ui import defaults as ui_defaults
|
from mflux.ui import defaults as ui_defaults
|
||||||
from mflux.ui.cli.parsers import CommandLineParser
|
from mflux.ui.cli.parsers import CommandLineParser
|
||||||
from mflux.ui.prompt_utils import get_effective_negative_prompt, get_effective_prompt
|
from mflux.ui.prompt_utils import get_effective_negative_prompt, get_effective_prompt
|
||||||
@ -11,7 +11,7 @@ from mflux.ui.prompt_utils import get_effective_negative_prompt, get_effective_p
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
# 0. Parse command line arguments
|
# 0. Parse command line arguments
|
||||||
parser = CommandLineParser(description="Generate an image using Qwen Image Edit Plus with image conditioning.")
|
parser = CommandLineParser(description="Generate an image using Qwen Image Edit with image conditioning.")
|
||||||
parser.add_general_arguments()
|
parser.add_general_arguments()
|
||||||
parser.add_model_arguments(require_model_arg=False)
|
parser.add_model_arguments(require_model_arg=False)
|
||||||
parser.add_lora_arguments()
|
parser.add_lora_arguments()
|
||||||
@ -22,7 +22,7 @@ def main():
|
|||||||
type=Path,
|
type=Path,
|
||||||
nargs="+",
|
nargs="+",
|
||||||
default=None,
|
default=None,
|
||||||
help="Local paths to multiple init images (for plus model). If not provided, uses --image-path as single image.",
|
help="Local paths to multiple init images. If not provided, uses --image-path as single image.",
|
||||||
)
|
)
|
||||||
parser.add_output_arguments()
|
parser.add_output_arguments()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -32,7 +32,7 @@ def main():
|
|||||||
args.guidance = ui_defaults.GUIDANCE_SCALE_KONTEXT
|
args.guidance = ui_defaults.GUIDANCE_SCALE_KONTEXT
|
||||||
|
|
||||||
# 1. Load the model
|
# 1. Load the model
|
||||||
qwen = QwenImageEditPlus(
|
qwen = QwenImageEdit(
|
||||||
quantize=args.quantize,
|
quantize=args.quantize,
|
||||||
local_path=args.path,
|
local_path=args.path,
|
||||||
lora_paths=args.lora_paths,
|
lora_paths=args.lora_paths,
|
||||||
@ -53,42 +53,36 @@ class QwenImageEditInitializer:
|
|||||||
root_path = Path(local_path) if local_path else QwenImageEditInitializer._download_vl_processor(repo_id)
|
root_path = Path(local_path) if local_path else QwenImageEditInitializer._download_vl_processor(repo_id)
|
||||||
|
|
||||||
# 2. Load only the tokenizer (we implement image processing ourselves)
|
# 2. Load only the tokenizer (we implement image processing ourselves)
|
||||||
tokenizer_path = root_path / "tokenizer"
|
tokenizer = QwenImageEditInitializer._load_tokenizer(root_path, repo_id)
|
||||||
if not tokenizer_path.exists():
|
|
||||||
tokenizer_path = root_path
|
|
||||||
|
|
||||||
try:
|
|
||||||
tokenizer = Qwen2TokenizerFast.from_pretrained(
|
|
||||||
pretrained_model_name_or_path=tokenizer_path,
|
|
||||||
local_files_only=True,
|
|
||||||
)
|
|
||||||
except OSError:
|
|
||||||
tokenizer = Qwen2TokenizerFast.from_pretrained(repo_id)
|
|
||||||
|
|
||||||
# 3. Create our MLX processor with the tokenizer
|
# 3. Create our MLX processor with the tokenizer
|
||||||
processor = QwenVisionLanguageProcessor(tokenizer=tokenizer)
|
processor = QwenVisionLanguageProcessor(tokenizer=tokenizer)
|
||||||
|
|
||||||
# 4. Initialize vision-language tokenizer wrapper
|
# 4. Initialize vision-language tokenizer wrapper
|
||||||
if model_config is not None:
|
|
||||||
model_name_lower = model_config.model_name.lower()
|
|
||||||
use_picture_prefix = (
|
|
||||||
"plus" in model_name_lower
|
|
||||||
or "2509" in model_config.model_name # Check original case for "2509"
|
|
||||||
or (hasattr(model_config, "use_picture_prefix") and model_config.use_picture_prefix)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Fallback: check repo_id
|
|
||||||
use_picture_prefix = "plus" in repo_id.lower() or "2509" in repo_id
|
|
||||||
|
|
||||||
qwen_model.qwen_vl_tokenizer = QwenVisionLanguageTokenizer(
|
qwen_model.qwen_vl_tokenizer = QwenVisionLanguageTokenizer(
|
||||||
processor=processor,
|
processor=processor,
|
||||||
max_length=1024,
|
max_length=1024,
|
||||||
use_picture_prefix=use_picture_prefix,
|
use_picture_prefix=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 5. Initialize vision-language encoder (integrated approach like Diffusers)
|
# 5. Initialize vision-language encoder (integrated approach like Diffusers)
|
||||||
qwen_model.qwen_vl_encoder = QwenVisionLanguageEncoder(encoder=qwen_model.text_encoder.encoder)
|
qwen_model.qwen_vl_encoder = QwenVisionLanguageEncoder(encoder=qwen_model.text_encoder.encoder)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_tokenizer(root_path: Path, repo_id: str) -> Qwen2TokenizerFast:
|
||||||
|
"""Load the tokenizer from local path or download from HuggingFace."""
|
||||||
|
tokenizer_path = root_path / "tokenizer"
|
||||||
|
if not tokenizer_path.exists():
|
||||||
|
tokenizer_path = root_path
|
||||||
|
|
||||||
|
try:
|
||||||
|
return Qwen2TokenizerFast.from_pretrained(
|
||||||
|
pretrained_model_name_or_path=tokenizer_path,
|
||||||
|
local_files_only=True,
|
||||||
|
)
|
||||||
|
except OSError:
|
||||||
|
return Qwen2TokenizerFast.from_pretrained(repo_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _download_vl_processor(repo_id: str) -> Path:
|
def _download_vl_processor(repo_id: str) -> Path:
|
||||||
return Path(
|
return Path(
|
||||||
|
|||||||
@ -59,7 +59,7 @@ class QwenVisionLanguageTokenizer:
|
|||||||
|
|
||||||
# Format prompt based on tokenizer mode
|
# Format prompt based on tokenizer mode
|
||||||
if self.use_picture_prefix:
|
if self.use_picture_prefix:
|
||||||
# Edit Plus format: Add "Picture N:" prefix for each image
|
# Edit format: Add "Picture N:" prefix for each image
|
||||||
# For multiple images: "Picture 1: ... Picture 2: ... Picture N: ..."
|
# For multiple images: "Picture 1: ... Picture 2: ... Picture N: ..."
|
||||||
img_prompt_template = "Picture {}: <|vision_start|><|image_pad|><|vision_end|>"
|
img_prompt_template = "Picture {}: <|vision_start|><|image_pad|><|vision_end|>"
|
||||||
base_img_prompt = ""
|
base_img_prompt = ""
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Qwen Image Edit variant
|
# Qwen Image Edit variant
|
||||||
from mflux.models.qwen.variants.edit.qwen_image_edit_plus import QwenImageEditPlus
|
from mflux.models.qwen.variants.edit.qwen_image_edit import QwenImageEdit
|
||||||
|
|
||||||
__all__ = ["QwenImageEditPlus"]
|
__all__ = ["QwenImageEdit"]
|
||||||
|
|||||||
@ -21,7 +21,7 @@ from mflux.post_processing.generated_image import GeneratedImage
|
|||||||
from mflux.post_processing.image_util import ImageUtil
|
from mflux.post_processing.image_util import ImageUtil
|
||||||
|
|
||||||
|
|
||||||
class QwenImageEditPlus(nn.Module):
|
class QwenImageEdit(nn.Module):
|
||||||
vae: QwenVAE
|
vae: QwenVAE
|
||||||
transformer: QwenTransformer
|
transformer: QwenTransformer
|
||||||
text_encoder: QwenTextEncoder
|
text_encoder: QwenTextEncoder
|
||||||
@ -38,7 +38,7 @@ class QwenImageEditPlus(nn.Module):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
QwenImageEditInitializer.init(
|
QwenImageEditInitializer.init(
|
||||||
qwen_model=self,
|
qwen_model=self,
|
||||||
model_config=ModelConfig.qwen_image_edit_plus(),
|
model_config=ModelConfig.qwen_image_edit(),
|
||||||
quantize=quantize,
|
quantize=quantize,
|
||||||
local_path=local_path,
|
local_path=local_path,
|
||||||
lora_paths=lora_paths,
|
lora_paths=lora_paths,
|
||||||
@ -70,7 +70,7 @@ class QwenImageEditPlus(nn.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 2. Encode the prompt
|
# 2. Encode the prompt
|
||||||
prompt_embeds, prompt_mask, negative_prompt_embeds, negative_prompt_mask = self._encode_prompts_with_image_plus(
|
prompt_embeds, prompt_mask, negative_prompt_embeds, negative_prompt_mask = self._encode_prompts_with_images(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
negative_prompt=negative_prompt,
|
negative_prompt=negative_prompt,
|
||||||
image_paths=image_paths,
|
image_paths=image_paths,
|
||||||
@ -186,7 +186,7 @@ class QwenImageEditPlus(nn.Module):
|
|||||||
negative_prompt=negative_prompt,
|
negative_prompt=negative_prompt,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _encode_prompts_with_image_plus(
|
def _encode_prompts_with_images(
|
||||||
self,
|
self,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
negative_prompt: str,
|
negative_prompt: str,
|
||||||
@ -31,9 +31,9 @@ class ImageGeneratorEditTestHelper:
|
|||||||
reference_image_path = ImageGeneratorEditTestHelper.resolve_path(reference_image_path)
|
reference_image_path = ImageGeneratorEditTestHelper.resolve_path(reference_image_path)
|
||||||
output_image_path = ImageGeneratorEditTestHelper.resolve_path(output_image_path)
|
output_image_path = ImageGeneratorEditTestHelper.resolve_path(output_image_path)
|
||||||
|
|
||||||
# For Edit Plus, if image_paths is provided but image_path is not, use first element of image_paths
|
# For Qwen Edit, if image_paths is provided but image_path is not, use first element of image_paths
|
||||||
is_edit_plus = "Plus" in model_class.__name__
|
is_qwen_edit = "QwenImageEdit" in model_class.__name__
|
||||||
if is_edit_plus and image_paths and not image_path:
|
if is_qwen_edit and image_paths and not image_path:
|
||||||
image_path = image_paths[0]
|
image_path = image_paths[0]
|
||||||
|
|
||||||
if image_path:
|
if image_path:
|
||||||
@ -64,9 +64,9 @@ class ImageGeneratorEditTestHelper:
|
|||||||
"config": Config(**config_kwargs),
|
"config": Config(**config_kwargs),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Edit Plus uses image_paths instead of image_path in config
|
# Qwen Edit uses image_paths instead of image_path in config
|
||||||
# Check if it's Edit Plus by checking the class name
|
# Check if it's Qwen Edit by checking the class name
|
||||||
if "Plus" in model_class.__name__:
|
if "QwenImageEdit" in model_class.__name__:
|
||||||
if image_paths:
|
if image_paths:
|
||||||
generate_kwargs["image_paths"] = image_paths
|
generate_kwargs["image_paths"] = image_paths
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from mflux.config.model_config import ModelConfig
|
from mflux.config.model_config import ModelConfig
|
||||||
from mflux.models.flux.variants.kontext.flux_kontext import Flux1Kontext
|
from mflux.models.flux.variants.kontext.flux_kontext import Flux1Kontext
|
||||||
from mflux.models.qwen.variants.edit import QwenImageEditPlus
|
from mflux.models.qwen.variants.edit import QwenImageEdit
|
||||||
from tests.image_generation.helpers.image_generation_edit_test_helper import ImageGeneratorEditTestHelper
|
from tests.image_generation.helpers.image_generation_edit_test_helper import ImageGeneratorEditTestHelper
|
||||||
|
|
||||||
|
|
||||||
@ -20,12 +20,12 @@ class TestImageGeneratorEdit:
|
|||||||
image_path="reference_upscaled.png",
|
image_path="reference_upscaled.png",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_generation_qwen_edit_plus(self):
|
def test_image_generation_qwen_edit(self):
|
||||||
ImageGeneratorEditTestHelper.assert_matches_reference_image(
|
ImageGeneratorEditTestHelper.assert_matches_reference_image(
|
||||||
reference_image_path="reference_qwen_edit_plus.png",
|
reference_image_path="reference_qwen_edit.png",
|
||||||
output_image_path="output_qwen_edit_plus.png",
|
output_image_path="output_qwen_edit.png",
|
||||||
model_class=QwenImageEditPlus,
|
model_class=QwenImageEdit,
|
||||||
model_config=ModelConfig.qwen_image_edit_plus(),
|
model_config=ModelConfig.qwen_image_edit(),
|
||||||
steps=20,
|
steps=20,
|
||||||
seed=4869845,
|
seed=4869845,
|
||||||
height=384,
|
height=384,
|
||||||
@ -36,12 +36,12 @@ class TestImageGeneratorEdit:
|
|||||||
image_path="reference_upscaled.png",
|
image_path="reference_upscaled.png",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_generation_qwen_edit_plus_multiple_images(self):
|
def test_image_generation_qwen_edit_multiple_images(self):
|
||||||
ImageGeneratorEditTestHelper.assert_matches_reference_image(
|
ImageGeneratorEditTestHelper.assert_matches_reference_image(
|
||||||
reference_image_path="reference_qwen_edit_plus_multiple_images.png",
|
reference_image_path="reference_qwen_edit_multiple_images.png",
|
||||||
output_image_path="output_qwen_edit_plus_multiple_images.png",
|
output_image_path="output_qwen_edit_multiple_images.png",
|
||||||
model_class=QwenImageEditPlus,
|
model_class=QwenImageEdit,
|
||||||
model_config=ModelConfig.qwen_image_edit_plus(),
|
model_config=ModelConfig.qwen_image_edit(),
|
||||||
steps=20,
|
steps=20,
|
||||||
seed=4869845,
|
seed=4869845,
|
||||||
height=384,
|
height=384,
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB |
Loading…
Reference in New Issue
Block a user