diff --git a/.github/workflows/nightly-test-amd-rocm720.yml b/.github/workflows/nightly-test-amd-rocm720.yml index 80d6a1afa..096d9a242 100644 --- a/.github/workflows/nightly-test-amd-rocm720.yml +++ b/.github/workflows/nightly-test-amd-rocm720.yml @@ -643,6 +643,46 @@ jobs: echo "$(> $GITHUB_STEP_SUMMARY || true exit ${TEST_EXIT_CODE:-0} + # ============================================== MI30x ROCm 7.2 Diffusion Tests ============================================== + # 1-GPU Z-Image-Turbo (Diffusion T2I) ROCm 7.2 + nightly-1-gpu-zimage-turbo-rocm720: + if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (inputs.job_filter == '' || inputs.job_filter == 'all' || inputs.job_filter == 'nightly-1-gpu-zimage-turbo-rocm720') + runs-on: linux-mi325-1gpu-sglang + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Setup docker (ROCm 7.2) + run: | + touch github_summary.md + bash scripts/ci/amd/amd_ci_start_container.sh --rocm-version rocm720 + env: + GITHUB_WORKSPACE: ${{ github.workspace }} + + - name: Install dependencies + run: bash scripts/ci/amd/amd_ci_install_dependency.sh + + - name: Z-Image-Turbo Diffusion Test ROCm 7.2 (1-GPU) + timeout-minutes: 45 + run: | + bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout \ + -e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \ + -e SGLANG_DIFFUSION_ARTIFACT_DIR="/sglang-checkout/diffusion-artifacts" \ + pytest test/registered/amd/test_zimage_turbo.py -v -s ${{ inputs.continue_on_error && '|| true' || '' }} || TEST_EXIT_CODE=$? + echo "$(> $GITHUB_STEP_SUMMARY || true + exit ${TEST_EXIT_CODE:-0} + + - name: Upload generated images + if: always() + uses: actions/upload-artifact@v4 + with: + name: zimage-turbo-outputs-rocm720 + path: diffusion-artifacts/ + if-no-files-found: ignore + retention-days: 30 + # ============================================== MI35x ROCm 7.2 Tests ============================================== # MI35x 1-GPU ROCm 7.2 tests nightly-test-1-gpu-mi35x-rocm720: @@ -1251,6 +1291,8 @@ jobs: - nightly-8-gpu-qwen35-rocm720 - nightly-8-gpu-glm5-rocm720 - nightly-8-gpu-minimax-m25-rocm720 + # MI30x ROCm 7.2 Diffusion Tests + - nightly-1-gpu-zimage-turbo-rocm720 # MI35x ROCm 7.2 jobs - nightly-test-1-gpu-mi35x-rocm720 - nightly-accuracy-8-gpu-mi35x-rocm720 diff --git a/.github/workflows/nightly-test-amd.yml b/.github/workflows/nightly-test-amd.yml index 54e0202cc..f1b48e757 100644 --- a/.github/workflows/nightly-test-amd.yml +++ b/.github/workflows/nightly-test-amd.yml @@ -646,6 +646,46 @@ jobs: echo "$(> $GITHUB_STEP_SUMMARY || true exit ${TEST_EXIT_CODE:-0} + # ============================================== MI30x Diffusion Tests ============================================== + # 1-GPU Z-Image-Turbo (Diffusion T2I) + nightly-1-gpu-zimage-turbo: + if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (inputs.job_filter == '' || inputs.job_filter == 'all' || inputs.job_filter == 'nightly-1-gpu-zimage-turbo') + runs-on: linux-mi325-1gpu-sglang + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Setup docker + run: | + touch github_summary.md + bash scripts/ci/amd/amd_ci_start_container.sh + env: + GITHUB_WORKSPACE: ${{ github.workspace }} + + - name: Install dependencies + run: bash scripts/ci/amd/amd_ci_install_dependency.sh + + - name: Z-Image-Turbo Diffusion Test (1-GPU) + timeout-minutes: 45 + run: | + bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout \ + -e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \ + -e SGLANG_DIFFUSION_ARTIFACT_DIR="/sglang-checkout/diffusion-artifacts" \ + pytest test/registered/amd/test_zimage_turbo.py -v -s ${{ inputs.continue_on_error && '|| true' || '' }} || TEST_EXIT_CODE=$? + echo "$(> $GITHUB_STEP_SUMMARY || true + exit ${TEST_EXIT_CODE:-0} + + - name: Upload generated images + if: always() + uses: actions/upload-artifact@v4 + with: + name: zimage-turbo-outputs + path: diffusion-artifacts/ + if-no-files-found: ignore + retention-days: 30 + # ============================================== MI35x Tests ============================================== # MI35x 1-GPU tests - platform-agnostic tests that may work on CDNA4 (gfx950) nightly-test-1-gpu-mi35x: @@ -1257,6 +1297,8 @@ jobs: - nightly-8-gpu-qwen35 - nightly-8-gpu-glm5 - nightly-8-gpu-minimax-m25 + # MI30x Diffusion Tests + - nightly-1-gpu-zimage-turbo # MI35x jobs - nightly-test-1-gpu-mi35x - nightly-accuracy-8-gpu-mi35x diff --git a/test/registered/amd/test_zimage_turbo.py b/test/registered/amd/test_zimage_turbo.py new file mode 100644 index 000000000..3e5d8c4ea --- /dev/null +++ b/test/registered/amd/test_zimage_turbo.py @@ -0,0 +1,150 @@ +"""AMD nightly test for Z-Image-Turbo diffusion model (text-to-image).""" + +import io +import logging +import os + +import pytest + +from sglang.multimodal_gen.test.server.test_server_common import ( # noqa: F401 + DiffusionServerBase, + diffusion_server, +) +from sglang.multimodal_gen.test.server.test_server_utils import ( + ServerContext, + get_generate_fn, +) +from sglang.multimodal_gen.test.server.testcase_configs import ( + DiffusionSamplingParams, + DiffusionServerArgs, + DiffusionTestCase, +) +from sglang.test.ci.ci_register import register_amd_ci + +logger = logging.getLogger(__name__) + +register_amd_ci(est_time=1800, suite="nightly-amd-1-gpu-zimage-turbo", nightly=True) + +AMD_ZIMAGE_CASES = [ + DiffusionTestCase( + "zimage_image_t2i", + DiffusionServerArgs(model_path="Tongyi-MAI/Z-Image-Turbo", modality="image"), + DiffusionSamplingParams( + prompt="Doraemon is eating dorayaki", + output_size="1024x1024", + ), + ), +] + +CLIP_SCORE_THRESHOLD = 0.20 + + +ARTIFACT_DIR = os.environ.get( + "SGLANG_DIFFUSION_ARTIFACT_DIR", "/tmp/diffusion-artifacts" +) + + +def _save_image_and_write_summary( + case_id: str, prompt: str, image_bytes: bytes, clip_score: float | None = None +): + """Save generated image to artifact dir and write summary.""" + ext = "jpg" if image_bytes[:2] == b"\xff\xd8" else "png" + os.makedirs(ARTIFACT_DIR, exist_ok=True) + img_path = os.path.join(ARTIFACT_DIR, f"{case_id}.{ext}") + with open(img_path, "wb") as f: + f.write(image_bytes) + logger.info("Saved image artifact: %s (%d bytes)", img_path, len(image_bytes)) + + summary_file = os.environ.get("GITHUB_STEP_SUMMARY") + if not summary_file: + return + + clip_line = "" + if clip_score is not None: + status = "PASS" if clip_score >= CLIP_SCORE_THRESHOLD else "FAIL" + clip_line = f"| CLIP Score | {clip_score:.4f} ({status}, threshold: {CLIP_SCORE_THRESHOLD}) |\n" + + md = ( + f"### Z-Image-Turbo — `{case_id}`\n\n" + f"| | |\n|---|---|\n" + f"| Prompt | {prompt} |\n" + f"| Size | {len(image_bytes):,} bytes |\n" + f"{clip_line}" + f"| Artifact | `{case_id}.{ext}` (download from Artifacts section above) |\n\n" + ) + + with open(summary_file, "a") as f: + f.write(md) + + +def _compute_clip_score(image_bytes: bytes, prompt: str) -> float | None: + """Compute CLIP cosine similarity between the image and prompt.""" + try: + import torch + from PIL import Image + from transformers import CLIPModel, CLIPProcessor + + model_name = "openai/clip-vit-base-patch32" + processor = CLIPProcessor.from_pretrained(model_name) + model = CLIPModel.from_pretrained(model_name) + model.eval() + + image = Image.open(io.BytesIO(image_bytes)).convert("RGB") + inputs = processor(text=[prompt], images=image, return_tensors="pt") + + with torch.no_grad(): + outputs = model(**inputs) + score = outputs.logits_per_image.item() / 100.0 + + logger.info("CLIP score for '%s': %.4f", prompt, score) + return score + except Exception as e: + logger.warning("CLIP score computation failed: %s", e) + return None + + +class TestZImageTurboAMD(DiffusionServerBase): + """AMD nightly test for Z-Image-Turbo text-to-image generation.""" + + @classmethod + def teardown_class(cls): + try: + super().teardown_class() + except AttributeError: + pass + + @pytest.fixture(params=AMD_ZIMAGE_CASES, ids=lambda c: c.id) + def case(self, request) -> DiffusionTestCase: + return request.param + + def test_diffusion_generation( + self, + case: DiffusionTestCase, + diffusion_server: ServerContext, + ): + generate_fn = get_generate_fn( + model_path=case.server_args.model_path, + modality=case.server_args.modality, + sampling_params=case.sampling_params, + ) + + perf_record, content = self.run_and_collect( + diffusion_server, case.id, generate_fn + ) + + self._validate_and_record(case, perf_record) + self._test_v1_models_endpoint(diffusion_server, case) + + prompt = case.sampling_params.prompt or "" + clip_score = _compute_clip_score(content, prompt) + + if clip_score is not None: + logger.info( + "CLIP score: %.4f (threshold: %.2f)", clip_score, CLIP_SCORE_THRESHOLD + ) + assert clip_score >= CLIP_SCORE_THRESHOLD, ( + f"CLIP score {clip_score:.4f} below threshold {CLIP_SCORE_THRESHOLD} " + f"for prompt '{prompt}'" + ) + + _save_image_and_write_summary(case.id, prompt, content, clip_score) diff --git a/test/run_suite.py b/test/run_suite.py index d4092dd73..6805160cc 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -76,6 +76,7 @@ NIGHTLY_SUITES = { "nightly-amd", "nightly-amd-1-gpu", "nightly-amd-1-gpu-mi35x", + "nightly-amd-1-gpu-zimage-turbo", "nightly-amd-8-gpu", "nightly-amd-vlm", # MI35x 8-GPU suite (different model configs)