From c7dff489667f3f382df44e97fedec21d2b0e7655 Mon Sep 17 00:00:00 2001 From: Jesse Archer Date: Tue, 4 Aug 2026 22:20:47 +0000 Subject: [PATCH 1/5] Add manual GitHub benchmark workflow Adds a workflow-dispatch GitHub Actions job for the local demo benchmark harness. The job runs the standard LingBot and Omnidreams baseline/candidate benchmark suite, summarizes performance and quality metrics in the Actions summary, and uploads the generated benchmark artifacts for inspection. --- .github/workflows/local-demo-benchmarks.yml | 151 ++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 .github/workflows/local-demo-benchmarks.yml diff --git a/.github/workflows/local-demo-benchmarks.yml b/.github/workflows/local-demo-benchmarks.yml new file mode 100644 index 000000000..1572fac99 --- /dev/null +++ b/.github/workflows/local-demo-benchmarks.yml @@ -0,0 +1,151 @@ +name: Local Demo Benchmarks + +on: + workflow_dispatch: + inputs: + cuda_group: + description: CUDA dependency group to install. + required: true + default: cuda13 + type: choice + options: + - cuda13 + - cuda12 + +permissions: + contents: read + +jobs: + benchmark: + name: LingBot and Omnidreams benchmarks + runs-on: linux-amd64-gpu-rtxpro6000-latest-2 + timeout-minutes: 360 + defaults: + run: + shell: bash + container: + image: nvidia/cuda:13.2.1-cudnn-devel-ubuntu24.04 + options: --gpus all + env: + UV_PROJECT_ENVIRONMENT: /tmp/flashdreams-local-demo-benchmark-venv + UV_LINK_MODE: copy + UV_PYTHON: "3.10" + MAX_JOBS: 8 + SCENARIO_FILE: configs/deterministic_quality_benchmarks.json + BENCHMARK_BASELINE_DIR: artifacts/benchmarks/local-demo-baseline + BENCHMARK_CANDIDATE_DIR: artifacts/benchmarks/local-demo-candidate + steps: + - name: Detect GPU architecture + id: gpu-arch + run: | + nvidia-smi + compute_cap=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1 | tr -d '[:space:]') + arch=$(echo "${compute_cap}" | tr -d '.') + echo "arch=${arch}" >> "$GITHUB_OUTPUT" + echo "Detected GPU compute capability: ${compute_cap} -> sm_${arch}" + + - name: Checkout + uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + apt-get update -qq + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \ + python3 python3-dev python3-venv \ + ffmpeg \ + gcc g++ ninja-build \ + libnccl-dev \ + curl git ca-certificates unzip jq + rm -rf /var/lib/apt/lists/* + + - name: Setup proxy cache + uses: nv-gha-runners/setup-proxy-cache@main + + - name: Setup uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-suffix: "local-demo-benchmarks-${{ github.event.inputs.cuda_group }}-sm${{ steps.gpu-arch.outputs.arch }}" + prune-cache: false + + - name: Install benchmark dependencies + run: | + uv venv --clear + uv sync --locked \ + --package flashdreams \ + --package flashdreams-lingbot \ + --package flashdreams-omnidreams \ + --no-dev \ + --group "${{ github.event.inputs.cuda_group }}" \ + --extra runners + + - name: Run baseline benchmark + env: + HF_TOKEN: ${{ secrets.HF_TOKEN }} + run: | + rm -rf artifacts/benchmarks + uv run --no-sync flashdreams-benchmark \ + --scenario-file "${SCENARIO_FILE}" \ + --scenario lingbot-world-fast-taehv-quality-smoke \ + --scenario omnidreams-sv-ci-quality-smoke \ + --scenario lingbot-world-fast-taehv-one-minute-review \ + --scenario omnidreams-sv-one-minute-review \ + --output-dir "${BENCHMARK_BASELINE_DIR}" + + - name: Run candidate benchmark + env: + HF_TOKEN: ${{ secrets.HF_TOKEN }} + run: | + uv run --no-sync flashdreams-benchmark \ + --scenario-file "${SCENARIO_FILE}" \ + --scenario lingbot-world-fast-taehv-quality-smoke \ + --scenario omnidreams-sv-ci-quality-smoke \ + --scenario lingbot-world-fast-taehv-one-minute-review \ + --scenario omnidreams-sv-one-minute-review \ + --quality-baseline-dir "${BENCHMARK_BASELINE_DIR}" \ + --output-dir "${BENCHMARK_CANDIDATE_DIR}" + + - name: Summarize benchmark metrics + if: always() + run: | + manifest="${BENCHMARK_CANDIDATE_DIR}/manifest.json" + { + echo "## Local Demo Benchmarks" + echo + echo "Scenario file: \`${SCENARIO_FILE}\`" + echo + if [ ! -f "${manifest}" ]; then + echo "Candidate manifest was not created." + exit 0 + fi + echo "Candidate report: \`${BENCHMARK_CANDIDATE_DIR}/report.html\`" + echo + echo "| Scenario | Status | Wall time | Median gen FPS | Quality score | Similarity | PSNR | RMSE |" + echo "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |" + jq -r ' + def metric($key): .metric_summary[$key].median // null; + def fmt: + if . == null then "n/a" + elif type == "number" then + if . >= 100 then ((. * 10 | round) / 10 | tostring) + else ((. * 10000 | round) / 10000 | tostring) + end + else tostring + end; + def duration: + if . == null then "n/a" + elif . >= 60 then (((. / 60 | floor) | tostring) + "m " + ((. % 60 | round) | tostring) + "s") + else (((. * 10 | round) / 10 | tostring) + "s") + end; + .scenarios[] + | "| \(.id) | \(.status) | \(.wall_time_s | duration) | \(metric("gen_fps") | fmt) | \(metric("quality_score") | fmt) | \(metric("quality_similarity_score") | fmt) | \(metric("quality_psnr_db") | fmt) | \(metric("quality_rmse") | fmt) |" + ' "${manifest}" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload benchmark artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: local-demo-benchmarks-${{ github.run_id }} + path: artifacts/benchmarks + if-no-files-found: warn From 6096182d9a3ed941b9d7b8e8a6330fa30c8911e5 Mon Sep 17 00:00:00 2001 From: Jesse Archer Date: Tue, 4 Aug 2026 22:55:00 +0000 Subject: [PATCH 2/5] Update demo benchmark workflow to run as PR canary Converts the local demo benchmark workflow from manual-only to an automatic, non-blocking PR and merge-queue canary. The workflow now runs the shorter 30-second LingBot and Omnidreams seeded quality scenarios, keeps baseline/candidate comparison for quality metrics, and still uploads the benchmark artifacts for review. --- .github/workflows/local-demo-benchmarks.yml | 36 +++++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/local-demo-benchmarks.yml b/.github/workflows/local-demo-benchmarks.yml index 1572fac99..60d2d9692 100644 --- a/.github/workflows/local-demo-benchmarks.yml +++ b/.github/workflows/local-demo-benchmarks.yml @@ -1,6 +1,22 @@ name: Local Demo Benchmarks on: + push: + branches: + - main + - "pull-request/[0-9]+" + paths: + - ".github/workflows/local-demo-benchmarks.yml" + - "configs/deterministic_quality_benchmarks.json" + - "flashdreams/flashdreams/**" + - "flashdreams/pyproject.toml" + - "flashdreams/tools/benchmarks/**" + - "integrations/lingbot/**" + - "integrations/omnidreams/**" + - "pyproject.toml" + - "uv.lock" + merge_group: + branches: [main] workflow_dispatch: inputs: cuda_group: @@ -17,9 +33,10 @@ permissions: jobs: benchmark: - name: LingBot and Omnidreams benchmarks + name: LingBot and Omnidreams benchmark canary runs-on: linux-amd64-gpu-rtxpro6000-latest-2 - timeout-minutes: 360 + timeout-minutes: 180 + continue-on-error: true defaults: run: shell: bash @@ -31,9 +48,10 @@ jobs: UV_LINK_MODE: copy UV_PYTHON: "3.10" MAX_JOBS: 8 + CUDA_GROUP: ${{ github.event.inputs.cuda_group || 'cuda13' }} SCENARIO_FILE: configs/deterministic_quality_benchmarks.json - BENCHMARK_BASELINE_DIR: artifacts/benchmarks/local-demo-baseline - BENCHMARK_CANDIDATE_DIR: artifacts/benchmarks/local-demo-candidate + BENCHMARK_BASELINE_DIR: artifacts/benchmarks/local-demo-canary-baseline + BENCHMARK_CANDIDATE_DIR: artifacts/benchmarks/local-demo-canary-candidate steps: - name: Detect GPU architecture id: gpu-arch @@ -65,7 +83,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: enable-cache: true - cache-suffix: "local-demo-benchmarks-${{ github.event.inputs.cuda_group }}-sm${{ steps.gpu-arch.outputs.arch }}" + cache-suffix: "local-demo-benchmark-canary-${{ github.event.inputs.cuda_group || 'cuda13' }}-sm${{ steps.gpu-arch.outputs.arch }}" prune-cache: false - name: Install benchmark dependencies @@ -76,7 +94,7 @@ jobs: --package flashdreams-lingbot \ --package flashdreams-omnidreams \ --no-dev \ - --group "${{ github.event.inputs.cuda_group }}" \ + --group "${CUDA_GROUP}" \ --extra runners - name: Run baseline benchmark @@ -88,8 +106,6 @@ jobs: --scenario-file "${SCENARIO_FILE}" \ --scenario lingbot-world-fast-taehv-quality-smoke \ --scenario omnidreams-sv-ci-quality-smoke \ - --scenario lingbot-world-fast-taehv-one-minute-review \ - --scenario omnidreams-sv-one-minute-review \ --output-dir "${BENCHMARK_BASELINE_DIR}" - name: Run candidate benchmark @@ -100,8 +116,6 @@ jobs: --scenario-file "${SCENARIO_FILE}" \ --scenario lingbot-world-fast-taehv-quality-smoke \ --scenario omnidreams-sv-ci-quality-smoke \ - --scenario lingbot-world-fast-taehv-one-minute-review \ - --scenario omnidreams-sv-one-minute-review \ --quality-baseline-dir "${BENCHMARK_BASELINE_DIR}" \ --output-dir "${BENCHMARK_CANDIDATE_DIR}" @@ -120,6 +134,8 @@ jobs: fi echo "Candidate report: \`${BENCHMARK_CANDIDATE_DIR}/report.html\`" echo + echo "This canary runs the two 30-second seeded quality scenarios only. The full one-minute review scenarios remain local/manual for now." + echo echo "| Scenario | Status | Wall time | Median gen FPS | Quality score | Similarity | PSNR | RMSE |" echo "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |" jq -r ' From 66de804af441047ae53c6b547a3eba1d02d7466e Mon Sep 17 00:00:00 2001 From: Jesse Archer Date: Wed, 5 Aug 2026 01:35:32 +0000 Subject: [PATCH 3/5] Fix LingBot benchmark CLI parsing Narrow LingBot's pipeline encoder config to the concrete camera-control encoder it already requires so Tyro can parse the full runner schema used by the local benchmark canary. Add a smoke test for the benchmark runner arguments to catch future CLI schema regressions before they reach the benchmark workflow. --- integrations/lingbot/lingbot/pipeline.py | 9 ++++- integrations/lingbot/tests/test_smoke.py | 47 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/integrations/lingbot/lingbot/pipeline.py b/integrations/lingbot/lingbot/pipeline.py index 2f42935a1..6a6e8f453 100644 --- a/integrations/lingbot/lingbot/pipeline.py +++ b/integrations/lingbot/lingbot/pipeline.py @@ -30,6 +30,7 @@ ) from lingbot.encoder.camctrl import ( CamCtrlInput, + I2VCamCtrlEncoderConfig, I2VCamCtrlInput, ) @@ -45,12 +46,18 @@ class LingbotUMT5TextEncoderConfig(UMT5TextEncoderConfig): class LingbotWorldInferencePipelineConfig(WanInferencePipelineConfig): """Config for the Lingbot World streaming pipeline. - Same shape as the Wan I2V config; only the target class is overridden. + Same shape as the Wan I2V config, with the camera-control encoder narrowed + to the concrete LingBot config that this pipeline requires. """ _target: type["LingbotWorldInferencePipeline"] = field( default_factory=lambda: LingbotWorldInferencePipeline ) + encoder: I2VCamCtrlEncoderConfig = field( # type: ignore[assignment] + default_factory=I2VCamCtrlEncoderConfig + ) + """Composite I2V + camera-control encoder.""" + text_encoder: LingbotUMT5TextEncoderConfig | None = field( default_factory=LingbotUMT5TextEncoderConfig ) diff --git a/integrations/lingbot/tests/test_smoke.py b/integrations/lingbot/tests/test_smoke.py index bf9c1ab1a..c7f856221 100644 --- a/integrations/lingbot/tests/test_smoke.py +++ b/integrations/lingbot/tests/test_smoke.py @@ -17,12 +17,15 @@ from __future__ import annotations +import dataclasses import sys from pathlib import Path +from typing import Annotated from typing import cast import pytest import tomli as tomllib +import tyro from lingbot import config as config_mod from lingbot import runner as runner_mod from lingbot.config import ( @@ -222,6 +225,50 @@ def test_model_versions_share_text_event_capable_pipeline(slug: str) -> None: assert transformer._target is LingbotWorldTransformer +def test_benchmark_runner_args_parse_through_tyro() -> None: + """Catch LingBot runner CLI schema regressions before benchmark CI.""" + cfg = RUNNER_CONFIGS["lingbot-world-fast-taehv-window15-sink3"] + union = tyro.extras.subcommand_type_from_defaults( + defaults={cfg.runner_name: cfg}, + descriptions={cfg.runner_name: cfg.description}, + prefix_names=False, + sort_subcommands=True, + ) + runner_union = tyro.conf.SuppressFixed[tyro.conf.FlagConversionOff[union]] + args_cls = dataclasses.make_dataclass( + "LingbotBenchmarkArgs", + [ + ("runner", Annotated[runner_union, tyro.conf.arg(name="")]), + ], + ) + + parsed = tyro.cli( + args_cls, + args=[ + cfg.runner_name, + "--example-data", + "True", + "--example-idx", + "0", + "--pixel-height", + "464", + "--pixel-width", + "832", + "--total-blocks", + "40", + "--pipeline.diffusion-model.seed", + "1", + ], + console_outputs=False, + ) + + runner_cfg = getattr(parsed, "runner") + assert runner_cfg.runner_name == cfg.runner_name + assert runner_cfg.example_data is True + assert runner_cfg.total_blocks == 40 + assert runner_cfg.pipeline.diffusion_model.seed == 1 + + def test_entry_points_match_module_literals() -> None: """The entry points in ``pyproject.toml`` must resolve to module attrs. From f1ff18515cf992e8268eac8f7776856b00d381ab Mon Sep 17 00:00:00 2001 From: Jesse Archer Date: Wed, 5 Aug 2026 02:41:06 +0000 Subject: [PATCH 4/5] Limit benchmark canary to Omnidreams Remove LingBot from the automatic benchmark canary for now because the GitHub runner cache volume does not satisfy LingBot's 200 GiB Hugging Face checkpoint preflight. Revert the LingBot CLI typing/test changes from the previous patch and keep LingBot benchmark scenarios available for local/manual runs. --- .github/workflows/local-demo-benchmarks.yml | 8 +--- integrations/lingbot/lingbot/pipeline.py | 9 +--- integrations/lingbot/tests/test_smoke.py | 47 --------------------- 3 files changed, 3 insertions(+), 61 deletions(-) diff --git a/.github/workflows/local-demo-benchmarks.yml b/.github/workflows/local-demo-benchmarks.yml index 60d2d9692..e6e10c399 100644 --- a/.github/workflows/local-demo-benchmarks.yml +++ b/.github/workflows/local-demo-benchmarks.yml @@ -11,7 +11,6 @@ on: - "flashdreams/flashdreams/**" - "flashdreams/pyproject.toml" - "flashdreams/tools/benchmarks/**" - - "integrations/lingbot/**" - "integrations/omnidreams/**" - "pyproject.toml" - "uv.lock" @@ -33,7 +32,7 @@ permissions: jobs: benchmark: - name: LingBot and Omnidreams benchmark canary + name: Omnidreams benchmark canary runs-on: linux-amd64-gpu-rtxpro6000-latest-2 timeout-minutes: 180 continue-on-error: true @@ -91,7 +90,6 @@ jobs: uv venv --clear uv sync --locked \ --package flashdreams \ - --package flashdreams-lingbot \ --package flashdreams-omnidreams \ --no-dev \ --group "${CUDA_GROUP}" \ @@ -104,7 +102,6 @@ jobs: rm -rf artifacts/benchmarks uv run --no-sync flashdreams-benchmark \ --scenario-file "${SCENARIO_FILE}" \ - --scenario lingbot-world-fast-taehv-quality-smoke \ --scenario omnidreams-sv-ci-quality-smoke \ --output-dir "${BENCHMARK_BASELINE_DIR}" @@ -114,7 +111,6 @@ jobs: run: | uv run --no-sync flashdreams-benchmark \ --scenario-file "${SCENARIO_FILE}" \ - --scenario lingbot-world-fast-taehv-quality-smoke \ --scenario omnidreams-sv-ci-quality-smoke \ --quality-baseline-dir "${BENCHMARK_BASELINE_DIR}" \ --output-dir "${BENCHMARK_CANDIDATE_DIR}" @@ -134,7 +130,7 @@ jobs: fi echo "Candidate report: \`${BENCHMARK_CANDIDATE_DIR}/report.html\`" echo - echo "This canary runs the two 30-second seeded quality scenarios only. The full one-minute review scenarios remain local/manual for now." + echo "This canary runs the 30-second seeded Omnidreams quality scenario only. The full one-minute review scenarios and LingBot scenarios remain local/manual for now." echo echo "| Scenario | Status | Wall time | Median gen FPS | Quality score | Similarity | PSNR | RMSE |" echo "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |" diff --git a/integrations/lingbot/lingbot/pipeline.py b/integrations/lingbot/lingbot/pipeline.py index 6a6e8f453..2f42935a1 100644 --- a/integrations/lingbot/lingbot/pipeline.py +++ b/integrations/lingbot/lingbot/pipeline.py @@ -30,7 +30,6 @@ ) from lingbot.encoder.camctrl import ( CamCtrlInput, - I2VCamCtrlEncoderConfig, I2VCamCtrlInput, ) @@ -46,18 +45,12 @@ class LingbotUMT5TextEncoderConfig(UMT5TextEncoderConfig): class LingbotWorldInferencePipelineConfig(WanInferencePipelineConfig): """Config for the Lingbot World streaming pipeline. - Same shape as the Wan I2V config, with the camera-control encoder narrowed - to the concrete LingBot config that this pipeline requires. + Same shape as the Wan I2V config; only the target class is overridden. """ _target: type["LingbotWorldInferencePipeline"] = field( default_factory=lambda: LingbotWorldInferencePipeline ) - encoder: I2VCamCtrlEncoderConfig = field( # type: ignore[assignment] - default_factory=I2VCamCtrlEncoderConfig - ) - """Composite I2V + camera-control encoder.""" - text_encoder: LingbotUMT5TextEncoderConfig | None = field( default_factory=LingbotUMT5TextEncoderConfig ) diff --git a/integrations/lingbot/tests/test_smoke.py b/integrations/lingbot/tests/test_smoke.py index c7f856221..bf9c1ab1a 100644 --- a/integrations/lingbot/tests/test_smoke.py +++ b/integrations/lingbot/tests/test_smoke.py @@ -17,15 +17,12 @@ from __future__ import annotations -import dataclasses import sys from pathlib import Path -from typing import Annotated from typing import cast import pytest import tomli as tomllib -import tyro from lingbot import config as config_mod from lingbot import runner as runner_mod from lingbot.config import ( @@ -225,50 +222,6 @@ def test_model_versions_share_text_event_capable_pipeline(slug: str) -> None: assert transformer._target is LingbotWorldTransformer -def test_benchmark_runner_args_parse_through_tyro() -> None: - """Catch LingBot runner CLI schema regressions before benchmark CI.""" - cfg = RUNNER_CONFIGS["lingbot-world-fast-taehv-window15-sink3"] - union = tyro.extras.subcommand_type_from_defaults( - defaults={cfg.runner_name: cfg}, - descriptions={cfg.runner_name: cfg.description}, - prefix_names=False, - sort_subcommands=True, - ) - runner_union = tyro.conf.SuppressFixed[tyro.conf.FlagConversionOff[union]] - args_cls = dataclasses.make_dataclass( - "LingbotBenchmarkArgs", - [ - ("runner", Annotated[runner_union, tyro.conf.arg(name="")]), - ], - ) - - parsed = tyro.cli( - args_cls, - args=[ - cfg.runner_name, - "--example-data", - "True", - "--example-idx", - "0", - "--pixel-height", - "464", - "--pixel-width", - "832", - "--total-blocks", - "40", - "--pipeline.diffusion-model.seed", - "1", - ], - console_outputs=False, - ) - - runner_cfg = getattr(parsed, "runner") - assert runner_cfg.runner_name == cfg.runner_name - assert runner_cfg.example_data is True - assert runner_cfg.total_blocks == 40 - assert runner_cfg.pipeline.diffusion_model.seed == 1 - - def test_entry_points_match_module_literals() -> None: """The entry points in ``pyproject.toml`` must resolve to module attrs. From bb5513b22e47d6cf0ce70562c43c19b3e2719eda Mon Sep 17 00:00:00 2001 From: Jesse Archer Date: Wed, 5 Aug 2026 03:38:50 +0000 Subject: [PATCH 5/5] Rename demo benchmark job and probe baseline host Rename the local demo benchmark workflow/check to Demo Benchmarks to avoid confusion with the existing Omnidreams WorldLens canary. Trim large uv cache directories before Actions cache upload, matching the existing GPU canary pattern, and add a temporary non-failing DNS/TCP reachability probe for the baseline artifact host. --- .github/workflows/local-demo-benchmarks.yml | 42 ++++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/workflows/local-demo-benchmarks.yml b/.github/workflows/local-demo-benchmarks.yml index e6e10c399..3944f2bea 100644 --- a/.github/workflows/local-demo-benchmarks.yml +++ b/.github/workflows/local-demo-benchmarks.yml @@ -1,4 +1,4 @@ -name: Local Demo Benchmarks +name: Demo Benchmarks on: push: @@ -32,7 +32,7 @@ permissions: jobs: benchmark: - name: Omnidreams benchmark canary + name: Demo Benchmarks runs-on: linux-amd64-gpu-rtxpro6000-latest-2 timeout-minutes: 180 continue-on-error: true @@ -85,6 +85,15 @@ jobs: cache-suffix: "local-demo-benchmark-canary-${{ github.event.inputs.cuda_group || 'cuda13' }}-sm${{ steps.gpu-arch.outputs.arch }}" prune-cache: false + - name: Check benchmark baseline host reachability + run: | + set -ux + host=2u2g-gen-0801.ipp3a2.colossus.nvidia.com + getent hosts "${host}" || true + timeout 10 bash -c "> "$GITHUB_STEP_SUMMARY" + - name: Trim uv cache for upload + if: always() + run: | + cache_dir="${UV_CACHE_DIR:-/github/home/.cache/uv}" + echo "=== Cache size before trim ===" + du -sh "${cache_dir}" || true + du -sh "${cache_dir}"/*/ 2>/dev/null || true + + # The proxy cache can re-download wheels faster than Actions can + # upload and restore a multi-GB uv cache. + rm -rf "${cache_dir}/wheels-v6" + + # uv can re-extract unzipped wheel archives on demand. + rm -rf "${cache_dir}/archive-v0" + + # Build artifacts from git checkouts bloat the saved cache but are + # not needed for reuse. + find "${cache_dir}/git-v0/checkouts" \ + \( -name "build" -o -name "*.egg-info" -o -name "__pycache__" \) \ + -type d -exec rm -rf {} + 2>/dev/null || true + + # Workspace editable installs rebuild quickly from source. + rm -rf "${cache_dir}/sdists-v9/editable" + + echo "" + echo "=== Cache size after trim ===" + du -sh "${cache_dir}" || true + du -sh "${cache_dir}"/*/ 2>/dev/null || true + - name: Upload benchmark artifacts if: always() uses: actions/upload-artifact@v4