[diffusion] model: support mutli-image input and qwen-image-edit-2509 (#15005)
This commit is contained in:
@@ -454,72 +454,71 @@
|
||||
"expected_median_denoise_ms": 102.03
|
||||
},
|
||||
"qwen_image_edit_ti2i": {
|
||||
"notes": "single uploaded reference image, Qwen/Qwen-Image-Edit",
|
||||
"expected_e2e_ms": 138500.0,
|
||||
"expected_avg_denoise_ms": 720.0,
|
||||
"expected_median_denoise_ms": 718.0,
|
||||
"stages_ms": {
|
||||
"InputValidationStage": 23,
|
||||
"ImageEncodingStage": 1485.0,
|
||||
"ImageVAEEncodingStage": 400.0,
|
||||
"ConditioningStage": 0.13,
|
||||
"TimestepPreparationStage": 13.78,
|
||||
"LatentPreparationStage": 15.0,
|
||||
"DenoisingStage": 36000.0,
|
||||
"DecodingStage": 850.0
|
||||
"InputValidationStage": 38.62,
|
||||
"ImageEncodingStage": 1174.26,
|
||||
"ImageVAEEncodingStage": 233.71,
|
||||
"TimestepPreparationStage": 3.0,
|
||||
"LatentPreparationStage": 0.17,
|
||||
"ConditioningStage": 0.01,
|
||||
"DenoisingStage": 42542.67,
|
||||
"DecodingStage": 508.39
|
||||
},
|
||||
"denoise_step_ms": {
|
||||
"0": 720.0,
|
||||
"1": 720.0,
|
||||
"2": 720.0,
|
||||
"3": 720.0,
|
||||
"4": 720.0,
|
||||
"5": 720.0,
|
||||
"6": 720.0,
|
||||
"7": 720.0,
|
||||
"8": 720.0,
|
||||
"9": 720.0,
|
||||
"10": 720.0,
|
||||
"11": 720.0,
|
||||
"12": 720.0,
|
||||
"13": 720.0,
|
||||
"14": 720.0,
|
||||
"15": 720.0,
|
||||
"16": 720.0,
|
||||
"17": 720.0,
|
||||
"18": 720.0,
|
||||
"19": 720.0,
|
||||
"20": 720.0,
|
||||
"21": 720.0,
|
||||
"22": 720.0,
|
||||
"23": 720.0,
|
||||
"24": 720.0,
|
||||
"25": 720.0,
|
||||
"26": 720.0,
|
||||
"27": 720.0,
|
||||
"28": 720.0,
|
||||
"29": 720.0,
|
||||
"30": 720.0,
|
||||
"31": 720.0,
|
||||
"32": 720.0,
|
||||
"33": 720.0,
|
||||
"34": 720.0,
|
||||
"35": 720.0,
|
||||
"36": 720.0,
|
||||
"37": 720.0,
|
||||
"38": 720.0,
|
||||
"39": 720.0,
|
||||
"40": 720.0,
|
||||
"41": 720.0,
|
||||
"42": 720.0,
|
||||
"43": 720.0,
|
||||
"44": 720.0,
|
||||
"45": 720.0,
|
||||
"46": 720.0,
|
||||
"47": 720.0,
|
||||
"48": 720.0,
|
||||
"49": 720.0
|
||||
}
|
||||
"0": 705.2,
|
||||
"1": 854.4,
|
||||
"2": 853.02,
|
||||
"3": 852.77,
|
||||
"4": 850.58,
|
||||
"5": 851.46,
|
||||
"6": 850.78,
|
||||
"7": 851.67,
|
||||
"8": 852.81,
|
||||
"9": 853.98,
|
||||
"10": 853.68,
|
||||
"11": 852.62,
|
||||
"12": 853.78,
|
||||
"13": 854.42,
|
||||
"14": 853.59,
|
||||
"15": 853.28,
|
||||
"16": 853.58,
|
||||
"17": 854.02,
|
||||
"18": 854.42,
|
||||
"19": 854.46,
|
||||
"20": 853.6,
|
||||
"21": 854.18,
|
||||
"22": 854.05,
|
||||
"23": 854.45,
|
||||
"24": 855.4,
|
||||
"25": 851.82,
|
||||
"26": 855.31,
|
||||
"27": 854.42,
|
||||
"28": 854.2,
|
||||
"29": 854.43,
|
||||
"30": 855.49,
|
||||
"31": 854.51,
|
||||
"32": 855.26,
|
||||
"33": 852.42,
|
||||
"34": 853.82,
|
||||
"35": 856.22,
|
||||
"36": 854.53,
|
||||
"37": 854.44,
|
||||
"38": 854.07,
|
||||
"39": 852.74,
|
||||
"40": 854.56,
|
||||
"41": 854.24,
|
||||
"42": 853.56,
|
||||
"43": 854.74,
|
||||
"44": 855.34,
|
||||
"45": 853.93,
|
||||
"46": 854.36,
|
||||
"47": 852.65,
|
||||
"48": 851.19,
|
||||
"49": 851.89
|
||||
},
|
||||
"expected_e2e_ms": 44503.12,
|
||||
"expected_avg_denoise_ms": 850.73,
|
||||
"expected_median_denoise_ms": 854.0
|
||||
},
|
||||
"wan2_1_t2v_1.3b": {
|
||||
"stages_ms": {
|
||||
@@ -655,6 +654,63 @@
|
||||
"expected_avg_denoise_ms": 2608.84,
|
||||
"expected_median_denoise_ms": 2601.59
|
||||
},
|
||||
"qwen_image_edit_2509_ti2i": {
|
||||
"stages_ms": {
|
||||
"InputValidationStage": 213.24,
|
||||
"ImageEncodingStage": 1089.12,
|
||||
"ImageVAEEncodingStage": 304.56,
|
||||
"TimestepPreparationStage": 2.94,
|
||||
"LatentPreparationStage": 0.2,
|
||||
"ConditioningStage": 0.01,
|
||||
"DenoisingStage": 50724.5,
|
||||
"DecodingStage": 601.02
|
||||
},
|
||||
"denoise_step_ms": {
|
||||
"0": 1057.09,
|
||||
"1": 1267.06,
|
||||
"2": 1268.33,
|
||||
"3": 1268.94,
|
||||
"4": 1270.36,
|
||||
"5": 1270.44,
|
||||
"6": 1268.61,
|
||||
"7": 1270.21,
|
||||
"8": 1274.98,
|
||||
"9": 1271.57,
|
||||
"10": 1273.15,
|
||||
"11": 1271.56,
|
||||
"12": 1272.69,
|
||||
"13": 1271.62,
|
||||
"14": 1274.04,
|
||||
"15": 1276.81,
|
||||
"16": 1272.2,
|
||||
"17": 1269.33,
|
||||
"18": 1275.96,
|
||||
"19": 1274.43,
|
||||
"20": 1272.57,
|
||||
"21": 1275.28,
|
||||
"22": 1273.63,
|
||||
"23": 1275.06,
|
||||
"24": 1277.39,
|
||||
"25": 1277.27,
|
||||
"26": 1274.74,
|
||||
"27": 1273.38,
|
||||
"28": 1276.77,
|
||||
"29": 1275.59,
|
||||
"30": 1275.51,
|
||||
"31": 1274.9,
|
||||
"32": 1274.8,
|
||||
"33": 1279.03,
|
||||
"34": 1272.9,
|
||||
"35": 1274.67,
|
||||
"36": 1272.61,
|
||||
"37": 1272.82,
|
||||
"38": 1276.41,
|
||||
"39": 1273.55
|
||||
},
|
||||
"expected_e2e_ms": 52938.04,
|
||||
"expected_avg_denoise_ms": 1267.96,
|
||||
"expected_median_denoise_ms": 1273.46
|
||||
},
|
||||
"fastwan2_2_ti2v_5b": {
|
||||
"stages_ms": {
|
||||
"InputValidationStage": 88.86,
|
||||
|
||||
@@ -90,15 +90,25 @@ def diffusion_server(case: DiffusionTestCase) -> ServerContext:
|
||||
and sampling_params.image_path
|
||||
):
|
||||
# Handle URL or local path
|
||||
if is_image_url(sampling_params.image_path):
|
||||
image_path = download_image_from_url(str(sampling_params.image_path))
|
||||
else:
|
||||
image_path = Path(sampling_params.image_path)
|
||||
image_path_list = sampling_params.image_path
|
||||
if not isinstance(image_path_list, list):
|
||||
image_path_list = [image_path_list]
|
||||
|
||||
new_image_path_list = []
|
||||
for image_path in image_path_list:
|
||||
if is_image_url(image_path):
|
||||
new_image_path_list.append(download_image_from_url(str(image_path)))
|
||||
else:
|
||||
new_image_path_list.append(Path(image_path))
|
||||
if not image_path.exists():
|
||||
pytest.skip(f"{case.id}: file missing: {image_path}")
|
||||
|
||||
image_path_list = new_image_path_list
|
||||
|
||||
warmup.run_edit_warmups(
|
||||
count=server_args.warmup_edit,
|
||||
edit_prompt=sampling_params.prompt,
|
||||
image_path=image_path,
|
||||
image_path=image_path_list,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error("Warm-up failed for %s: %s", case.id, exc)
|
||||
|
||||
@@ -278,23 +278,31 @@ class WarmupRunner:
|
||||
if count <= 0:
|
||||
return
|
||||
|
||||
if not image_path.exists():
|
||||
logger.warning(
|
||||
"[server-test] Skipping edit warmup: image missing at %s", image_path
|
||||
)
|
||||
return
|
||||
if not isinstance(image_path, list):
|
||||
image_path = [image_path]
|
||||
|
||||
for image in image_path:
|
||||
if not image.exists():
|
||||
logger.warning(
|
||||
"[server-test] Skipping edit warmup: image missing at %s", image
|
||||
)
|
||||
return
|
||||
|
||||
logger.info("[server-test] Running %s edit warm-up(s)", count)
|
||||
for _ in range(count):
|
||||
with image_path.open("rb") as fh:
|
||||
images = [open(image, "rb") for image in image_path]
|
||||
try:
|
||||
result = self.client.images.edit(
|
||||
model=self.model,
|
||||
image=fh,
|
||||
image=images,
|
||||
prompt=edit_prompt,
|
||||
n=1,
|
||||
size=self.output_size,
|
||||
response_format="b64_json",
|
||||
)
|
||||
finally:
|
||||
for img in images:
|
||||
img.close()
|
||||
validate_image(result.data[0].b64_json)
|
||||
|
||||
|
||||
@@ -585,22 +593,36 @@ def get_generate_fn(
|
||||
if not sampling_params.prompt or not sampling_params.image_path:
|
||||
pytest.skip(f"{id}: no edit config")
|
||||
|
||||
if is_image_url(sampling_params.image_path):
|
||||
image_path = download_image_from_url(str(sampling_params.image_path))
|
||||
else:
|
||||
image_path = Path(sampling_params.image_path)
|
||||
if not image_path.exists():
|
||||
pytest.skip(f"{id}: file missing: {image_path}")
|
||||
image_paths = sampling_params.image_path
|
||||
|
||||
with image_path.open("rb") as fh:
|
||||
if not isinstance(image_paths, list):
|
||||
image_paths = [image_paths]
|
||||
|
||||
new_image_paths = []
|
||||
for image_path in image_paths:
|
||||
if is_image_url(image_path):
|
||||
new_image_paths.append(download_image_from_url(str(image_path)))
|
||||
else:
|
||||
new_image_paths.append(Path(image_path))
|
||||
if not image_path.exists():
|
||||
pytest.skip(f"{id}: file missing: {image_path}")
|
||||
|
||||
image_paths = new_image_paths
|
||||
|
||||
images = [open(image_path, "rb") for image_path in image_paths]
|
||||
try:
|
||||
response = client.images.with_raw_response.edit(
|
||||
model=model_path,
|
||||
image=fh,
|
||||
image=images,
|
||||
prompt=sampling_params.prompt,
|
||||
n=1,
|
||||
size=sampling_params.output_size,
|
||||
response_format="b64_json",
|
||||
)
|
||||
finally:
|
||||
for img in images:
|
||||
img.close()
|
||||
|
||||
rid = response.headers.get("x-request-id", "")
|
||||
|
||||
result = response.parse()
|
||||
|
||||
@@ -226,6 +226,14 @@ TI2I_sampling_params = DiffusionSamplingParams(
|
||||
image_path="https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",
|
||||
)
|
||||
|
||||
MULTI_IMAGE_TI2I_sampling_params = DiffusionSamplingParams(
|
||||
prompt="The magician bear is on the left, the alchemist bear is on the right, facing each other in the central park square.",
|
||||
image_path=[
|
||||
"https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-Image/edit2509/edit2509_1.jpg",
|
||||
"https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-Image/edit2509/edit2509_2.jpg",
|
||||
],
|
||||
)
|
||||
|
||||
T2V_PROMPT = "A curious raccoon"
|
||||
|
||||
TI2V_sampling_params = DiffusionSamplingParams(
|
||||
@@ -288,6 +296,16 @@ ONE_GPU_CASES_A: list[DiffusionTestCase] = [
|
||||
),
|
||||
TI2I_sampling_params,
|
||||
),
|
||||
DiffusionTestCase(
|
||||
"qwen_image_edit_2509_ti2i",
|
||||
DiffusionServerArgs(
|
||||
model_path="Qwen/Qwen-Image-Edit-2509",
|
||||
modality="image",
|
||||
warmup_text=0,
|
||||
warmup_edit=1,
|
||||
),
|
||||
MULTI_IMAGE_TI2I_sampling_params,
|
||||
),
|
||||
]
|
||||
|
||||
ONE_GPU_CASES_B: list[DiffusionTestCase] = [
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from typing import List, Union
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
@@ -100,9 +101,9 @@ def upload_file_to_slack(
|
||||
model: str = None,
|
||||
prompt: str = None,
|
||||
file_path: str = None,
|
||||
origin_file_path: str = None,
|
||||
origin_file_path: Union[str, List[str]] = None,
|
||||
) -> bool:
|
||||
temp_path = None
|
||||
temp_paths = []
|
||||
try:
|
||||
from slack_sdk import WebClient
|
||||
|
||||
@@ -117,17 +118,39 @@ def upload_file_to_slack(
|
||||
logger.info(f"Slack upload failed: no file path")
|
||||
return False
|
||||
|
||||
if origin_file_path and origin_file_path.startswith(("http", "https")):
|
||||
suffix = os.path.splitext(urlparse(origin_file_path).path)[1] or ".tmp"
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tf:
|
||||
with urlopen(origin_file_path) as response:
|
||||
tf.write(response.read())
|
||||
temp_path = tf.name
|
||||
origin_file_path = temp_path
|
||||
origin_paths = []
|
||||
if isinstance(origin_file_path, str):
|
||||
if origin_file_path:
|
||||
origin_paths.append(origin_file_path)
|
||||
elif isinstance(origin_file_path, list):
|
||||
origin_paths = [p for p in origin_file_path if p]
|
||||
|
||||
uploads = [{"file": file_path, "title": "Generated Image"}]
|
||||
if origin_file_path and os.path.exists(origin_file_path):
|
||||
uploads.insert(0, {"file": origin_file_path, "title": "Original Image"})
|
||||
final_origin_paths = []
|
||||
for path in origin_paths:
|
||||
if path.startswith(("http", "https")):
|
||||
try:
|
||||
suffix = os.path.splitext(urlparse(path).path)[1] or ".tmp"
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tf:
|
||||
with urlopen(path) as response:
|
||||
tf.write(response.read())
|
||||
temp_paths.append(tf.name)
|
||||
final_origin_paths.append(tf.name)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to download {path}: {e}")
|
||||
else:
|
||||
final_origin_paths.append(path)
|
||||
|
||||
uploads = []
|
||||
for i, path in enumerate(final_origin_paths):
|
||||
if os.path.exists(path):
|
||||
title = (
|
||||
"Original Image"
|
||||
if len(final_origin_paths) == 1
|
||||
else f"Original Image {i+1}"
|
||||
)
|
||||
uploads.append({"file": path, "title": title})
|
||||
|
||||
uploads.append({"file": file_path, "title": "Generated Image"})
|
||||
|
||||
message = (
|
||||
f"*Case ID:* `{case_id}`\n" f"*Model:* `{model}`\n" f"*Prompt:* {prompt}"
|
||||
@@ -189,5 +212,6 @@ def upload_file_to_slack(
|
||||
logger.info(f"Slack upload failed: {e}")
|
||||
return False
|
||||
finally:
|
||||
if temp_path and os.path.exists(temp_path):
|
||||
os.remove(temp_path)
|
||||
for p in temp_paths:
|
||||
if os.path.exists(p):
|
||||
os.remove(p)
|
||||
|
||||
Reference in New Issue
Block a user