项目文件夹

文件
wehub-resource-sync eec33d25b2
Build Wheel / build (3.11) (push) Failing after 1s
Build Wheel / build (3.12) (push) Failing after 0s
pre-commit / pre-commit (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:29:08 +08:00

756 行
25 KiB
Python

# ruff: noqa: E402, I001
import argparse
import math
import os
import sys
import types
from pathlib import Path
import pytest
from PIL import Image
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu]
REPO_ROOT = Path(__file__).resolve().parents[2]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from benchmarks.accuracy.common import VllmOmniImageClient
from benchmarks.accuracy.image_to_image.gedit_bench import (
GROUPS as GEDIT_GROUPS,
GEditBenchEvaluator,
GEditBenchRunner,
_load_gedit_dataset,
_resolve_gedit_split,
infer_model_name,
resolve_model_name,
select_balanced_gedit_rows,
parse_score_payload,
summarize_generated_records as summarize_gedit_generated_records,
summarize_gedit_rows,
summarize_gedit_rows_with_backbone,
)
from benchmarks.accuracy.text_to_image.gbench import (
_expand_sample_path,
_trajectory_judge_payload,
_write_json_with_timestamp,
LocalJudgeClient,
GEBenchEvaluator,
TYPE_TO_FOLDER,
select_balanced_gebench_samples,
summarize_generated_records as summarize_gebench_generated_records,
summarize_gebench_results,
)
from tests.e2e.accuracy.qwen3_omni.qwen3_omni_acc_bench_core import seed_tts_bench_argv
from tests.e2e.accuracy.qwen3_omni.run_qwen_omni_acc_benchmark import sync_dataset_env_from_ns
from vllm_omni.benchmarks.data_modules.seed_tts_dataset import resolve_seed_tts_root
def test_seed_tts_bench_argv_preserves_hf_repo_id_from_env(monkeypatch):
monkeypatch.setenv("VLLM_SEED_TTS_DATASET_PATH", "zhaochenyang20/seed-tts-eval")
monkeypatch.delenv("VLLM_SEED_TTS_REPO", raising=False)
argv = seed_tts_bench_argv(locale="en")
dataset_idx = argv.index("--dataset-path")
assert argv[dataset_idx + 1] == "zhaochenyang20/seed-tts-eval"
def test_sync_dataset_env_preserves_seed_tts_hf_repo_id(monkeypatch):
ns = argparse.Namespace(
daily_omni_repo=None,
daily_omni_qa_json=None,
daily_omni_video_dir=None,
seed_tts_dataset_path="zhaochenyang20/seed-tts-eval",
seed_tts_root=None,
)
monkeypatch.delenv("VLLM_SEED_TTS_DATASET_PATH", raising=False)
sync_dataset_env_from_ns(ns)
assert os.environ["VLLM_SEED_TTS_DATASET_PATH"] == "zhaochenyang20/seed-tts-eval"
def test_resolve_seed_tts_root_downloads_only_requested_locale(monkeypatch, tmp_path: Path):
downloaded_root = tmp_path / "seed_tts_cache"
(downloaded_root / "zh" / "prompt-wavs").mkdir(parents=True)
(downloaded_root / "zh" / "meta.lst").write_text("", encoding="utf-8")
captured: dict[str, object] = {}
def fake_snapshot_download(*, repo_id, repo_type, allow_patterns):
captured["repo_id"] = repo_id
captured["repo_type"] = repo_type
captured["allow_patterns"] = allow_patterns
return str(downloaded_root)
monkeypatch.setitem(
sys.modules,
"huggingface_hub",
types.SimpleNamespace(snapshot_download=fake_snapshot_download),
)
resolved = resolve_seed_tts_root(
"zhaochenyang20/seed-tts-eval",
explicit_root=None,
locale="zh",
)
assert resolved == downloaded_root.resolve()
assert captured["repo_id"] == "zhaochenyang20/seed-tts-eval"
assert captured["repo_type"] == "dataset"
assert captured["allow_patterns"] == ["zh/**"]
def test_summarize_gebench_generated_records_groups_by_type():
records = [
{"data_type": "type1", "sample_name": "english_phone/folder_1", "output_path": "a.png"},
{"data_type": "type1", "sample_name": "english_phone/folder_2", "output_path": "b.png"},
{"data_type": "type2", "sample_name": "english_phone/folder_3", "output_path": "c.png"},
]
summary = summarize_gebench_generated_records(records)
assert summary["count"] == 3
assert summary["by_type"]["type1"]["count"] == 2
assert summary["by_type"]["type2"]["count"] == 1
assert "samples" not in summary["by_type"]["type1"]
def test_summarize_gebench_results_computes_type_and_global_means():
results = [
{"data_type": "type1", "overall": 0.8, "scores": {"goal": 5, "logic": 4}},
{"data_type": "type1", "overall": 0.6, "scores": {"goal": 3, "logic": 4}},
{"data_type": "type2", "overall": 0.5, "scores": {"goal": 2, "logic": 3}},
]
summary = summarize_gebench_results(results)
assert math.isclose(summary["overall_mean"], (0.8 + 0.6 + 0.5) / 3)
assert math.isclose(summary["by_type"]["type1"]["overall_mean"], 0.7)
assert math.isclose(summary["by_type"]["type2"]["overall_mean"], 0.5)
assert math.isclose(summary["by_type"]["type1"]["score_means"]["goal"], 4.0)
def test_write_json_with_timestamp_writes_stable_and_timestamped_files(monkeypatch, tmp_path: Path):
monkeypatch.setattr(
"benchmarks.accuracy.text_to_image.gbench._utc_timestamp",
lambda: "20260325T130000Z",
)
timestamped_path = _write_json_with_timestamp(tmp_path / "summary.json", {"ok": True})
assert (tmp_path / "summary.json").exists()
assert timestamped_path == tmp_path / "summary_20260325T130000Z.json"
assert timestamped_path.exists()
def test_select_balanced_gebench_samples_limits_each_type_independently():
sample_paths_by_type = {
"type1": [Path(f"/tmp/type1_{idx}") for idx in range(12)],
"type2": [Path(f"/tmp/type2_{idx}") for idx in range(8)],
"type3": [Path(f"/tmp/type3_{idx}") for idx in range(15)],
}
selected = select_balanced_gebench_samples(sample_paths_by_type, samples_per_type=10)
assert len(selected["type1"]) == 10
assert len(selected["type2"]) == 8
assert len(selected["type3"]) == 10
assert selected["type1"][0].name == "type1_0"
assert selected["type3"][-1].name == "type3_9"
def test_expand_sample_path_flattens_json_list_samples(tmp_path: Path):
sample_path = tmp_path / "trajectories.json"
sample_path.write_text(
"""
[
{"id": "sample_a", "lang_device": "english_phone", "instruction": "do a"},
{"id": "sample_b", "lang_device": "english_phone", "instruction": "do b"}
]
""".strip(),
encoding="utf-8",
)
specs = _expand_sample_path(sample_path)
assert len(specs) == 2
assert specs[0].sample_name == "sample_a"
assert specs[1].sample_name == "sample_b"
assert specs[0].lang_device == "english_phone"
def test_gebench_evaluate_skips_missing_output_folder(tmp_path: Path):
dataset_type_root = tmp_path / TYPE_TO_FOLDER["type3"] / "english_phone"
sample_dir = dataset_type_root / "sample_a"
sample_dir.mkdir(parents=True)
(sample_dir / "meta_data.json").write_text("{}", encoding="utf-8")
judge = LocalJudgeClient(base_url="http://127.0.0.1:8094", api_key="EMPTY", model="judge")
evaluator = GEBenchEvaluator(dataset_root=tmp_path, output_root=tmp_path / "outputs", judge=judge)
payload = evaluator.evaluate(data_type="type3")
assert payload["results"] == []
assert payload["summary"]["count"] == 0
def test_local_judge_client_retries_when_first_response_is_not_json(monkeypatch):
responses = iter(
[
"The image looks like a GUI screenshot with several controls.",
'{"goal": 4, "logic": 4, "cons": 5, "ui": 4, "qual": 4, "reasoning": "mostly correct"}',
]
)
def fake_request_text(self, prompt, images):
return next(responses)
monkeypatch.setattr(LocalJudgeClient, "_request_text", fake_request_text)
judge = LocalJudgeClient(base_url="http://127.0.0.1:8094", api_key="EMPTY", model="judge")
result = judge.evaluate(prompt="Evaluate this GUI trajectory.", images=[Image.new("RGB", (2, 2), color="white")])
assert result["goal"] == 4
assert result["cons"] == 5
def test_local_judge_client_returns_zero_scores_when_retry_is_still_invalid(monkeypatch):
responses = iter(
[
"not json",
"still not json",
]
)
def fake_request_text(self, prompt, images):
return next(responses)
monkeypatch.setattr(LocalJudgeClient, "_request_text", fake_request_text)
judge = LocalJudgeClient(base_url="http://127.0.0.1:8094", api_key="EMPTY", model="judge")
result = judge.evaluate(prompt="Evaluate this GUI trajectory.", images=[Image.new("RGB", (2, 2), color="white")])
assert result["goal"] == 0
assert result["logic"] == 0
assert result["cons"] == 0
assert result["ui"] == 0
assert result["qual"] == 0
assert result["reasoning"] == "still not json"
def test_trajectory_judge_payload_collapses_six_frames_into_single_storyboard():
frames = [Image.new("RGB", (8, 6), color=(idx * 10, idx * 10, idx * 10)) for idx in range(6)]
prompt_suffix, judge_images = _trajectory_judge_payload(frames)
assert "frame0" in prompt_suffix
assert len(judge_images) == 1
assert judge_images[0].size == (24, 12)
def test_image_edit_client_uses_openai_image_edit_endpoint(monkeypatch):
captured = {}
class FakeResponse:
status_code = 200
def raise_for_status(self):
return None
def json(self):
return {
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aY0cAAAAASUVORK5CYII="
}
]
}
def fake_post(url, data=None, files=None, headers=None, timeout=None, **kwargs):
captured["url"] = url
captured["data"] = data
captured["files"] = files
captured["headers"] = headers
captured["timeout"] = timeout
return FakeResponse()
monkeypatch.setattr("benchmarks.accuracy.common.requests.post", fake_post)
client = VllmOmniImageClient(base_url="http://127.0.0.1:8093", api_key="EMPTY")
image = Image.new("RGB", (2, 2), color="white")
output = client.generate_image_edit(
model="Qwen/Qwen-Image-Edit",
prompt="edit this image",
images=image,
width=512,
height=512,
)
assert output.size == (1, 1)
assert captured["url"] == "http://127.0.0.1:8093/v1/images/edits"
assert captured["data"]["prompt"] == "edit this image"
assert captured["data"]["size"] == "512x512"
assert captured["files"][0][0] == "image"
def test_text_to_image_client_forwards_output_compression(monkeypatch):
captured = {}
class FakeResponse:
status_code = 200
def raise_for_status(self):
return None
def json(self):
return {
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aY0cAAAAASUVORK5CYII="
}
]
}
def fake_post(url, json=None, headers=None, timeout=None, **kwargs):
captured["url"] = url
captured["json"] = json
return FakeResponse()
monkeypatch.setattr("benchmarks.accuracy.common.requests.post", fake_post)
client = VllmOmniImageClient(base_url="http://127.0.0.1:8093", api_key="EMPTY")
output = client.generate_text_to_image(
model="Qwen/Qwen-Image",
prompt="generate a gui",
width=768,
height=576,
num_inference_steps=8,
output_compression=98,
)
assert output.size == (1, 1)
assert captured["url"] == "http://127.0.0.1:8093/v1/images/generations"
assert captured["json"]["size"] == "768x576"
assert captured["json"]["num_inference_steps"] == 8
assert captured["json"]["output_compression"] == 98
def test_parse_score_payload_handles_raw_json_and_delimited_json():
raw = '{"score": [7, 8], "reasoning": "ok"}'
wrapped = 'prefix ||V^=^V|| {"score": [6], "reasoning": "fine"} ||V^=^V|| suffix'
assert parse_score_payload(raw)["score"] == [7, 8]
assert parse_score_payload(wrapped)["score"] == [6]
def test_parse_score_payload_handles_qwen_vl_nested_score_dicts():
qwen_style = '{"score": [{"naturalness": 8, "artifact_free": 7}], "reasoning": "good quality"}'
assert parse_score_payload(qwen_style)["score"] == [8, 7]
nested_values = '{"score": [{"score": 9}, {"score": 6}], "reasoning": "ok"}'
assert parse_score_payload(nested_values)["score"] == [9, 6]
def test_summarize_gedit_generated_records_groups_by_task_and_language():
records = []
for group in GEDIT_GROUPS[:2]:
records.append(
{
"task_type": group,
"instruction_language": "en",
"key": f"{group}_en",
"output_path": f"{group}_en.png",
}
)
records.append(
{
"task_type": group,
"instruction_language": "cn",
"key": f"{group}_cn",
"output_path": f"{group}_cn.png",
}
)
summary = summarize_gedit_generated_records(records)
assert summary["count"] == 4
assert summary["by_task"][GEDIT_GROUPS[0]]["count"] == 2
assert summary["by_language"]["en"]["count"] == 2
assert summary["by_language"]["cn"]["samples"] == [
f"{GEDIT_GROUPS[0]}_cn",
f"{GEDIT_GROUPS[1]}_cn",
]
def test_select_balanced_gedit_rows_limits_each_group_independently():
rows = []
for idx in range(12):
rows.append(
{
"task_type": "background_change",
"instruction_language": "en",
"key": f"background_change_{idx}",
}
)
for idx in range(7):
rows.append(
{
"task_type": "color_alter",
"instruction_language": "en",
"key": f"color_alter_{idx}",
}
)
selected = select_balanced_gedit_rows(
rows,
task_type="all",
instruction_language="en",
samples_per_group=10,
)
selected_background = [row for row in selected if row["task_type"] == "background_change"]
selected_color = [row for row in selected if row["task_type"] == "color_alter"]
assert len(selected_background) == 10
assert len(selected_color) == 7
assert selected_background[0]["key"] == "background_change_0"
assert selected_background[-1]["key"] == "background_change_9"
def test_select_balanced_gedit_rows_balances_languages_when_all_requested():
rows = []
for idx in range(10):
rows.append(
{
"task_type": "background_change",
"instruction_language": "cn",
"key": f"background_change_cn_{idx}",
}
)
for idx in range(10):
rows.append(
{
"task_type": "background_change",
"instruction_language": "en",
"key": f"background_change_en_{idx}",
}
)
selected = select_balanced_gedit_rows(
rows,
task_type="all",
instruction_language="all",
samples_per_group=10,
)
selected_background = [row for row in selected if row["task_type"] == "background_change"]
assert len(selected_background) == 10
assert sum(1 for row in selected_background if row["instruction_language"] == "en") == 5
assert sum(1 for row in selected_background if row["instruction_language"] == "cn") == 5
def test_infer_model_name_uses_last_path_segment():
assert infer_model_name("/workspace/models/Qwen/Qwen-Image-Edit") == "Qwen-Image-Edit"
def test_resolve_model_name_prefers_explicit_value_then_model_then_output_root(tmp_path: Path):
assert (
resolve_model_name(
model_name="explicit_name",
model="/workspace/models/Qwen/Qwen-Image-Edit",
)
== "explicit_name"
)
assert (
resolve_model_name(
model_name=None,
model="/workspace/models/Qwen/Qwen-Image-Edit",
)
== "Qwen-Image-Edit"
)
output_root = tmp_path / "results"
(output_root / "qwen_image_edit").mkdir(parents=True)
assert resolve_model_name(model_name=None, output_root=output_root) == "qwen_image_edit"
def test_resolve_gedit_split_accepts_dataset_dict_like_input():
train_rows = [{"key": "a"}]
dataset = {"train": train_rows}
assert _resolve_gedit_split(dataset) == train_rows
def test_resolve_gedit_split_accepts_dataset_like_input():
rows = [{"key": "a"}]
assert _resolve_gedit_split(rows) == rows
def test_load_gedit_dataset_uses_load_from_disk_for_saved_dataset(monkeypatch, tmp_path: Path):
(tmp_path / "state.json").write_text("{}", encoding="utf-8")
(tmp_path / "dataset_info.json").write_text("{}", encoding="utf-8")
captured = {}
def fake_load_dataset(path):
captured["load_dataset"] = path
return "load_dataset"
def fake_load_from_disk(path):
captured["load_from_disk"] = path
return "load_from_disk"
monkeypatch.setattr(
"benchmarks.accuracy.image_to_image.gedit_bench._require_datasets",
lambda: (fake_load_dataset, fake_load_from_disk),
)
result = _load_gedit_dataset(str(tmp_path))
assert result == "load_from_disk"
assert captured["load_from_disk"] == str(tmp_path)
assert "load_dataset" not in captured
def test_load_gedit_dataset_uses_load_dataset_for_local_snapshot_path(monkeypatch, tmp_path: Path):
(tmp_path / "README.md").write_text("dataset repo snapshot", encoding="utf-8")
captured = {}
def fake_load_dataset(path):
captured["load_dataset"] = path
return "load_dataset"
def fake_load_from_disk(path):
captured["load_from_disk"] = path
return "load_from_disk"
monkeypatch.setattr(
"benchmarks.accuracy.image_to_image.gedit_bench._require_datasets",
lambda: (fake_load_dataset, fake_load_from_disk),
)
result = _load_gedit_dataset(str(tmp_path))
assert result == "load_dataset"
assert captured["load_dataset"] == str(tmp_path)
assert "load_from_disk" not in captured
def test_gedit_runner_generate_skips_failed_samples(monkeypatch, tmp_path: Path):
rows = [
{"key": "ok", "task_type": "background_change", "instruction_language": "en"},
{"key": "bad", "task_type": "background_change", "instruction_language": "en"},
]
monkeypatch.setattr("benchmarks.accuracy.image_to_image.gedit_bench._load_gedit_dataset", lambda ref: rows)
runner = GEditBenchRunner(
dataset_ref="dataset",
output_root=tmp_path,
base_url="http://127.0.0.1:8093",
model="model",
)
def fake_generate_one(self, model_name, item):
if item["key"] == "bad":
raise RuntimeError("boom")
return {
"key": item["key"],
"task_type": item["task_type"],
"instruction_language": item["instruction_language"],
}
monkeypatch.setattr(GEditBenchRunner, "_generate_one", fake_generate_one)
outputs = runner.generate(model_name="demo", workers=1)
assert outputs == [{"key": "ok", "task_type": "background_change", "instruction_language": "en"}]
def test_gedit_runner_uses_tqdm_progress(monkeypatch, tmp_path: Path):
rows = [
{"key": "one", "task_type": "background_change", "instruction_language": "en"},
{"key": "two", "task_type": "background_change", "instruction_language": "en"},
]
updates = []
monkeypatch.setattr("benchmarks.accuracy.image_to_image.gedit_bench._load_gedit_dataset", lambda ref: rows)
runner = GEditBenchRunner(
dataset_ref="dataset",
output_root=tmp_path,
base_url="http://127.0.0.1:8093",
model="model",
)
def fake_generate_one(self, model_name, item):
return {
"key": item["key"],
"task_type": item["task_type"],
"instruction_language": item["instruction_language"],
}
monkeypatch.setattr(GEditBenchRunner, "_generate_one", fake_generate_one)
class FakeTqdm:
def __init__(self, total, desc, unit):
self.total = total
self.desc = desc
self.unit = unit
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def update(self, value):
updates.append(value)
monkeypatch.setattr("benchmarks.accuracy.image_to_image.gedit_bench.tqdm", FakeTqdm)
runner.generate(model_name="demo", workers=1)
assert updates == [1, 1]
def test_gedit_evaluator_skips_failed_samples(monkeypatch, tmp_path: Path):
rows = [
{"key": "ok", "task_type": "background_change", "instruction_language": "en"},
{"key": "bad", "task_type": "background_change", "instruction_language": "en"},
]
monkeypatch.setattr("benchmarks.accuracy.image_to_image.gedit_bench._load_gedit_dataset", lambda ref: rows)
evaluator = GEditBenchEvaluator(dataset_ref="dataset", output_root=tmp_path / "results", scorer=object())
def fake_evaluate_one(self, model_name, item):
if item["key"] == "bad":
raise RuntimeError("boom")
return {
"key": item["key"],
"task_type": item["task_type"],
"edited_image": "ok.png",
"instruction": "edit",
"semantics_score": 8.0,
"quality_score": 7.0,
"overall_score": math.sqrt(56.0),
"intersection_exist": True,
"instruction_language": item["instruction_language"],
}
monkeypatch.setattr(GEditBenchEvaluator, "_evaluate_one", fake_evaluate_one)
monkeypatch.setattr(
"benchmarks.accuracy.image_to_image.gedit_bench._utc_timestamp",
lambda: "20260325T120000Z",
)
payload = evaluator.evaluate(
model_name="demo",
save_dir=tmp_path / "scores",
instruction_language="en",
workers=1,
)
assert len(payload["results"]) == 1
assert payload["results"][0]["key"] == "ok"
assert payload["summary"]["overall"]["count"] == 1
assert Path(payload["csv_path"]).name == "demo_all_en_vie_score.csv"
assert Path(payload["summary_path"]).name == "demo_all_en_summary.json"
assert Path(payload["timestamped_csv_path"]).name == "demo_all_en_vie_score_20260325T120000Z.csv"
assert Path(payload["timestamped_summary_path"]).name == "demo_all_en_summary_20260325T120000Z.json"
assert Path(payload["timestamped_csv_path"]).exists()
assert Path(payload["timestamped_summary_path"]).exists()
def test_summarize_gedit_rows_computes_group_and_intersection_means():
rows = []
for group in GEDIT_GROUPS:
rows.append(
{
"task_type": group,
"instruction_language": "en",
"semantics_score": 8.0,
"quality_score": 9.0,
"intersection_exist": True,
}
)
rows.append(
{
"task_type": group,
"instruction_language": "en",
"semantics_score": 6.0,
"quality_score": 4.0,
"intersection_exist": False,
}
)
summary = summarize_gedit_rows(rows, language="en")
expected_overall = (math.sqrt(8.0 * 9.0) + math.sqrt(6.0 * 4.0)) / 2
assert math.isclose(summary["overall"]["Q_SC"], 7.0)
assert math.isclose(summary["overall"]["Q_PQ"], 6.5)
assert math.isclose(summary["overall"]["Q_O"], expected_overall)
assert math.isclose(summary["intersection"]["Q_SC"], 8.0)
def test_summarize_gedit_rows_uses_macro_average_across_groups():
rows = []
for idx in range(10):
rows.append(
{
"task_type": "background_change",
"instruction_language": "en",
"semantics_score": 10.0,
"quality_score": 10.0,
"intersection_exist": True,
}
)
for group in GEDIT_GROUPS[1:]:
rows.append(
{
"task_type": group,
"instruction_language": "en",
"semantics_score": 1.0,
"quality_score": 1.0,
"intersection_exist": True,
}
)
summary = summarize_gedit_rows_with_backbone(rows, language="en")
expected_macro = (10.0 + 10.0 * 1.0) / 11
assert math.isclose(summary["overall"]["Q_SC"], expected_macro)
assert math.isclose(summary["overall"]["Q_O"], expected_macro)
assert math.isclose(summary["by_group"]["background_change"]["Q_SC"], 10.0)
def test_summarize_gedit_rows_with_all_language_splits_en_and_cn():
rows = []
for group in GEDIT_GROUPS:
rows.append(
{
"task_type": group,
"instruction_language": "en",
"semantics_score": 8.0,
"quality_score": 6.0,
"intersection_exist": True,
}
)
rows.append(
{
"task_type": group,
"instruction_language": "cn",
"semantics_score": 4.0,
"quality_score": 2.0,
"intersection_exist": True,
}
)
summary = summarize_gedit_rows_with_backbone(rows, language="all")
assert set(summary["languages"]) == {"en", "cn"}
assert math.isclose(summary["languages"]["en"]["overall"]["Q_SC"], 8.0)
assert math.isclose(summary["languages"]["en"]["overall"]["Q_PQ"], 6.0)
assert math.isclose(summary["languages"]["cn"]["overall"]["Q_O"], math.sqrt(8.0))