From 0b1fbdba15a299ab07032b8afa4cfa029073d936 Mon Sep 17 00:00:00 2001 From: Mick Date: Thu, 30 Apr 2026 12:26:10 +0800 Subject: [PATCH] [diffusion] CI: change ground truth upload path and improve publish script (#24120) --- .github/workflows/diffusion-ci-gt-gen.yml | 14 ++- .../test/server/test_server_common.py | 4 +- .../sglang/multimodal_gen/test/test_utils.py | 3 +- .../utils/diffusion/publish_diffusion_gt.py | 86 ++++++++++++++----- 4 files changed, 79 insertions(+), 28 deletions(-) diff --git a/.github/workflows/diffusion-ci-gt-gen.yml b/.github/workflows/diffusion-ci-gt-gen.yml index b517a7a1a..58a0b0fba 100644 --- a/.github/workflows/diffusion-ci-gt-gen.yml +++ b/.github/workflows/diffusion-ci-gt-gen.yml @@ -14,7 +14,12 @@ on: default: '' type: string output_name: - description: 'Custom output folder name (also used for artifact names and publish target dir). Leave empty to use defaults.' + description: 'Custom local output/artifact folder name. Leave empty to use defaults.' + required: false + default: '' + type: string + publish_target_dir: + description: 'Remote target directory in sglang-ci-data. Leave empty to use diffusion-ci/consistency_gt/sglang_generated.' required: false default: '' type: string @@ -36,6 +41,7 @@ env: SGLANG_IS_IN_CI: true SGLANG_CUDA_COREDUMP: "1" OUTPUT_NAME: ${{ inputs.output_name || 'diffusion-ci-outputs' }} + PUBLISH_TARGET_DIR: ${{ inputs.publish_target_dir || 'diffusion-ci/consistency_gt/sglang_generated' }} jobs: compute-diffusion-partitions: @@ -136,7 +142,7 @@ jobs: run: | python scripts/ci/utils/diffusion/publish_diffusion_gt.py \ --source-dir python/${{ env.OUTPUT_NAME }} \ - ${{ inputs.output_name != '' && format('--target-dir diffusion-ci/{0}', inputs.output_name) || '' }} + --target-dir "${{ env.PUBLISH_TARGET_DIR }}" multimodal-diffusion-gen-2gpu: needs: compute-diffusion-partitions @@ -202,7 +208,7 @@ jobs: run: | python scripts/ci/utils/diffusion/publish_diffusion_gt.py \ --source-dir python/${{ env.OUTPUT_NAME }} \ - ${{ inputs.output_name != '' && format('--target-dir diffusion-ci/{0}', inputs.output_name) || '' }} + --target-dir "${{ env.PUBLISH_TARGET_DIR }}" multimodal-diffusion-gen-b200: needs: compute-diffusion-partitions @@ -268,4 +274,4 @@ jobs: run: | python scripts/ci/utils/diffusion/publish_diffusion_gt.py \ --source-dir python/${{ env.OUTPUT_NAME }} \ - ${{ inputs.output_name != '' && format('--target-dir diffusion-ci/{0}', inputs.output_name) || '' }} + --target-dir "${{ env.PUBLISH_TARGET_DIR }}" diff --git a/python/sglang/multimodal_gen/test/server/test_server_common.py b/python/sglang/multimodal_gen/test/server/test_server_common.py index 9338e3f33..ae941b51d 100644 --- a/python/sglang/multimodal_gen/test/server/test_server_common.py +++ b/python/sglang/multimodal_gen/test/server/test_server_common.py @@ -561,13 +561,13 @@ Consider updating perf_baselines.json with the snippets below: --- MISSING GROUND TRUTH DETECTED --- GT image(s) not found for '{case.id}'. -Add the expected file(s) to sglang-ci-data in diffusion-ci/consistency_gt/ with naming (n=num_gpus). +Add the expected file(s) to sglang-ci-data in diffusion-ci/consistency_gt/sglang_generated/ with naming (n=num_gpus). Image: {case.id}_{{n}}gpu. (ext from output_format: png, jpg, webp) Video: {case.id}_{{n}}gpu_frame_0.png, {case.id}_{{n}}gpu_frame_mid.png, {case.id}_{{n}}gpu_frame_last.png For this case, expected file(s): {names} -Repository: https://github.com/sglang-bot/sglang-ci-data (path: diffusion-ci/consistency_gt/) +Repository: https://github.com/sglang-bot/sglang-ci-data (path: diffusion-ci/consistency_gt/sglang_generated/) (Optional) Per-case override in consistency_threshold.json: "cases": {{ diff --git a/python/sglang/multimodal_gen/test/test_utils.py b/python/sglang/multimodal_gen/test/test_utils.py index 3eec717b0..d9cc0ab89 100644 --- a/python/sglang/multimodal_gen/test/test_utils.py +++ b/python/sglang/multimodal_gen/test/test_utils.py @@ -33,11 +33,10 @@ logger = init_logger(__name__) SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE = "https://raw.githubusercontent.com/sglang-bot/sglang-ci-data/main/diffusion-ci/consistency_gt/official_generated" SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE = "https://raw.githubusercontent.com/sglang-bot/sglang-ci-data/main/diffusion-ci/consistency_gt/sglang_generated" -SGL_TEST_FILES_CONSISTENCY_GT_BASE = "https://raw.githubusercontent.com/sglang-bot/sglang-ci-data/main/diffusion-ci/consistency_gt" +SGL_TEST_FILES_CONSISTENCY_GT_BASE = SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE SGL_TEST_FILES_CONSISTENCY_GT_BASES = ( SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE, SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE, - SGL_TEST_FILES_CONSISTENCY_GT_BASE, ) CONSISTENCY_THRESHOLD_JSON_PATH = ( Path(__file__).resolve().parent / "server" / "consistency_threshold.json" diff --git a/scripts/ci/utils/diffusion/publish_diffusion_gt.py b/scripts/ci/utils/diffusion/publish_diffusion_gt.py index ff0f3db27..7dae95d6f 100644 --- a/scripts/ci/utils/diffusion/publish_diffusion_gt.py +++ b/scripts/ci/utils/diffusion/publish_diffusion_gt.py @@ -4,9 +4,12 @@ via the GitHub API (same pattern as publish_traces.py). """ import argparse +import hashlib +import json import os import sys from pathlib import Path +from urllib.error import HTTPError # Reuse GitHub API helpers from publish_traces. # Support both direct script execution and package-style imports. @@ -19,6 +22,7 @@ if __package__: get_tree_sha, is_permission_error, is_rate_limit_error, + make_github_request, update_branch_ref, verify_token_permissions, ) @@ -32,6 +36,7 @@ else: get_tree_sha, is_permission_error, is_rate_limit_error, + make_github_request, update_branch_ref, verify_token_permissions, ) @@ -39,7 +44,7 @@ else: REPO_OWNER = "sglang-bot" REPO_NAME = "sglang-ci-data" BRANCH = "main" -DEFAULT_TARGET_DIR = "diffusion-ci/consistency_gt" +DEFAULT_TARGET_DIR = "diffusion-ci/consistency_gt/sglang_generated" IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"} @@ -61,6 +66,38 @@ def collect_images(source_dir, target_dir): return files +def git_blob_sha(content): + header = f"blob {len(content)}\0".encode() + return hashlib.sha1(header + content).hexdigest() + + +def get_remote_blob_shas(repo_owner, repo_name, target_dir, token): + url = ( + f"https://api.github.com/repos/{repo_owner}/{repo_name}/contents/" + f"{target_dir}?ref={BRANCH}" + ) + try: + response = make_github_request(url, token) + except HTTPError as e: + if e.code == 404: + return {} + raise + entries = json.loads(response) + return { + item["path"]: item["sha"] + for item in entries + if item.get("type") == "file" and "sha" in item + } + + +def filter_changed_files(files, remote_blob_shas): + return [ + (path, content) + for path, content in files + if remote_blob_shas.get(path) != git_blob_sha(content) + ] + + def publish(source_dir, target_dir=None): target_dir = target_dir or DEFAULT_TARGET_DIR token = os.getenv("GITHUB_TOKEN") @@ -86,37 +123,48 @@ def publish(source_dir, target_dir=None): print("Token permission verification failed.") sys.exit(1) - # Create blobs - try: - tree_items = create_blobs(REPO_OWNER, REPO_NAME, files_to_upload, token) - except Exception as e: - if is_rate_limit_error(e): - print("Rate-limited during blob creation, skipping.") - return - if is_permission_error(e): - print( - f"ERROR: Token lacks write permission to {REPO_OWNER}/{REPO_NAME}. " - "Update GH_PAT_FOR_NIGHTLY_CI_DATA with a token that has contents:write." - ) - sys.exit(1) - raise - # Commit with retry (handle concurrent pushes) max_retries = 5 for attempt in range(max_retries): try: branch_sha = get_branch_sha(REPO_OWNER, REPO_NAME, BRANCH, token) tree_sha = get_tree_sha(REPO_OWNER, REPO_NAME, branch_sha, token) + remote_blob_shas = get_remote_blob_shas( + REPO_OWNER, REPO_NAME, target_dir, token + ) + changed_files = filter_changed_files(files_to_upload, remote_blob_shas) + if not changed_files: + print("No image changes to publish.") + return + + try: + tree_items = create_blobs(REPO_OWNER, REPO_NAME, changed_files, token) + except Exception as e: + if is_rate_limit_error(e): + print("Rate-limited during blob creation, skipping.") + return + if is_permission_error(e): + print( + f"ERROR: Token lacks write permission to {REPO_OWNER}/{REPO_NAME}. " + "Update GH_PAT_FOR_NIGHTLY_CI_DATA with a token that has contents:write." + ) + sys.exit(1) + raise + new_tree_sha = create_tree( REPO_OWNER, REPO_NAME, tree_sha, tree_items, token ) - commit_msg = f"diffusion-ci: update consistency_gt images ({len(files_to_upload)} files) [automated]" + if new_tree_sha == tree_sha: + print("No tree changes to publish.") + return + + commit_msg = f"diffusion-ci: update images in {target_dir} ({len(changed_files)} files) [automated]" commit_sha = create_commit( REPO_OWNER, REPO_NAME, new_tree_sha, branch_sha, commit_msg, token ) update_branch_ref(REPO_OWNER, REPO_NAME, BRANCH, commit_sha, token) print( - f"Successfully pushed {len(files_to_upload)} images (commit {commit_sha[:10]})" + f"Successfully pushed {len(changed_files)} changed images (commit {commit_sha[:10]})" ) return except Exception as e: @@ -134,8 +182,6 @@ def publish(source_dir, target_dir=None): elif "Object does not exist" in e.error_body: retryable = True - from urllib.error import HTTPError - if isinstance(e, HTTPError) and e.code in [422, 500, 502, 503, 504]: retryable = True