[Feature] HunyuanImage-3.0 IT2I: multi-image input + prompt API cleanup (#3444)

Signed-off-by: TaffyOfficial <2324465096@qq.com>
Signed-off-by: TaffyOfficial <wu15922848573@outlook.com>
Signed-off-by: skf1999 <13234016272@163.com>
Signed-off-by: zuiho <2324465096@qq.com>
Signed-off-by: Claude Code <noreply@anthropic.com>
Signed-off-by: zuiho <wu15922848573@outlook.com>
Signed-off-by: TaffyOfficial <2587297563@qq.com>
Co-authored-by: TaffyOfficial <2324465096@qq.com>
Co-authored-by: TaffyOfficial <wu15922848573@outlook.com>
Co-authored-by: skf1999 <13234016272@163.com>
This commit is contained in:
authored and GitHub committed 2026-05-14 16:58:40 +08:00
1 parent c4f859bf56
commit 3f63aaf982
17 files changed
+1559 -534

No files matched your search

@@ -1349,8 +1349,16 @@ def test_image_edit_parameter_default(async_omni_test_client):
engine = async_omni_test_client.app.state.engine_client
captured_sampling_params = engine.captured_sampling_params_list[-1]
assert captured_sampling_params.width == 24
assert captured_sampling_params.height == 16
# size="auto" on multi-stage pipelines deliberately leaves the diffusion
# stages sampling_params width/height unset so AR-driven pipelines (e.g.
# HunyuanImage-3.0) can let ar2diffusion override the final bucket from
# the AR-predicted ratio token; see
# test_image_edits_size_auto_preserves_bridge_size for the contract.
# Single-stage diffusion (test_image_edit_parameter_default_single_stage)
# still pins width/height to the input image size via api_servers
# gen_params, which is unchanged.
assert captured_sampling_params.width is None
assert captured_sampling_params.height is None
assert captured_sampling_params.num_outputs_per_prompt == 1
assert captured_sampling_params.num_inference_steps == 4
assert captured_sampling_params.guidance_scale == 7.5
@@ -1649,3 +1657,59 @@ def test_extract_images_from_result():
assert len(images) == 1
assert isinstance(images[0], Image.Image)
assert images[0].size == (32, 32)
def test_image_edits_size_auto_preserves_bridge_size(async_omni_stage_configs_only_client):
"""size=auto must NOT pin the diffusion stage sampling_params.height/width.
Regression: prior to the fix, edit_images resolved size=auto to the
first input image dimensions and forwarded them through gen_params +
extra_body to the diffusion stages sampling_params. AR-driven
pipelines (e.g. HunyuanImage-3.0) rely on ar2diffusions
bridge to override the final bucket via the AR-predicted ratio token,
and the DiT pre_process_func only fills sampling_params from the
bridge value when sampling_params.width is None (see
pipeline_hunyuan_image3.py:290). Non-None width from the input image
silently suppressed the AR decision, producing the wrong bucket
(e.g. 1024x1024 square instead of the AR-decided 1280x720 landscape
for multi-image fusion).
Cross-pins the multi-image fix at the API level: 2 reference images
with bot_task=think must produce 2 <img> placeholders in the captured
AR prompt (build_prompt called with num_images=2).
"""
img_a = make_test_image_bytes((32, 32))
img_b = make_test_image_bytes((128, 64))
response = async_omni_stage_configs_only_client.post(
"/v1/images/edits",
files=[("image", img_a), ("image", img_b)],
data={
"prompt": "fuse",
"size": "auto",
"bot_task": "think",
},
)
assert response.status_code == 200, response.text
engine = async_omni_stage_configs_only_client.app.state.engine_client
captured = engine.captured_sampling_params_list
assert captured is not None
assert len(captured) == 2
diffusion_params = captured[1]
assert diffusion_params.height is None, (
f"size=auto leaked into diffusion sampling_params.height={diffusion_params.height}; "
"must stay None so AR-driven pipelines can apply the bridges decision."
)
assert diffusion_params.width is None, (
f"size=auto leaked into diffusion sampling_params.width={diffusion_params.width}; "
"must stay None so AR-driven pipelines can apply the bridges decision."
)
KEY = "prompt"
IMG = "<img>"
captured_prompt = engine.captured_prompt
if isinstance(captured_prompt, dict) and isinstance(captured_prompt.get("prompt"), str):
assert captured_prompt["prompt"].count("<img>") == 2, (
f"N=2 reference images must emit 2 <img> placeholders in AR prompt; got {captured_prompt[KEY].count(IMG)} -- prompt: {captured_prompt[KEY]!r}"
)