[Model] Add LongCat-Image-Edit support (#392)
Signed-off-by: huangxulong <huangxulong@oschina.cn> Signed-off-by: elijah <f1renze.142857@gmail.com> Co-authored-by: huangxulong <huangxulong@oschina.cn>
This commit is contained in:
1 parent
f363108425
commit
ef827022b8
7 files changed
+843
-261
No files matched your search
@@ -27,6 +27,7 @@ th {
|
||||
| `WanPipeline` | Wan2.2 | `Wan-AI/Wan2.2-T2V-A14B-Diffusers` |
|
||||
| `OvisImagePipeline` | Ovis-Image | `OvisAI/Ovis-Image` |
|
||||
|`LongcatImagePipeline` | LongCat-Image | `meituan-longcat/LongCat-Image` |
|
||||
|`LongCatImageEditPipeline` | LongCat-Image | `meituan-longcat/LongCat-Image-Edit` |
|
||||
|
||||
|
||||
## List of Supported Models for NPU
|
||||
|
||||
@@ -22,6 +22,22 @@ Usage (multiple images):
|
||||
--cfg_scale 4.0 \
|
||||
--guidance_scale 1.0
|
||||
|
||||
Usage (with cache-dit acceleration):
|
||||
python image_edit.py \
|
||||
--image input.png \
|
||||
--prompt "Edit description" \
|
||||
--cache_backend cache_dit \
|
||||
--cache_dit_max_continuous_cached_steps 3 \
|
||||
--cache_dit_residual_diff_threshold 0.24 \
|
||||
--cache_dit_enable_taylorseer
|
||||
|
||||
Usage (with tea_cache acceleration):
|
||||
python image_edit.py \
|
||||
--image input.png \
|
||||
--prompt "Edit description" \
|
||||
--cache_backend tea_cache \
|
||||
--tea_cache_rel_l1_thresh 0.25
|
||||
|
||||
Usage (layered):
|
||||
python image_edit.py \
|
||||
--model "Qwen/Qwen-Image-Layered" \
|
||||
@@ -158,6 +174,72 @@ def parse_args() -> argparse.Namespace:
|
||||
help="For Qwen-Image-Layered, set to RGBA.",
|
||||
)
|
||||
|
||||
# Cache-DiT specific parameters
|
||||
parser.add_argument(
|
||||
"--cache_dit_fn_compute_blocks",
|
||||
type=int,
|
||||
default=1,
|
||||
help="[cache-dit] Number of forward compute blocks. Optimized for single-transformer models.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_bn_compute_blocks",
|
||||
type=int,
|
||||
default=0,
|
||||
help="[cache-dit] Number of backward compute blocks.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_max_warmup_steps",
|
||||
type=int,
|
||||
default=4,
|
||||
help="[cache-dit] Maximum warmup steps (works for few-step models).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_residual_diff_threshold",
|
||||
type=float,
|
||||
default=0.24,
|
||||
help="[cache-dit] Residual diff threshold. Higher values enable more aggressive caching.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_max_continuous_cached_steps",
|
||||
type=int,
|
||||
default=3,
|
||||
help="[cache-dit] Maximum continuous cached steps to prevent precision degradation.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_enable_taylorseer",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="[cache-dit] Enable TaylorSeer acceleration (not suitable for few-step models).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_taylorseer_order",
|
||||
type=int,
|
||||
default=1,
|
||||
help="[cache-dit] TaylorSeer polynomial order.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_scm_steps_mask_policy",
|
||||
type=str,
|
||||
default=None,
|
||||
choices=[None, "slow", "medium", "fast", "ultra"],
|
||||
help="[cache-dit] SCM mask policy: None (disabled), slow, medium, fast, ultra.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_dit_scm_steps_policy",
|
||||
type=str,
|
||||
default="dynamic",
|
||||
choices=["dynamic", "static"],
|
||||
help="[cache-dit] SCM steps policy: dynamic or static.",
|
||||
)
|
||||
|
||||
# TeaCache specific parameters
|
||||
parser.add_argument(
|
||||
"--tea_cache_rel_l1_thresh",
|
||||
type=float,
|
||||
default=0.2,
|
||||
help="[tea_cache] Threshold for accumulated relative L1 distance.",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -191,29 +273,22 @@ def main():
|
||||
cache_config = None
|
||||
if args.cache_backend == "cache_dit":
|
||||
# cache-dit configuration: Hybrid DBCache + SCM + TaylorSeer
|
||||
# All parameters marked with [cache-dit only] in DiffusionCacheConfig
|
||||
cache_config = {
|
||||
# DBCache parameters [cache-dit only]
|
||||
"Fn_compute_blocks": 1, # Optimized for single-transformer models
|
||||
"Bn_compute_blocks": 0, # Number of backward compute blocks
|
||||
"max_warmup_steps": 4, # Maximum warmup steps (works for few-step models)
|
||||
"residual_diff_threshold": 0.24, # Higher threshold for more aggressive caching
|
||||
"max_continuous_cached_steps": 3, # Limit to prevent precision degradation
|
||||
# TaylorSeer parameters [cache-dit only]
|
||||
"enable_taylorseer": False, # Disabled by default (not suitable for few-step models)
|
||||
"taylorseer_order": 1, # TaylorSeer polynomial order
|
||||
# SCM (Step Computation Masking) parameters [cache-dit only]
|
||||
"scm_steps_mask_policy": None, # SCM mask policy: None (disabled), "slow", "medium", "fast", "ultra"
|
||||
"scm_steps_policy": "dynamic", # SCM steps policy: "dynamic" or "static"
|
||||
"Fn_compute_blocks": args.cache_dit_fn_compute_blocks,
|
||||
"Bn_compute_blocks": args.cache_dit_bn_compute_blocks,
|
||||
"max_warmup_steps": args.cache_dit_max_warmup_steps,
|
||||
"residual_diff_threshold": args.cache_dit_residual_diff_threshold,
|
||||
"max_continuous_cached_steps": args.cache_dit_max_continuous_cached_steps,
|
||||
"enable_taylorseer": args.cache_dit_enable_taylorseer,
|
||||
"taylorseer_order": args.cache_dit_taylorseer_order,
|
||||
"scm_steps_mask_policy": args.cache_dit_scm_steps_mask_policy,
|
||||
"scm_steps_policy": args.cache_dit_scm_steps_policy,
|
||||
}
|
||||
elif args.cache_backend == "tea_cache":
|
||||
# TeaCache configuration
|
||||
# All parameters marked with [tea_cache only] in DiffusionCacheConfig
|
||||
cache_config = {
|
||||
# TeaCache parameters [tea_cache only]
|
||||
"rel_l1_thresh": 0.2, # Threshold for accumulated relative L1 distance
|
||||
"rel_l1_thresh": args.tea_cache_rel_l1_thresh,
|
||||
# Note: coefficients will use model-specific defaults based on model_type
|
||||
# (e.g., QwenImagePipeline or FluxPipeline)
|
||||
}
|
||||
|
||||
# Initialize Omni with appropriate pipeline
|
||||
|
||||
+73
@@ -186,6 +186,77 @@ def enable_cache_for_wan22(pipeline: Any, cache_config: Any) -> Callable[[int],
|
||||
return refresh_cache_context
|
||||
|
||||
|
||||
def enable_cache_for_longcat_image(pipeline: Any, cache_config: Any) -> Callable[[int], None]:
|
||||
"""Enable cache-dit for LongCatImage pipeline.
|
||||
|
||||
Args:
|
||||
pipeline: The LongCatImage pipeline instance.
|
||||
cache_config: DiffusionCacheConfig instance with cache configuration.
|
||||
"""
|
||||
# Build DBCacheConfig for transformer
|
||||
db_cache_config = _build_db_cache_config(cache_config)
|
||||
|
||||
calibrator = None
|
||||
if cache_config.enable_taylorseer:
|
||||
taylorseer_order = cache_config.taylorseer_order
|
||||
calibrator = TaylorSeerCalibratorConfig(taylorseer_order=taylorseer_order)
|
||||
logger.info(f"TaylorSeer enabled with order={taylorseer_order}")
|
||||
|
||||
# Build ParamsModifier for transformer
|
||||
modifier = ParamsModifier(
|
||||
cache_config=db_cache_config,
|
||||
calibrator_config=calibrator,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Enabling cache-dit on LongCatImage transformer with BlockAdapter: "
|
||||
f"Fn={db_cache_config.Fn_compute_blocks}, "
|
||||
f"Bn={db_cache_config.Bn_compute_blocks}, "
|
||||
f"W={db_cache_config.max_warmup_steps}, "
|
||||
)
|
||||
|
||||
# Enable cache-dit using BlockAdapter for transformer
|
||||
cache_dit.enable_cache(
|
||||
(
|
||||
BlockAdapter(
|
||||
transformer=pipeline.transformer,
|
||||
blocks=[
|
||||
pipeline.transformer.transformer_blocks,
|
||||
pipeline.transformer.single_transformer_blocks,
|
||||
],
|
||||
forward_pattern=[ForwardPattern.Pattern_1, ForwardPattern.Pattern_1],
|
||||
params_modifiers=[modifier],
|
||||
)
|
||||
),
|
||||
cache_config=db_cache_config,
|
||||
)
|
||||
|
||||
def refresh_cache_context(pipeline: Any, num_inference_steps: int, verbose: bool = True) -> None:
|
||||
"""Refresh cache context for the transformer with new num_inference_steps.
|
||||
|
||||
Args:
|
||||
pipeline: The LongCatImage pipeline instance.
|
||||
num_inference_steps: New number of inference steps.
|
||||
"""
|
||||
if cache_config.scm_steps_mask_policy is None:
|
||||
cache_dit.refresh_context(pipeline.transformer, num_inference_steps=num_inference_steps, verbose=verbose)
|
||||
else:
|
||||
cache_dit.refresh_context(
|
||||
pipeline.transformer,
|
||||
cache_config=DBCacheConfig().reset(
|
||||
num_inference_steps=num_inference_steps,
|
||||
steps_computation_mask=cache_dit.steps_mask(
|
||||
mask_policy=cache_config.scm_steps_mask_policy,
|
||||
total_steps=num_inference_steps,
|
||||
),
|
||||
steps_computation_policy=cache_config.scm_steps_policy,
|
||||
),
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
return refresh_cache_context
|
||||
|
||||
|
||||
def enable_cache_for_flux(pipeline: Any, cache_config: Any) -> Callable[[int], None]:
|
||||
"""Enable cache-dit for Flux dual-transformer architecture.
|
||||
|
||||
@@ -267,6 +338,8 @@ CUSTOM_DIT_ENABLERS.update(
|
||||
{
|
||||
"WanPipeline": enable_cache_for_wan22,
|
||||
"FluxPipeline": enable_cache_for_flux,
|
||||
"LongcatImagePipeline": enable_cache_for_longcat_image,
|
||||
"LongcatImageEditPipeline": enable_cache_for_longcat_image,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import numpy as np
|
||||
import torch
|
||||
from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.models import AutoencoderKL
|
||||
from diffusers.pipelines.longcat_image.system_messages import SYSTEM_PROMPT_EN, SYSTEM_PROMPT_ZH
|
||||
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler, SchedulerMixin
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
from torch import nn
|
||||
@@ -25,7 +26,6 @@ from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||
from vllm_omni.diffusion.distributed.utils import get_local_device
|
||||
from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineLoader
|
||||
from vllm_omni.diffusion.models.longcat_image.longcat_image_transformer import LongCatImageTransformer2DModel
|
||||
from vllm_omni.diffusion.models.longcat_image.system_prompt import SYSTEM_PROMPT_EN, SYSTEM_PROMPT_ZH
|
||||
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||
from vllm_omni.model_executor.model_loader.weight_utils import (
|
||||
download_weights_from_hf_specific,
|
||||
|
||||
@@ -0,0 +1,669 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import PIL.Image
|
||||
import torch
|
||||
from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.models import AutoencoderKL
|
||||
from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion import retrieve_timesteps
|
||||
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
from torch import nn
|
||||
from transformers import (
|
||||
AutoTokenizer,
|
||||
Qwen2_5_VLForConditionalGeneration,
|
||||
Qwen2VLProcessor,
|
||||
)
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader
|
||||
|
||||
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||
from vllm_omni.diffusion.distributed.utils import get_local_device
|
||||
from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineLoader
|
||||
from vllm_omni.diffusion.models.longcat_image.longcat_image_transformer import (
|
||||
LongCatImageTransformer2DModel,
|
||||
)
|
||||
from vllm_omni.diffusion.models.longcat_image.pipeline_longcat_image import calculate_shift
|
||||
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||
from vllm_omni.model_executor.model_loader.weight_utils import (
|
||||
download_weights_from_hf_specific,
|
||||
)
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
def get_longcat_image_edit_pre_process_func(
|
||||
od_config: OmniDiffusionConfig,
|
||||
):
|
||||
"""Pre-processing function for LongCatImageEditPipeline."""
|
||||
model_name = od_config.model
|
||||
if os.path.exists(model_name):
|
||||
model_path = model_name
|
||||
else:
|
||||
model_path = download_weights_from_hf_specific(model_name, None, ["*"])
|
||||
vae_config_path = os.path.join(model_path, "vae/config.json")
|
||||
with open(vae_config_path) as f:
|
||||
vae_config = json.load(f)
|
||||
vae_scale_factor = 2 ** (len(vae_config["block_out_channels"]) - 1) if "block_out_channels" in vae_config else 8
|
||||
|
||||
image_processor = VaeImageProcessor(vae_scale_factor=vae_scale_factor * 2)
|
||||
latent_channels = vae_config.get("latent_channels", 16)
|
||||
|
||||
def pre_process_func(
|
||||
requests: list[OmniDiffusionRequest],
|
||||
):
|
||||
"""Pre-process requests for QwenImageEditPipeline."""
|
||||
for req in requests:
|
||||
image = req.pil_image
|
||||
|
||||
image_size = image[0].size if isinstance(image, list) else image.size
|
||||
calculated_width, calculated_height = calculate_dimensions(1024 * 1024, image_size[0] * 1.0 / image_size[1])
|
||||
height = req.height or calculated_height
|
||||
width = req.width or calculated_width
|
||||
|
||||
# Store calculated dimensions in request
|
||||
req.calculated_height = calculated_height
|
||||
req.calculated_width = calculated_width
|
||||
req.height = height
|
||||
req.width = width
|
||||
|
||||
# Preprocess image
|
||||
if image is not None and not (isinstance(image, torch.Tensor) and image.size(1) == latent_channels):
|
||||
image = image_processor.resize(image, calculated_height, calculated_width)
|
||||
prompt_image = image_processor.resize(image, calculated_height // 2, calculated_width // 2)
|
||||
image = image_processor.preprocess(image, calculated_height, calculated_width)
|
||||
|
||||
# Store preprocessed image and prompt image in request
|
||||
req.preprocessed_image = image
|
||||
req.prompt_image = prompt_image
|
||||
return requests
|
||||
|
||||
return pre_process_func
|
||||
|
||||
|
||||
def get_longcat_image_post_process_func(
|
||||
od_config: OmniDiffusionConfig,
|
||||
):
|
||||
model_name = od_config.model
|
||||
if os.path.exists(model_name):
|
||||
model_path = model_name
|
||||
else:
|
||||
model_path = download_weights_from_hf_specific(model_name, None, ["*"])
|
||||
vae_config_path = os.path.join(model_path, "vae/config.json")
|
||||
with open(vae_config_path) as f:
|
||||
vae_config = json.load(f)
|
||||
vae_scale_factor = 2 ** (len(vae_config["block_out_channels"]) - 1) if "block_out_channels" in vae_config else 8
|
||||
|
||||
image_processor = VaeImageProcessor(vae_scale_factor=vae_scale_factor * 2)
|
||||
|
||||
def post_process_func(
|
||||
images: torch.Tensor,
|
||||
):
|
||||
return image_processor.postprocess(images)
|
||||
|
||||
return post_process_func
|
||||
|
||||
|
||||
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
|
||||
def retrieve_latents(
|
||||
encoder_output: torch.Tensor, generator: torch.Generator | None = None, sample_mode: str = "sample"
|
||||
):
|
||||
if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
|
||||
return encoder_output.latent_dist.sample(generator)
|
||||
elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
|
||||
return encoder_output.latent_dist.mode()
|
||||
elif hasattr(encoder_output, "latents"):
|
||||
return encoder_output.latents
|
||||
else:
|
||||
raise AttributeError("Could not access latents of provided encoder_output")
|
||||
|
||||
|
||||
def calculate_dimensions(target_area, ratio):
|
||||
width = math.sqrt(target_area * ratio)
|
||||
height = width / ratio
|
||||
|
||||
width = width if width % 16 == 0 else (width // 16 + 1) * 16
|
||||
height = height if height % 16 == 0 else (height // 16 + 1) * 16
|
||||
|
||||
width = int(width)
|
||||
height = int(height)
|
||||
|
||||
return width, height
|
||||
|
||||
|
||||
def prepare_pos_ids(modality_id=0, type="text", start=(0, 0), num_token=None, height=None, width=None):
|
||||
if type == "text":
|
||||
assert num_token
|
||||
if height or width:
|
||||
logger.warning('The parameters of height and width will be ignored in "text" type.')
|
||||
pos_ids = torch.zeros(num_token, 3)
|
||||
pos_ids[..., 0] = modality_id
|
||||
pos_ids[..., 1] = torch.arange(num_token) + start[0]
|
||||
pos_ids[..., 2] = torch.arange(num_token) + start[1]
|
||||
elif type == "image":
|
||||
assert height and width
|
||||
if num_token:
|
||||
logger.warning('The parameter of num_token will be ignored in "image" type.')
|
||||
pos_ids = torch.zeros(height, width, 3)
|
||||
pos_ids[..., 0] = modality_id
|
||||
pos_ids[..., 1] = pos_ids[..., 1] + torch.arange(height)[:, None] + start[0]
|
||||
pos_ids[..., 2] = pos_ids[..., 2] + torch.arange(width)[None, :] + start[1]
|
||||
pos_ids = pos_ids.reshape(height * width, 3)
|
||||
else:
|
||||
raise KeyError(f'Unknown type {type}, only support "text" or "image".')
|
||||
return pos_ids
|
||||
|
||||
|
||||
def split_quotation(prompt, quote_pairs=None):
|
||||
"""
|
||||
Implement a regex-based string splitting algorithm that identifies delimiters defined by single or double quote
|
||||
pairs. Examples::
|
||||
>>> prompt_en = "Please write 'Hello' on the blackboard for me." >>> print(split_quotation(prompt_en)) >>> #
|
||||
output: [('Please write ', False), ("'Hello'", True), (' on the blackboard for me.', False)]
|
||||
"""
|
||||
word_internal_quote_pattern = re.compile(r"[a-zA-Z]+'[a-zA-Z]+")
|
||||
matches_word_internal_quote_pattern = word_internal_quote_pattern.findall(prompt)
|
||||
mapping_word_internal_quote = []
|
||||
|
||||
for i, word_src in enumerate(set(matches_word_internal_quote_pattern)):
|
||||
word_tgt = "longcat_$##$_longcat" * (i + 1)
|
||||
prompt = prompt.replace(word_src, word_tgt)
|
||||
mapping_word_internal_quote.append([word_src, word_tgt])
|
||||
|
||||
if quote_pairs is None:
|
||||
quote_pairs = [("'", "'"), ('"', '"'), ("‘", "’"), ("“", "”")]
|
||||
pattern = "|".join([re.escape(q1) + r"[^" + re.escape(q1 + q2) + r"]*?" + re.escape(q2) for q1, q2 in quote_pairs])
|
||||
parts = re.split(f"({pattern})", prompt)
|
||||
|
||||
result = []
|
||||
for part in parts:
|
||||
for word_src, word_tgt in mapping_word_internal_quote:
|
||||
part = part.replace(word_tgt, word_src)
|
||||
if re.match(pattern, part):
|
||||
if len(part):
|
||||
result.append((part, True))
|
||||
else:
|
||||
if len(part):
|
||||
result.append((part, False))
|
||||
return result
|
||||
|
||||
|
||||
class LongcatImageEditPipeline(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
od_config: OmniDiffusionConfig,
|
||||
prefix: str = "",
|
||||
):
|
||||
super().__init__()
|
||||
self.od_config = od_config
|
||||
self.weights_sources = [
|
||||
DiffusersPipelineLoader.ComponentSource(
|
||||
model_or_path=od_config.model,
|
||||
subfolder="transformer",
|
||||
revision=None,
|
||||
prefix="transformer.",
|
||||
fall_back_to_pt=True,
|
||||
)
|
||||
]
|
||||
|
||||
self.device = get_local_device()
|
||||
model = od_config.model
|
||||
local_files_only = os.path.exists(model)
|
||||
|
||||
self.scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
|
||||
model, subfolder="scheduler", local_files_only=local_files_only
|
||||
)
|
||||
self.text_encoder = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
||||
model, subfolder="text_encoder", local_files_only=local_files_only
|
||||
)
|
||||
self.text_processor = Qwen2VLProcessor.from_pretrained(
|
||||
model, subfolder="text_processor", local_files_only=local_files_only
|
||||
)
|
||||
|
||||
self.vae = AutoencoderKL.from_pretrained(model, subfolder="vae", local_files_only=local_files_only).to(
|
||||
self.device
|
||||
)
|
||||
self.transformer = LongCatImageTransformer2DModel(od_config=od_config)
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model, subfolder="tokenizer", local_files_only=local_files_only)
|
||||
|
||||
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) if getattr(self, "vae", None) else 8
|
||||
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor * 2)
|
||||
self.image_processor_vl = self.text_processor.image_processor
|
||||
self.latent_channels = self.vae.config.get("latent_channels", 16)
|
||||
|
||||
self.image_token = "<|image_pad|>"
|
||||
self.prompt_template_encode_prefix = (
|
||||
"<|im_start|>system\n"
|
||||
"As an image editing expert, first analyze the content and attributes of the input image(s). "
|
||||
"Then, based on the user's editing instructions, clearly and precisely determine how to modify "
|
||||
"the given image(s), "
|
||||
"ensuring that only the specified parts are altered and all other aspects remain consistent "
|
||||
"with the original(s)."
|
||||
"<|im_end|>\n"
|
||||
"<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>"
|
||||
)
|
||||
self.prompt_template_encode_suffix = "<|im_end|>\n<|im_start|>assistant\n"
|
||||
|
||||
self.default_sample_size = 128
|
||||
self.tokenizer_max_length = 512
|
||||
|
||||
def _encode_prompt(self, prompt, image):
|
||||
raw_vl_input = self.image_processor_vl(images=image, return_tensors="pt")
|
||||
pixel_values = raw_vl_input["pixel_values"]
|
||||
image_grid_thw = raw_vl_input["image_grid_thw"]
|
||||
all_tokens = []
|
||||
for clean_prompt_sub, matched in split_quotation(prompt[0]):
|
||||
if matched:
|
||||
for sub_word in clean_prompt_sub:
|
||||
tokens = self.tokenizer(sub_word, add_special_tokens=False)["input_ids"]
|
||||
all_tokens.extend(tokens)
|
||||
else:
|
||||
tokens = self.tokenizer(clean_prompt_sub, add_special_tokens=False)["input_ids"]
|
||||
all_tokens.extend(tokens)
|
||||
|
||||
if len(all_tokens) > self.tokenizer_max_length:
|
||||
logger.warning(
|
||||
"Your input was truncated because `max_sequence_length` is set to "
|
||||
f" {self.tokenizer_max_length} input token nums : {len(len(all_tokens))}"
|
||||
)
|
||||
all_tokens = all_tokens[: self.tokenizer_max_length]
|
||||
|
||||
text_tokens_and_mask = self.tokenizer.pad(
|
||||
{"input_ids": [all_tokens]},
|
||||
max_length=self.tokenizer_max_length,
|
||||
padding="max_length",
|
||||
return_attention_mask=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
|
||||
text = self.prompt_template_encode_prefix
|
||||
|
||||
merge_length = self.image_processor_vl.merge_size**2
|
||||
while self.image_token in text:
|
||||
num_image_tokens = image_grid_thw.prod() // merge_length
|
||||
text = text.replace(self.image_token, "<|placeholder|>" * num_image_tokens, 1)
|
||||
text = text.replace("<|placeholder|>", self.image_token)
|
||||
|
||||
prefix_tokens = self.tokenizer(text, add_special_tokens=False)["input_ids"]
|
||||
suffix_tokens = self.tokenizer(self.prompt_template_encode_suffix, add_special_tokens=False)["input_ids"]
|
||||
|
||||
vision_start_token_id = self.tokenizer.convert_tokens_to_ids("<|vision_start|>")
|
||||
prefix_len = prefix_tokens.index(vision_start_token_id)
|
||||
suffix_len = len(suffix_tokens)
|
||||
|
||||
prefix_tokens_mask = torch.tensor([1] * len(prefix_tokens), dtype=text_tokens_and_mask.attention_mask[0].dtype)
|
||||
suffix_tokens_mask = torch.tensor([1] * len(suffix_tokens), dtype=text_tokens_and_mask.attention_mask[0].dtype)
|
||||
|
||||
prefix_tokens = torch.tensor(prefix_tokens, dtype=text_tokens_and_mask.input_ids.dtype)
|
||||
suffix_tokens = torch.tensor(suffix_tokens, dtype=text_tokens_and_mask.input_ids.dtype)
|
||||
|
||||
input_ids = torch.cat((prefix_tokens, text_tokens_and_mask.input_ids[0], suffix_tokens), dim=-1)
|
||||
attention_mask = torch.cat(
|
||||
(prefix_tokens_mask, text_tokens_and_mask.attention_mask[0], suffix_tokens_mask), dim=-1
|
||||
)
|
||||
|
||||
input_ids = input_ids.unsqueeze(0).to(self.device)
|
||||
attention_mask = attention_mask.unsqueeze(0).to(self.device)
|
||||
|
||||
pixel_values = pixel_values.to(self.device)
|
||||
image_grid_thw = image_grid_thw.to(self.device)
|
||||
|
||||
text_output = self.text_encoder(
|
||||
input_ids=input_ids,
|
||||
attention_mask=attention_mask,
|
||||
pixel_values=pixel_values,
|
||||
image_grid_thw=image_grid_thw,
|
||||
output_hidden_states=True,
|
||||
)
|
||||
# [max_sequence_length, batch, hidden_size] -> [batch, max_sequence_length, hidden_size]
|
||||
# clone to have a contiguous tensor
|
||||
prompt_embeds = text_output.hidden_states[-1].detach()
|
||||
prompt_embeds = prompt_embeds[:, prefix_len:-suffix_len, :]
|
||||
return prompt_embeds
|
||||
|
||||
def encode_prompt(
|
||||
self,
|
||||
prompt: list[str] = None,
|
||||
image: torch.Tensor | None = None,
|
||||
num_images_per_prompt: int | None = 1,
|
||||
prompt_embeds: torch.Tensor | None = None,
|
||||
):
|
||||
prompt = [prompt] if isinstance(prompt, str) else prompt
|
||||
batch_size = len(prompt)
|
||||
# If prompt_embeds is provided and prompt is None, skip encoding
|
||||
if prompt_embeds is None:
|
||||
prompt_embeds = self._encode_prompt(prompt, image)
|
||||
|
||||
_, seq_len, _ = prompt_embeds.shape
|
||||
# duplicate text embeddings and attention mask for each generation per prompt, using mps friendly method
|
||||
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
|
||||
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
|
||||
|
||||
text_ids = prepare_pos_ids(modality_id=0, type="text", start=(0, 0), num_token=prompt_embeds.shape[1]).to(
|
||||
self.device
|
||||
)
|
||||
return prompt_embeds, text_ids
|
||||
|
||||
@staticmethod
|
||||
def _pack_latents(latents, batch_size, num_channels_latents, height, width):
|
||||
latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
|
||||
latents = latents.permute(0, 2, 4, 1, 3, 5)
|
||||
latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4)
|
||||
|
||||
return latents
|
||||
|
||||
@staticmethod
|
||||
def _unpack_latents(latents, height, width, vae_scale_factor):
|
||||
batch_size, num_patches, channels = latents.shape
|
||||
|
||||
# VAE applies 8x compression on images but we must also account for packing which requires
|
||||
# latent height and width to be divisible by 2.
|
||||
height = 2 * (int(height) // (vae_scale_factor * 2))
|
||||
width = 2 * (int(width) // (vae_scale_factor * 2))
|
||||
|
||||
latents = latents.view(batch_size, height // 2, width // 2, channels // 4, 2, 2)
|
||||
latents = latents.permute(0, 3, 1, 4, 2, 5)
|
||||
|
||||
latents = latents.reshape(batch_size, channels // (2 * 2), height, width)
|
||||
|
||||
return latents
|
||||
|
||||
def _encode_vae_image(self, image: torch.Tensor, generator: torch.Generator):
|
||||
if isinstance(generator, list):
|
||||
image_latents = [
|
||||
retrieve_latents(self.vae.encode(image[i : i + 1]), generator=generator[i], sample_mode="argmax")
|
||||
for i in range(image.shape[0])
|
||||
]
|
||||
image_latents = torch.cat(image_latents, dim=0)
|
||||
else:
|
||||
image_latents = retrieve_latents(self.vae.encode(image), generator=generator, sample_mode="argmax")
|
||||
image_latents = (image_latents - self.vae.config.shift_factor) * self.vae.config.scaling_factor
|
||||
|
||||
return image_latents
|
||||
|
||||
def prepare_latents(
|
||||
self,
|
||||
image,
|
||||
batch_size,
|
||||
num_channels_latents,
|
||||
height,
|
||||
width,
|
||||
dtype,
|
||||
prompt_embeds_length,
|
||||
device,
|
||||
generator,
|
||||
latents=None,
|
||||
):
|
||||
# VAE applies 8x compression on images but we must also account for packing which requires
|
||||
# latent height and width to be divisible by 2.
|
||||
height = 2 * (int(height) // (self.vae_scale_factor * 2))
|
||||
width = 2 * (int(width) // (self.vae_scale_factor * 2))
|
||||
|
||||
image_latents, image_latents_ids = None, None
|
||||
|
||||
if image is not None:
|
||||
image = image.to(device=self.device, dtype=dtype)
|
||||
|
||||
if image.shape[1] != self.vae.config.latent_channels:
|
||||
image_latents = self._encode_vae_image(image=image, generator=generator)
|
||||
else:
|
||||
image_latents = image
|
||||
if batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] == 0:
|
||||
additional_image_per_prompt = batch_size // image_latents.shape[0]
|
||||
image_latents = torch.cat([image_latents] * additional_image_per_prompt, dim=0)
|
||||
elif batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] != 0:
|
||||
raise ValueError(
|
||||
f"Cannot duplicate `image` of batch size {image_latents.shape[0]} to {batch_size} text prompts."
|
||||
)
|
||||
else:
|
||||
image_latents = torch.cat([image_latents], dim=0)
|
||||
|
||||
image_latents = self._pack_latents(image_latents, batch_size, num_channels_latents, height, width)
|
||||
|
||||
image_latents_ids = prepare_pos_ids(
|
||||
modality_id=2,
|
||||
type="image",
|
||||
start=(prompt_embeds_length, prompt_embeds_length),
|
||||
height=height // 2,
|
||||
width=width // 2,
|
||||
).to(device, dtype=torch.float64)
|
||||
|
||||
shape = (batch_size, num_channels_latents, height, width)
|
||||
latents_ids = prepare_pos_ids(
|
||||
modality_id=1,
|
||||
type="image",
|
||||
start=(prompt_embeds_length, prompt_embeds_length),
|
||||
height=height // 2,
|
||||
width=width // 2,
|
||||
).to(device)
|
||||
|
||||
if isinstance(generator, list) and len(generator) != batch_size:
|
||||
raise ValueError(
|
||||
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
||||
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
||||
)
|
||||
|
||||
if latents is None:
|
||||
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
||||
latents = self._pack_latents(latents, batch_size, num_channels_latents, height, width)
|
||||
else:
|
||||
latents = latents.to(device=device, dtype=dtype)
|
||||
|
||||
return latents, image_latents, latents_ids, image_latents_ids
|
||||
|
||||
def check_inputs(
|
||||
self, prompt, height, width, negative_prompt=None, prompt_embeds=None, negative_prompt_embeds=None
|
||||
):
|
||||
if height % (self.vae_scale_factor * 2) != 0 or width % (self.vae_scale_factor * 2) != 0:
|
||||
logger.warning(
|
||||
"`height` and `width` have to be divisible by "
|
||||
"{self.vae_scale_factor * 2} but are {height} and {width}. "
|
||||
"Dimensions will be resized accordingly"
|
||||
)
|
||||
|
||||
if prompt is not None and prompt_embeds is not None:
|
||||
raise ValueError(
|
||||
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
|
||||
" only forward one of the two."
|
||||
)
|
||||
elif prompt is None and prompt_embeds is None:
|
||||
raise ValueError(
|
||||
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
|
||||
)
|
||||
elif prompt is not None:
|
||||
if isinstance(prompt, str):
|
||||
pass
|
||||
elif isinstance(prompt, list) and len(prompt) == 1:
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
f"`prompt` must be a `str` or a `list` of length 1, but is {prompt} (type: {type(prompt)})"
|
||||
)
|
||||
|
||||
if negative_prompt is not None and negative_prompt_embeds is not None:
|
||||
raise ValueError(
|
||||
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
|
||||
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
req: OmniDiffusionRequest,
|
||||
image: PIL.Image.Image | torch.Tensor | None = None,
|
||||
prompt: str | list[str] = None,
|
||||
negative_prompt: str | list[str] = None,
|
||||
num_inference_steps: int = 50,
|
||||
sigmas: list[float] | None = None,
|
||||
guidance_scale: float = 3.5,
|
||||
num_images_per_prompt: int | None = 1,
|
||||
generator: torch.Generator | list[torch.Generator] | None = None,
|
||||
latents: torch.FloatTensor | None = None,
|
||||
prompt_embeds: torch.FloatTensor | None = None,
|
||||
negative_prompt_embeds: torch.FloatTensor | None = None,
|
||||
output_type: str | None = "pil",
|
||||
return_dict: bool = True,
|
||||
joint_attention_kwargs: dict[str, Any] | None = None,
|
||||
):
|
||||
prompt = req.prompt if req.prompt is not None else prompt
|
||||
negative_prompt = req.negative_prompt if req.negative_prompt is not None else negative_prompt
|
||||
negative_prompt = "" if negative_prompt is None else negative_prompt
|
||||
guidance_scale = req.guidance_scale if req.guidance_scale is not None else guidance_scale
|
||||
num_inference_steps = req.num_inference_steps or num_inference_steps
|
||||
num_images_per_prompt = getattr(req, "num_outputs_per_prompt", None) or num_images_per_prompt
|
||||
generator = req.generator or generator
|
||||
prompt_embeds = getattr(req, "prompt_embeds", None) or prompt_embeds
|
||||
negative_prompt_embeds = getattr(req, "negative_prompt_embeds", None) or negative_prompt_embeds
|
||||
height = req.height or self.default_sample_size * self.vae_scale_factor
|
||||
width = req.width or self.default_sample_size * self.vae_scale_factor
|
||||
|
||||
if prompt is not None:
|
||||
batch_size = 1 if isinstance(prompt, str) else len(prompt)
|
||||
else:
|
||||
batch_size = prompt_embeds.shape[0]
|
||||
|
||||
if hasattr(req, "preprocessed_image"):
|
||||
prompt_image = req.prompt_image
|
||||
image = req.preprocessed_image
|
||||
calculated_height = req.calculated_height if hasattr(req, "calculated_height") else height
|
||||
calculated_width = req.calculated_width if hasattr(req, "calculated_width") else width
|
||||
else:
|
||||
image_size = image[0].size if isinstance(image, list) else image.size
|
||||
calculated_width, calculated_height = calculate_dimensions(1024 * 1024, image_size[0] * 1.0 / image_size[1])
|
||||
|
||||
if image is not None and not (isinstance(image, torch.Tensor) and image.size(1) == self.latent_channels):
|
||||
image = self.image_processor.resize(image, calculated_height, calculated_width)
|
||||
prompt_image = self.image_processor.resize(image, calculated_height // 2, calculated_width // 2)
|
||||
image = self.image_processor.preprocess(image, calculated_height, calculated_width)
|
||||
|
||||
self.check_inputs(
|
||||
prompt,
|
||||
calculated_height,
|
||||
calculated_width,
|
||||
negative_prompt=negative_prompt,
|
||||
prompt_embeds=prompt_embeds,
|
||||
negative_prompt_embeds=negative_prompt_embeds,
|
||||
)
|
||||
|
||||
(prompt_embeds, text_ids) = self.encode_prompt(
|
||||
prompt=prompt, image=prompt_image, prompt_embeds=prompt_embeds, num_images_per_prompt=num_images_per_prompt
|
||||
)
|
||||
|
||||
if guidance_scale > 1:
|
||||
(negative_prompt_embeds, negative_text_ids) = self.encode_prompt(
|
||||
prompt=negative_prompt,
|
||||
image=prompt_image,
|
||||
prompt_embeds=negative_prompt_embeds,
|
||||
num_images_per_prompt=num_images_per_prompt,
|
||||
)
|
||||
|
||||
device = self.device
|
||||
|
||||
# Prepare latent variables
|
||||
num_channels_latents = 16
|
||||
latents, image_latents, latents_ids, image_latents_ids = self.prepare_latents(
|
||||
image,
|
||||
batch_size * num_images_per_prompt,
|
||||
num_channels_latents,
|
||||
calculated_height,
|
||||
calculated_width,
|
||||
prompt_embeds.dtype,
|
||||
prompt_embeds.shape[1],
|
||||
device,
|
||||
generator,
|
||||
latents,
|
||||
)
|
||||
|
||||
# Prepare timesteps
|
||||
sigmas = np.linspace(1.0, 1.0 / num_inference_steps, num_inference_steps) if sigmas is None else sigmas
|
||||
image_seq_len = latents.shape[1]
|
||||
mu = calculate_shift(
|
||||
image_seq_len,
|
||||
self.scheduler.config.get("base_image_seq_len", 256),
|
||||
self.scheduler.config.get("max_image_seq_len", 4096),
|
||||
self.scheduler.config.get("base_shift", 0.5),
|
||||
self.scheduler.config.get("max_shift", 1.15),
|
||||
)
|
||||
timesteps, num_inference_steps = retrieve_timesteps(
|
||||
self.scheduler,
|
||||
num_inference_steps,
|
||||
device,
|
||||
sigmas=sigmas,
|
||||
mu=mu,
|
||||
)
|
||||
self._num_timesteps = len(timesteps)
|
||||
|
||||
guidance = None
|
||||
|
||||
if image is not None:
|
||||
latent_image_ids = torch.cat([latents_ids, image_latents_ids], dim=0)
|
||||
else:
|
||||
latent_image_ids = latents_ids
|
||||
|
||||
for i, t in enumerate(timesteps):
|
||||
self._current_timestep = t
|
||||
|
||||
latent_model_input = latents
|
||||
if image_latents is not None:
|
||||
latent_model_input = torch.cat([latents, image_latents], dim=1)
|
||||
|
||||
timestep = t.expand(latent_model_input.shape[0]).to(latents.dtype)
|
||||
|
||||
noise_pred_text = self.transformer(
|
||||
hidden_states=latent_model_input,
|
||||
timestep=timestep / 1000,
|
||||
guidance=guidance,
|
||||
encoder_hidden_states=prompt_embeds,
|
||||
txt_ids=text_ids,
|
||||
img_ids=latent_image_ids,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
noise_pred_text = noise_pred_text[:, :image_seq_len]
|
||||
if guidance_scale > 1:
|
||||
noise_pred_uncond = self.transformer(
|
||||
hidden_states=latent_model_input,
|
||||
timestep=timestep / 1000,
|
||||
encoder_hidden_states=negative_prompt_embeds,
|
||||
txt_ids=negative_text_ids,
|
||||
img_ids=latent_image_ids,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
noise_pred_uncond = noise_pred_uncond[:, :image_seq_len]
|
||||
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
||||
else:
|
||||
noise_pred = noise_pred_text
|
||||
# compute the previous noisy sample x_t -> x_t-1
|
||||
latents_dtype = latents.dtype
|
||||
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
|
||||
|
||||
if latents.dtype != latents_dtype:
|
||||
if torch.backends.mps.is_available():
|
||||
# some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
|
||||
latents = latents.to(latents_dtype)
|
||||
|
||||
self._current_timestep = None
|
||||
|
||||
if output_type == "latent":
|
||||
image = latents
|
||||
else:
|
||||
latents = self._unpack_latents(latents, calculated_height, calculated_width, self.vae_scale_factor)
|
||||
latents = (latents / self.vae.config.scaling_factor) + self.vae.config.shift_factor
|
||||
|
||||
if latents.dtype != self.vae.dtype:
|
||||
latents = latents.to(dtype=self.vae.dtype)
|
||||
|
||||
image = self.vae.decode(latents, return_dict=False)[0]
|
||||
return DiffusionOutput(output=image)
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights using AutoWeightsLoader for vLLM integration."""
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(weights)
|
||||
@@ -1,243 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
# Copied from https://github.com/meituan-longcat/LongCat-Image/blob/main/longcat_image/pipelines/pipeline_longcat_image.py#L53
|
||||
|
||||
SYSTEM_PROMPT_EN = """
|
||||
|
||||
You are a prompt engineering expert for text-to-image models. Since text-to-image models have limited capabilities in
|
||||
understanding user prompts, you need to identify the core theme and intent of the user's input and improve the model's
|
||||
understanding accuracy and generation quality through optimization and rewriting. The rewrite must strictly retain all
|
||||
information from the user's original prompt without deleting or distorting any details. Specific requirements are as
|
||||
follows:
|
||||
|
||||
1. The rewrite must not affect any information expressed in the user's original prompt; the rewritten prompt should use
|
||||
coherent natural language, avoid low-information redundant descriptions, and keep the rewritten prompt length as
|
||||
concise as possible.
|
||||
|
||||
2. Ensure consistency between input and output languages: Chinese input yields Chinese output, and English input yields
|
||||
English output. The rewritten token count should not exceed 512.
|
||||
|
||||
3. The rewritten description should further refine subject characteristics and aesthetic techniques appearing in the
|
||||
original prompt, such as lighting and textures.
|
||||
|
||||
4. If the original prompt does not specify an image style, ensure the rewritten prompt uses a **realistic photography
|
||||
style**. If the user specifies a style, retain the user's style.
|
||||
|
||||
5. When the original prompt requires reasoning to clarify user intent, use logical reasoning based on world knowledge to
|
||||
convert vague abstract descriptions into specific tangible objects (e.g., convert "the tallest animal" to "a
|
||||
giraffe").
|
||||
|
||||
6. When the original prompt requires text generation, please use double quotes to enclose the text part (e.g., `"50%
|
||||
OFF"`).
|
||||
|
||||
7. When the original prompt requires generating text-heavy scenes like webpages, logos, UIs, or posters, and no specific
|
||||
text content is specified, you need to infer appropriate text content and enclose it in double quotes. For example,
|
||||
if the user inputs: "A tourism flyer with a grassland theme," it should be rewritten as: "A tourism flyer with the
|
||||
image title 'Grassland'."
|
||||
|
||||
8. When negative words exist in the original prompt, ensure the rewritten prompt does not contain negative words. For
|
||||
example, "a lakeside without boats" should be rewritten such that the word "boat" does not appear at all.
|
||||
|
||||
9. Except for text content explicitly requested by the user, **adding any extra text content is prohibited**.
|
||||
|
||||
Here are examples of rewrites for different types of prompts: # Examples (Few-Shot Learning)
|
||||
|
||||
1. User Input: An animal with nine lives.
|
||||
|
||||
Rewrite Output: A cat bathed in soft sunlight, its fur soft and glossy. The background is a comfortable home
|
||||
environment with light from the window filtering through curtains, creating a warm light and shadow effect. The
|
||||
shot uses a medium distance perspective to highlight the cat's leisurely and stretched posture. Light cleverly hits
|
||||
the cat's face, emphasizing its spirited eyes and delicate whiskers, adding depth and affinity to the image.
|
||||
|
||||
2. User Input: Create an anime-style tourism flyer with a grassland theme.
|
||||
|
||||
Rewrite Output: In the lower right of the center, a short-haired girl sits sideways on a gray, irregularly shaped
|
||||
rock. She wears a white short-sleeved dress and brown flat shoes, holding a bunch of small white flowers in her
|
||||
left hand, smiling with her legs hanging naturally. The girl has dark brown shoulder-length hair with bangs covering
|
||||
her forehead, brown eyes, and a slightly open mouth. The rock surface has textures of varying depths. To the girl's
|
||||
left and front is lush grass, with long, yellow-green blades, some glowing golden in the sunlight. The grass extends
|
||||
into the distance, forming rolling green hills that fade in color as they recede. The sky occupies the upper half of
|
||||
the picture, pale blue dotted with a few fluffy white clouds. In the upper left corner, there is a line of text in
|
||||
italic, dark green font reading "Explore Nature's Peace". Colors are dominated by green, blue, and yellow, fluid
|
||||
lines, and distinct light and shadow contrast, creating a quiet and comfortable atmosphere.
|
||||
|
||||
3. User Input: A Christmas sale poster with a red background, promoting a Buy 1 Get 1 Free milk tea offer.
|
||||
|
||||
Rewrite Output: The poster features an overall red tone, embellished with white snowflake patterns on the top and
|
||||
left side. The upper right features a bunch of holly leaves with red berries and a pine cone. In the upper center,
|
||||
golden 3D text reads "Christmas Heartwarming Feedback" centered, along with red bold text "Buy 1 Get 1". Below, two
|
||||
transparent cups filled with bubble tea are placed side by side; the tea is light brown with dark brown pearls
|
||||
scattered at the bottom and middle. Below the cups, white snow piles up, decorated with pine branches, red berries,
|
||||
and pine cones. A blurry Christmas tree is faintly visible in the lower right corner. The image has high clarity,
|
||||
accurate text content, a unified design style, a prominent Christmas theme, and a reasonable layout, providing
|
||||
strong visual appeal.
|
||||
|
||||
4. User Input: A woman indoors shot in natural light, smiling with arms crossed, showing a relaxed and confident
|
||||
posture.
|
||||
|
||||
Rewrite Output: The image features a young Asian woman with long dark brown hair naturally falling over her
|
||||
shoulders, with some strands illuminated by light, showing a soft sheen. Her features are delicate, with long
|
||||
eyebrows, bright and spirited dark brown eyes looking directly at the camera, revealing peace and confidence. She
|
||||
has a high nose bridge, full lips with nude lipstick, and corners of the mouth slightly raised in a faint smile. Her
|
||||
skin is fair, with cheeks and collarbones illuminated by warm light, showing a healthy ruddiness. She wears a black
|
||||
spaghetti strap tank top revealing graceful collarbone lines, and a thin gold necklace with small beads and metal
|
||||
bars glinting in the light. Her outer layer is a beige knitted cardigan, soft in texture with visible knitting
|
||||
patterns on the sleeves. Her arms are crossed over her chest, hands covered by the cardigan sleeves, in a relaxed
|
||||
posture. The background is a pure dark brown without extra decoration, making the figure the absolute focus. The
|
||||
figure is located in the center of the frame. Light enters from the upper right, creating bright spots on her left
|
||||
cheek, neck, and collarbone, while the right side is slightly shadowed, creating a three-dimensional and soft tone.
|
||||
Image details are clear, showcasing skin texture, hair, and clothing materials well. Colors are dominated by warm
|
||||
tones, with the combination of beige and dark brown creating a warm and comfortable atmosphere. The overall style is
|
||||
natural, elegant, and artistic.
|
||||
|
||||
5. User Input: Create a series of images showing the growth process of an apple from seed to fruit. The series should
|
||||
include four stages: 1. Sowing, 2. Seedling growth, 3. Plant maturity, 4. Fruit harvesting.
|
||||
|
||||
Rewrite Output: A 4-panel exquisite illustration depicting the growth process of an apple, capturing each stage
|
||||
precisely and clearly. 1. "Sowing": A close-up shot of a hand gently placing a small apple seed into fertile dark
|
||||
soil, with visible soil texture and the seed's smooth surface. The background is a soft-focus garden dotted with
|
||||
green leaves and sunlight filtering through. 2. "Seedling Growth": A young apple sapling breaks through the soil,
|
||||
stretching tender green leaves toward the sky. The scene is set in a vibrant garden illuminated by warm golden
|
||||
light, highlighting the seedling's delicate structure. 3. "Plant Maturity": A mature apple tree, lush with branches
|
||||
and leaves, covered in tender green foliage and developing small apples. The background is a vibrant orchard under a
|
||||
clear blue sky, with dappled sunlight creating a peaceful atmosphere. 4. "Fruit Harvesting": A hand reaches into the
|
||||
tree to pick a ripe red apple, its smooth skin glistening in the sun. The scene shows the abundance of the orchard,
|
||||
with baskets of apples in the background, giving a sense of fulfillment. Each illustration uses a realistic style,
|
||||
focusing on details and harmonious colors to showcase the natural beauty and development of the apple's life cycle.
|
||||
|
||||
6. User Input: If 1 represents red, 2 represents green, 3 represents purple, and 4 represents yellow, please generate
|
||||
a four-color rainbow based on this rule. The color order from top to bottom is 3142.
|
||||
|
||||
Rewrite Output: The image consists of four horizontally arranged colored stripes, ordered from top to bottom as
|
||||
purple, red, yellow, and green. A white number is centered on each stripe. The top purple stripe features the number
|
||||
"3", the red stripe below it has the number "1", the yellow stripe further down has the number "4", and the bottom
|
||||
green stripe has the number "2". All numbers use a sans-serif font in pure white, forming a sharp contrast with the
|
||||
background colors to ensure good readability. The stripes have high color saturation and a slight texture. The
|
||||
overall layout is simple and clear, with distinct visual effects and no extra decorative elements, emphasizing the
|
||||
numerical information. The image is high definition, with accurate colors and a consistent style, offering strong
|
||||
visual appeal.
|
||||
|
||||
7. User Input: A stone tablet carved with "Guan Guan Ju Jiu, On the River Isle", natural light, background is a
|
||||
Chinese garden.
|
||||
|
||||
Rewrite Output: An ancient stone tablet carved with "Guan Guan Ju Jiu, On the River Isle", the surface covered with
|
||||
traces of time, the writing clear and deep. Natural light falls from above, softly illuminating every detail of the
|
||||
stone tablet and enhancing its sense of history. The background is an elegant Chinese garden featuring lush bamboo
|
||||
forests, winding paths, and quiet pools, creating a serene and distant atmosphere. The overall picture uses a
|
||||
realistic style with rich details and natural light and shadow effects, highlighting the cultural heritage of the
|
||||
stone tablet and the classical beauty of the garden.
|
||||
|
||||
# Output Format Please directly output the rewritten and optimized Prompt content. Do not include any explanatory
|
||||
language or JSON formatting, and do not add opening or closing quotes yourself."""
|
||||
|
||||
|
||||
SYSTEM_PROMPT_ZH = """
|
||||
你是一名文生图模型的prompt engineering专家。由于文生图模型对用户prompt的理解能力有限,
|
||||
你需要识别用户输入的核心主题和意图,并通过优化改写提升模型的理解准确性和生成质量。
|
||||
改写必须严格保留用户原始prompt的所有信息,不得删减或曲解任何细节。
|
||||
|
||||
具体要求如下:
|
||||
|
||||
1. 改写不能影响用户原始prompt里表达的任何信息,改写后的prompt应该使用连贯的自然语言表达,
|
||||
不要出现低信息量的冗余描述,尽可能保持改写后prompt长度精简。
|
||||
2. 请确保输入和输出的语言类型一致,中文输入中文输出,英文输入英文输出,改写后的token数量不要
|
||||
超过512个。
|
||||
3. 改写后的描述应当进一步完善原始prompt中出现的主体特征、美学技巧,如打光、纹理等。
|
||||
4. 如果原始prompt没有指定图片风格时,确保改写后的prompt采用真实摄影风格;如果用户指定了图片风
|
||||
格,则保留用户风格。
|
||||
5. 当原始prompt需要推理才能明确用户意图时,根据世界知识进行适当逻辑推理,将模糊抽象描述转化为
|
||||
具体事物(例:将“最高的动物”转化为“一头长颈鹿”)。
|
||||
6. 当原始prompt需要生成文字时,请使用双引号圈定文字部分,例:"限时5折"。
|
||||
7. 当原始prompt需要生成网页、logo、UI、海报等文字场景时,且没有指定具体文字内容,需要推断合适
|
||||
的文字内容,并使用双引号圈定,如用户输入“一个以草原为主题的旅游宣传单”,应改写为“一个旅游
|
||||
宣传单,图片标题为"草原"”。
|
||||
8. 当原始prompt中存在否定词时,需要确保改写后的prompt不存在被否定的对象,如“没有船的湖边”,改
|
||||
写后的prompt不能出现“船”这个词汇。
|
||||
9. 除非用户指定生成品牌logo,否则不要增加额外的品牌logo。
|
||||
10. 除了用户明确要求书写的文字内容外,禁止增加任何额外的文字内容。
|
||||
|
||||
以下是针对不同类型prompt改写的示例:
|
||||
|
||||
# Examples (Few-Shot Learning)
|
||||
|
||||
1. 用户输入: 九条命的动物。
|
||||
|
||||
改写输出:
|
||||
一只猫,被柔和的阳光笼罩着,毛发柔软而富有光泽。背景是一个舒适的家居环境,窗外的光线透过
|
||||
窗帘,形成温馨的光影效果。镜头采用中距离视角,突出猫悠闲舒展的姿态。光线巧妙地打在猫的脸
|
||||
部,强调它灵动的眼睛和精致的胡须,增加画面的层次感与亲和力。
|
||||
|
||||
2. 用户输入: 制作一个动画风格的旅游宣传单,以草原为主题。
|
||||
|
||||
改写输出:
|
||||
画面中央偏右下角,一个短发女孩侧身坐在灰色的不规则形状岩石上,她穿着白色短袖连衣裙和棕色
|
||||
平底鞋,左手拿着一束白色小花,面带微笑,双腿自然垂下。女孩的头发为深棕色,齐肩短发,刘海
|
||||
覆盖额头,眼睛呈棕色,嘴巴微张。岩石表面有深浅不一的纹理。女孩的左侧和前方是茂盛的草地,
|
||||
草叶细长,呈黄绿色,部分草叶在阳光下泛着金色的光芒,仿佛被阳光照亮。草地向远处延伸,形成
|
||||
连绵起伏的绿色山丘,山丘的颜色由近及远逐渐变浅。天空占据了画面的上半部分,呈淡蓝色,点缀
|
||||
着几朵白色蓬松的云彩。画面的左上角有一行文字,文字内容是斜体、深绿色的“Explore Nature's
|
||||
Peace”。色彩以绿色、蓝色和黄色为主,线条流畅,光影明暗对比明显,营造出一种宁静、舒适的氛
|
||||
围。
|
||||
|
||||
3. 用户输入: 一张以红色为背景的圣诞节促销海报,主要宣传奶茶买一送一的优惠活动。
|
||||
|
||||
改写输出:
|
||||
海报整体呈现红色调,上方和左侧点缀着白色雪花图案,右上方有一束冬青叶和红色浆果,以及一个
|
||||
松果。海报中央偏上位置,金色立体字样“圣诞节 暖心回馈”居中排列,和红色粗体字“买1送1”。海
|
||||
报下方,两个装满珍珠奶茶的透明杯子并排摆放,杯中奶茶呈浅棕色,底部和中间散布着深棕色珍珠。
|
||||
杯子下方,堆积着白色雪花,雪花上装饰着松枝、红色浆果和松果。右下角隐约可见一棵模糊的圣诞
|
||||
树。图片清晰度高,文字内容准确,整体设计风格统一,圣诞主题突出,排版布局合理,具有较强的
|
||||
视觉吸引力。
|
||||
|
||||
4. 用户输入: 一位女性在室内以自然光线拍摄,她面带微笑,双臂交叉,展现出轻松自信的姿态。
|
||||
|
||||
改写输出:
|
||||
画面中是一位年轻的亚洲女性,她拥有深棕色的长发,发丝自然地垂落在双肩,部分发丝被光线照亮,
|
||||
呈现出柔和的光泽。她的五官精致,眉毛修长,眼睛明亮有神,瞳孔呈深棕色,眼神直视镜头,流露
|
||||
出平和与自信。鼻梁挺拔,嘴唇丰满,涂有裸色系唇膏,嘴角微微上扬,展现出浅浅的微笑。她的肤色
|
||||
白皙,脸颊和锁骨处被暖色调的光线照亮,呈现出健康的红润感。她穿着一件黑色的细吊带背心,肩带
|
||||
纤细,露出优美的锁骨线条。脖颈上佩戴着一条金色的细项链,项链由小珠子和几个细长的金属条组
|
||||
成,在光线下闪烁着光泽。她的外搭是一件米黄色的针织开衫,材质柔软,袖子部分有明显的针织纹理。
|
||||
她双臂交叉在胸前,双手被开衫的袖子覆盖,姿态放松。背景是纯粹的深棕色,没有多余的装饰,使得
|
||||
人物成为画面的绝对焦点。人物位于画面中央。光线从画面的右上方射入,在人物的左侧脸颊、脖颈
|
||||
和锁骨处形成明亮的光斑,右侧则略显阴影,营造出立体感和柔和的影调。图像细节清晰,人物的皮肤
|
||||
纹理、发丝以及衣物材质都得到了很好的展现。色彩以暖色调为主,米黄色和深棕色的搭配营造出温馨
|
||||
舒适的氛围。整体呈现出一种自然、优雅且富有亲和力的艺术风格。
|
||||
|
||||
5. 用户输入: 创作一系列图片,展现苹果从种子到结果的生长过程。该系列图片应包含以下四个阶段:
|
||||
1. 播种,2. 幼苗生长,3. 植物成熟,4. 果实采摘。
|
||||
|
||||
改写输出:
|
||||
一个4宫格的精美插图,描绘苹果的生长过程,精确清晰地捕捉每个阶段。1.“播种”:特写镜头,一只
|
||||
手轻轻地将一颗小小的苹果种子放入肥沃的深色土壤中,土壤的纹理和种子光滑的表面清晰可见。背景
|
||||
是花园的柔焦画面,点缀着绿色的树叶和透过树叶洒下的阳光。2.“幼苗生长”:一棵幼小的苹果树苗破
|
||||
土而出,嫩绿的叶子向天空舒展。场景设定在一个生机勃勃的花园中,温暖的金光照亮了它。幼苗的纤
|
||||
细结构。3.“植物的成熟”:一棵成熟的苹果树,枝繁叶茂,挂满了嫩绿的叶子和正在萌发的小苹果。背
|
||||
景是一片生机勃勃的果园,湛蓝的天空下,斑驳的阳光营造出宁静祥和的氛围。4.“采摘果实”:一只手
|
||||
伸向树上,摘下一个成熟的红苹果,苹果光滑的果皮在阳光下闪闪发光。画面展现了果园的丰收景象,
|
||||
背景中摆放着一篮篮的苹果,给人一种圆满满足的感觉。每幅插图都采用写实风格,注重细节,色彩和
|
||||
谐,展现了苹果生命周期的自然之美和发展过程。
|
||||
|
||||
6. 用户输入: 如果1代表红色,2代表绿色,3代表紫色,4代表黄色,请按照此规则生成四色彩虹。它的
|
||||
颜色顺序从上到下是3142。
|
||||
|
||||
改写输出:
|
||||
图片由四个水平排列的彩色条纹组成,从上到下依次为紫色、红色、黄色和绿色。每个条纹上都居中放
|
||||
置一个白色数字。最上方的紫色条纹上是数字“3”,其下方红色条纹上是数字“1”,再下方黄色条纹上
|
||||
是数字“4”,最下方的绿色条纹上是数字“2”。所有数字均采用无衬线字体,颜色为纯白色,与背景色
|
||||
形成鲜明对比,确保了良好的可读性。条纹的颜色饱和度高,且带有轻微的纹理感,整体排版简洁明了,
|
||||
视觉效果清晰,没有多余的装饰元素,强调了数字信息本身。图片整体清晰度高,色彩准确,风格一致,
|
||||
具有较强的视觉吸引力。
|
||||
|
||||
7. 用户输入: 石碑上刻着“关关雎鸠,在河之洲”,自然光照,背景是中式园林。
|
||||
|
||||
改写输出:
|
||||
一块古老的石碑上刻着“关关雎鸠,在河之洲”,石碑表面布满岁月的痕迹,字迹清晰而深刻。自然光线
|
||||
从上方洒下,柔和地照亮石碑的每一个细节,增强了其历史感。背景是一座典雅的中式园林,园林中有
|
||||
翠绿的竹林、蜿蜒的小径和静谧的水池,营造出一种宁静而悠远的氛围。整体画面采用写实风格,细节
|
||||
丰富,光影效果自然,突出了石碑的文化底蕴和园林的古典美。
|
||||
|
||||
# 输出格式 请直接输出改写优化后的 Prompt 内容,不要包含任何解释性语言或 JSON 格式,不要自行添加开头
|
||||
或结尾的引号。
|
||||
"""
|
||||
@@ -49,6 +49,11 @@ _DIFFUSION_MODELS = {
|
||||
"pipeline_longcat_image",
|
||||
"LongCatImagePipeline",
|
||||
),
|
||||
"LongCatImageEditPipeline": (
|
||||
"longcat_image",
|
||||
"pipeline_longcat_image_edit",
|
||||
"LongcatImageEditPipeline",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +95,7 @@ _DIFFUSION_POST_PROCESS_FUNCS = {
|
||||
"OvisImagePipeline": "get_ovis_image_post_process_func",
|
||||
"WanPipeline": "get_wan22_post_process_func",
|
||||
"LongCatImagePipeline": "get_longcat_image_post_process_func",
|
||||
"LongCatImageEditPipeline": "get_longcat_image_post_process_func",
|
||||
}
|
||||
|
||||
_DIFFUSION_PRE_PROCESS_FUNCS = {
|
||||
@@ -98,6 +104,7 @@ _DIFFUSION_PRE_PROCESS_FUNCS = {
|
||||
# where mod_folder and mod_relname are defined and mapped using `_DIFFUSION_MODELS` via the `arch` key
|
||||
"QwenImageEditPipeline": "get_qwen_image_edit_pre_process_func",
|
||||
"QwenImageEditPlusPipeline": "get_qwen_image_edit_plus_pre_process_func",
|
||||
"LongCatImageEditPipeline": "get_longcat_image_edit_pre_process_func",
|
||||
"QwenImageLayeredPipeline": "get_qwen_image_layered_pre_process_func",
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user