Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eb382fa
z-image support npu
Jan 15, 2026
654c530
Merge branch 'main' into main
luren55 Jan 15, 2026
e4bbc6d
Update attention_dispatch.py
luren55 Jan 22, 2026
677c0ff
attention_dispatch.py backup
luren55 Jan 22, 2026
1dc7cc5
merge RopeEmbedderNPU into RopeEmbedder
luren55 Jan 26, 2026
5a5c479
Merge branch 'main' into main
sayakpaul Jan 28, 2026
b7d1325
Apply style fixes
github-actions[bot] Jan 28, 2026
9aabdc7
Merge branch 'huggingface:main' into main
luren55 Apr 29, 2026
95cfd71
chore: scaffold ci_runners directory structure
Apr 29, 2026
fbd1b98
feat: add runner_utils with timer, validation, precision comparison
Apr 29, 2026
0879fde
feat: add YAML configs for 8 pipeline classes and 12 model variants
Apr 29, 2026
88ed608
feat: add run_pipeline.py main driver
Apr 29, 2026
a87d4a5
feat: add report.py for Markdown summary
Apr 29, 2026
92493de
feat: add issue_bot.py for automated GitHub Issue creation
Apr 29, 2026
5d97219
feat: add model_ci.yml GitHub Actions workflow
Apr 29, 2026
8dc1601
fix: pass model_id to load_pipeline for modelscope/hf download
May 6, 2026
236acae
fix: switch Qwen variants to modelscope backend
May 6, 2026
5181860
fix: downgrade checkout/upload-artifact to v4 for node20 compat
May 6, 2026
e75b2d9
fix: remove container block - runner already inside container
May 6, 2026
2b26a72
fix: UnboundLocalError in issue_bot.py date_str reference
May 6, 2026
dafeb65
fix: remove checkout, use local paths; fix import in run_pipeline.py
May 6, 2026
e0dd1e2
fix: add missing model_id arg in main loop load_pipeline call
May 6, 2026
5de2329
fix: remove ltx2, boundary_ratio/expand_timesteps from extra_params, …
May 6, 2026
2995cd0
fix: reduce resolution, add image input for I2V/VACE/Edit, gc.collect…
May 6, 2026
f8d8a8d
fix: exclude image from JSON serializable params
May 6, 2026
5c67c2f
fix: reduce WanPipeline params, remove image from VACE, add NPU expan…
May 6, 2026
9762372
Merge branch 'main' into main
yiyixuxu May 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/ci_runners/configs/qwen_image/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pipeline_class: QwenImagePipeline
module: diffusers.pipelines.qwenimage.pipeline_qwenimage

params_grid:
- name: "landscape_50steps_single"
width: 1344
height: 768
num_inference_steps: 50
true_cfg_scale: 4.0
parallel: "single"

prompt: "A 20-year-old East Asian girl with delicate, charming features and large, bright brown eyes, cheerful expression, wavy long hair tied in twin ponytails, fair skin, light makeup, wearing a modern cute dress in bright soft colors, standing indoors at an anime convention, casual iPhone snapshot style."
negative_prompt: "低分辨率,低画质,肢体畸形,手指畸形,画面过饱和,蜡像感,人脸无细节,过度光滑,画面具有AI感。构图混乱。文字模糊,扭曲。"
reference_config: "landscape_50steps_single"
4 changes: 4 additions & 0 deletions .github/ci_runners/configs/qwen_image/variants/2512.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Qwen/Qwen-Image-2512
weight_path: /home/weights/Qwen-Image-2512
backend: modelscope
extra_params: {}
15 changes: 15 additions & 0 deletions .github/ci_runners/configs/qwen_image_edit/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pipeline_class: QwenImageEditPipeline
module: diffusers.pipelines.qwenimage.pipeline_qwenimage_edit

params_grid:
- name: "square_50steps_single"
width: 1024
height: 1024
num_inference_steps: 50
true_cfg_scale: 4.0
parallel: "single"
image: ".github/ci_runners/test_data/sample_input.png"

prompt: "Transform this image into a watercolor painting style."
negative_prompt: "blurry, low quality, distorted."
reference_config: "square_50steps_single"
4 changes: 4 additions & 0 deletions .github/ci_runners/configs/qwen_image_edit/variants/2511.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Qwen/Qwen-Image-Edit-2511
weight_path: /home/weights/Qwen-Image-Edit-2511
backend: modelscope
extra_params: {}
16 changes: 16 additions & 0 deletions .github/ci_runners/configs/qwen_image_layered/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pipeline_class: QwenImageLayeredPipeline
module: diffusers.pipelines.qwenimage.pipeline_qwenimage_layered

params_grid:
- name: "default_50steps_single"
width: 1024
height: 1024
num_inference_steps: 50
true_cfg_scale: 4.0
layers: 4
resolution: 640
parallel: "single"

prompt: "A cute cat sitting on a wooden table."
negative_prompt: "blurry, low quality, distorted."
reference_config: "default_50steps_single"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Qwen/Qwen-Image-Layered
weight_path: /home/weights/Qwen-Image-Layered
backend: modelscope
extra_params: {}
16 changes: 16 additions & 0 deletions .github/ci_runners/configs/wan_i2v/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pipeline_class: WanImageToVideoPipeline
module: diffusers.pipelines.wan.pipeline_wan_i2v

params_grid:
- name: "small_30steps_single"
width: 480
height: 272
num_inference_steps: 30
guidance_scale: 5.0
num_frames: 33
parallel: "single"
image: ".github/ci_runners/test_data/sample_input.png"

prompt: "A cat walking on a beach at sunset, cinematic quality."
negative_prompt: "blurry, low quality, distorted."
reference_config: "small_30steps_single"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Wan-AI/Wan2.1-I2V-14B-480P-Diffusers
weight_path: /home/weights/Wan2.1-I2V-14B-480P-Diffusers
backend: modelscope
extra_params: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
model_id: Wan-AI/Wan2.2-I2V-A14B-Diffusers
weight_path: /home/weights/Wan2.2-I2V-A14B-Diffusers
backend: modelscope
extra_params:
boundary_ratio: 0.875
guidance_scale_2: 5.0
15 changes: 15 additions & 0 deletions .github/ci_runners/configs/wan_pipeline/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pipeline_class: WanPipeline
module: diffusers.pipelines.wan.pipeline_wan

params_grid:
- name: "small_30steps_single"
width: 640
height: 352
num_inference_steps: 30
guidance_scale: 5.0
num_frames: 49
parallel: "single"

prompt: "A cat walking on a beach at sunset, cinematic quality."
negative_prompt: "blurry, low quality, distorted."
reference_config: "small_30steps_single"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Wan-AI/Wan2.1-T2V-14B-Diffusers
weight_path: /home/weights/Wan2.1-T2V-14B-Diffusers
backend: modelscope
extra_params: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
model_id: Wan-AI/Wan2.2-T2V-A14B-Diffusers
weight_path: /home/weights/Wan2.2-T2V-A14B-Diffusers
backend: modelscope
extra_params:
boundary_ratio: 0.875
guidance_scale_2: 5.0
expand_timesteps: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Wan-AI/Wan2.2-TI2V-5B-Diffusers
weight_path: /home/weights/Wan2.2-TI2V-5B-Diffusers
backend: modelscope
extra_params: {}
15 changes: 15 additions & 0 deletions .github/ci_runners/configs/wan_vace/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pipeline_class: WanVACEPipeline
module: diffusers.pipelines.wan.pipeline_wan_vace

params_grid:
- name: "small_30steps_single"
width: 480
height: 272
num_inference_steps: 30
guidance_scale: 5.0
num_frames: 33
parallel: "single"

prompt: "A cat walking on a beach at sunset, cinematic quality."
negative_prompt: "blurry, low quality, distorted."
reference_config: "small_30steps_single"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Wan-AI/Wan2.1-FLF2V-14B-720P-diffusers
weight_path: /home/weights/Wan2.1-FLF2V-14B-720P-diffusers
backend: modelscope
extra_params: {}
4 changes: 4 additions & 0 deletions .github/ci_runners/configs/wan_vace/variants/vace_14b.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Wan-AI/Wan2.1-VACE-14B-diffusers
weight_path: /home/weights/Wan2.1-VACE-14B-diffusers
backend: modelscope
extra_params: {}
16 changes: 16 additions & 0 deletions .github/ci_runners/configs/z_image/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pipeline_class: ZImagePipeline
module: diffusers.pipelines.z_image.pipeline_z_image

params_grid:
- name: "landscape_50steps_single"
width: 1024
height: 1024
num_inference_steps: 50
guidance_scale: 5.0
cfg_normalization: false
cfg_truncation: 1.0
parallel: "single"

prompt: "A serene mountain lake at sunrise, hyperrealistic, 8k quality."
negative_prompt: ""
reference_config: "landscape_50steps_single"
4 changes: 4 additions & 0 deletions .github/ci_runners/configs/z_image/variants/default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
model_id: Tongyi-MAI/Z-Image
weight_path: /home/weights/Z-Image
backend: modelscope
extra_params: {}
131 changes: 131 additions & 0 deletions .github/ci_runners/issue_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import argparse
import json
import os
import re
import urllib.request
import urllib.error


def find_existing_issues(repo: str, token: str, title_prefix: str) -> list[dict]:
url = f"https://api.github.com/repos/{repo}/issues?state=open&labels=ci-failure"
req = urllib.request.Request(url)
req.add_header("Authorization", f"Bearer {token}")
req.add_header("Accept", "application/vnd.github+json")
req.add_header("User-Agent", "diffusers-ci-bot")

try:
with urllib.request.urlopen(req) as resp:
issues = json.loads(resp.read())
return [i for i in issues if title_prefix in i.get("title", "")]
except Exception:
return []


def create_issue(repo: str, token: str, title: str, body: str, label: str = "ci-failure") -> str:
url = f"https://api.github.com/repos/{repo}/issues"
data = json.dumps({"title": title, "body": body, "labels": [label]}).encode("utf-8")

req = urllib.request.Request(url, data=data, method="POST")
req.add_header("Authorization", f"Bearer {token}")
req.add_header("Accept", "application/vnd.github+json")
req.add_header("User-Agent", "diffusers-ci-bot")
req.add_header("Content-Type", "application/json")

with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
return result.get("html_url", "")


def classify_error(error_text: str) -> str:
if "out of memory" in error_text.lower() or "OOM" in error_text:
return ("**错误类型**: OOM (显存不足)\n\n"
"**建议**: 减小 `num_frames`、`width`/`height`,或启用 `enable_attention_slicing`")
if "connection" in error_text.lower() or "timeout" in error_text.lower():
return ("**错误类型**: 网络/连接异常\n\n"
"**建议**: 检查 HuggingFace/ModelScope 网络连通性,或切换到本地权重")
if "key" in error_text.lower() or "AttributeError" in error_text:
return ("**错误类型**: API 不兼容\n\n"
"**建议**: 检查 diffusers 版本与模型是否匹配,检查参数名是否正确(true_cfg_scale vs guidance_scale)")
if "import" in error_text.lower() or "ModuleNotFoundError" in error_text.lower():
return ("**错误类型**: 依赖缺失\n\n"
"**建议**: 检查 requirements,安装缺失的依赖包")
return ("**错误类型**: 未知\n\n"
"**建议**: 请查看上方错误日志进行人工排查")


def build_issue_title(pipeline: str, variant: str, date_str: str) -> str:
return f"[CI] {pipeline} / {variant} 运行失败 ({date_str})"


def build_issue_body(r: dict, run_url: str) -> str:
error_text = r.get("error", "无错误信息")
lines = [
"## 失败信息",
f"- **Pipeline**: {r['pipeline']}",
f"- **Variant**: {r['variant']}",
f"- **配置**: {r['config_name']}",
f"- **设备**: {r['device']} / {r['dtype']}",
f"- **并行策略**: {r['parallel']}",
"",
"## 修复建议",
classify_error(error_text),
"",
"## 错误日志",
"```",
error_text[:5000],
"```",
"",
f"## 完整日志",
f"[Actions Run]({run_url})" if run_url else run_url,
]
return "\n".join(lines)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--report", required=True, help="output directory containing all_results.json")
args = parser.parse_args()

repo = os.environ.get("GITHUB_REPOSITORY", "luren55/diffusers")
token = os.environ.get("GITHUB_TOKEN", "")
run_url = ""
server_url = os.environ.get("GITHUB_SERVER_URL", "https://github.com")
run_id = os.environ.get("GITHUB_RUN_ID", "")
if server_url and repo and run_id:
run_url = f"{server_url}/{repo}/actions/runs/{run_id}"

results_path = os.path.join(args.report, "all_results.json")
if not os.path.isfile(results_path):
print(f"ERROR: {results_path} not found")
return

with open(results_path, "r") as f:
results = json.load(f)

failed = [r for r in results if r["result"] and r["result"].get("status") == "failed"]

if not failed:
print("No failures, no issues to create.")
return

date_str = failed[0]["timestamp"][:10]

for r in failed:
title = build_issue_title(r["pipeline"], r["variant"], date_str)
title_prefix = f"[CI] {r['pipeline']} / {r['variant']}"

existing = find_existing_issues(repo, token, title_prefix)
if existing:
print(f"[SKIP] Issue already exists for {r['pipeline']} / {r['variant']}: {existing[0].get('html_url')}")
continue

body = build_issue_body(r, run_url)
try:
url = create_issue(repo, token, title, body)
print(f"[OK] Created issue: {url}")
except Exception as e:
print(f"[FAIL] Could not create issue: {e}")


if __name__ == "__main__":
main()
Empty file.
68 changes: 68 additions & 0 deletions .github/ci_runners/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import argparse
import json
import os
from datetime import datetime, timezone


def generate_report(results_path: str) -> str:
with open(results_path, "r") as f:
results = json.load(f)

passed = [r for r in results if r["result"] and r["result"].get("status") == "passed"]
failed = [r for r in results if r["result"] and r["result"].get("status") == "failed"]

lines = []
lines.append("# Diffusers Model CI Report")
lines.append(f"**Generated**: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}")
lines.append(f"**Total**: {len(results)} | **Passed**: {len(passed)} | **Failed**: {len(failed)}")
lines.append("")

if passed:
lines.append("## Passed")
lines.append("| Pipeline | Variant | Config | Parallel | Time (s) | PSNR | SSIM |")
lines.append("|---|---|---|---|---|---|---|")
for r in passed:
prec = r["result"].get("precision", {})
psnr = prec.get("psnr", "-") if isinstance(prec, dict) else "-"
ssim = prec.get("ssim", "-") if isinstance(prec, dict) else "-"
lines.append(
f"| {r['pipeline']} | {r['variant']} | {r['config_name']} | "
f"{r['parallel']} | {r['result'].get('inference_time_s', '-')} | "
f"{psnr} | {ssim} |"
)
lines.append("")

if failed:
lines.append("## Failed")
for r in failed:
lines.append(f"### {r['pipeline']} / {r['variant']} / {r['config_name']}")
lines.append(f"- **Parallel**: {r['parallel']}")
lines.append(f"- **Device**: {r['device']} / {r['dtype']}")
lines.append("```")
lines.append(r.get("error", "")[:2000])
lines.append("```")
lines.append("")
else:
lines.append("## All tests passed!")

return "\n".join(lines)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True, help="output directory containing all_results.json")
args = parser.parse_args()

results_path = os.path.join(args.output, "all_results.json")
report = generate_report(results_path)

report_path = os.path.join(args.output, "report.md")
with open(report_path, "w") as f:
f.write(report)

print(report)
print(f"\nReport written to {report_path}")


if __name__ == "__main__":
main()
Loading
Loading