Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
060fcc4
Add semantic early-stop for Langevin iterations
godnight10061 Jan 11, 2026
fb35a1d
Add ImageNet LPIPS benchmark workflow
godnight10061 Jan 11, 2026
7c79712
Make LPIPS benchmark long-running e2e
godnight10061 Jan 12, 2026
11c1490
Make benchmark workflow test semantic early stop
godnight10061 Jan 14, 2026
4074473
Add per-case quality gates to benchmark
godnight10061 Jan 14, 2026
207860e
Avoid semantic early-stop at low sigma
godnight10061 Jan 16, 2026
896729c
Fix semantic early-stop distance
godnight10061 Jan 16, 2026
2086f00
Skip score model after semantic convergence
godnight10061 Jan 19, 2026
e7080f9
Test ring changes prevent premature freeze
godnight10061 Jan 17, 2026
450a25e
Refine semantic inner stop
godnight10061 Jan 18, 2026
9c9b555
Tighten semantic stop in mid-noise steps
godnight10061 Jan 18, 2026
6fd5f60
Adjust SHO regression test for x0 return
godnight10061 Jan 19, 2026
73ab537
Make CI-only imports lighter
godnight10061 Jan 19, 2026
ac7a5a1
Fix semantic-stop distance_fn dispatch
godnight10061 Jan 19, 2026
c5fe635
Harden semantic-stop distance hook
godnight10061 Jan 19, 2026
7d46147
Guard torch.nn.functional import
godnight10061 Jan 19, 2026
cbc9f3a
Refactor early-stop logic into LanPaintEarlyStopper class
scraed Jan 22, 2026
1b87239
Move Coef_C definition above advance_time
scraed Jan 22, 2026
34ded92
Simplify semantic early-stop knobs
godnight10061 Jan 24, 2026
a895d2a
Tighten semantic early-stop robustness
godnight10061 Jan 24, 2026
c7962bf
Refactor semantic early-stop metric
godnight10061 Jan 25, 2026
8f52a1d
Use NamedTuple for Langevin state
godnight10061 Jan 25, 2026
06da285
Fix LangevinState backward compat
godnight10061 Jan 25, 2026
114d091
Simplify LanPaint inner-loop glue
godnight10061 Jan 25, 2026
cd06069
Avoid early-stop when no inpaint region
godnight10061 Jan 26, 2026
d73f3db
Document distance_fn hook
godnight10061 Jan 26, 2026
5e7d472
Use LangevinState in semantic stop test mocks
godnight10061 Jan 26, 2026
6ad4a18
Refactor earlystop to reduce duplication
godnight10061 Jan 26, 2026
39c90bb
Revert benchmark workflow
godnight10061 Jan 26, 2026
9ddd5f6
Map legacy min_steps into patience
godnight10061 Jan 26, 2026
4fcbfc7
Merge branch 'master' into pr/74
scraed Jan 30, 2026
8fe1a9d
Move parameters to the last for backward compatibility
scraed Jan 30, 2026
2e3a566
Keep a minimal set of test
scraed Jan 30, 2026
30cfd2f
add contribution info
scraed Jan 30, 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
238 changes: 238 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
name: LanPaintBench LPIPS (ImageNet subset)

on:
workflow_dispatch:
inputs:
base_ref:
description: "Base ref to benchmark against (commit SHA or branch). Leave empty to use repo default branch."
required: false
default: ""
subset:
description: "Number of ImageNet images to benchmark"
required: true
default: "1"
sample_steps:
description: "Outer diffusion steps (guided_diffusion_e2e)"
required: true
default: "20"
n_steps:
description: "LanPaint inner steps per outer step"
required: true
default: "5"
seed:
description: "Random seed"
required: true
default: "42"
shuffle_buffer:
description: "HF streaming shuffle buffer"
required: true
default: "1024"
semantic_threshold:
description: "Enable semantic early-stop if > 0 (head_on run)"
required: true
default: "0.01"
semantic_patience:
description: "Semantic early-stop patience (head_on run)"
required: true
default: "2"
min_seconds:
description: "Fail if a benchmark runtime is below this (0 to disable)"
required: true
default: "0"
max_case_mean_delta:
description: "Fail if mean per-case metric delta exceeds this (head_on - head_off)"
required: true
default: "0.01"
max_case_p95_delta:
description: "Fail if P95 per-case metric delta exceeds this (head_on - head_off)"
required: true
default: "0.02"
max_case_max_delta:
description: "Fail if max per-case metric delta exceeds this (head_on - head_off)"
required: true
default: "0.05"
gallery_topk:
description: "How many worst-case examples to render in the gallery"
required: true
default: "8"

jobs:
benchmark:
name: Compare Base vs Head
runs-on: ubuntu-latest
timeout-minutes: 360
env:
HF_HOME: ~/.cache/huggingface
BASE_REF: ${{ inputs.base_ref || github.event.repository.default_branch }}
OMP_NUM_THREADS: "1"
MKL_NUM_THREADS: "1"
LANPAINTBENCH_REF: "ea5be5d"
CHECKPOINT_URL: "https://openaipublic.blob.core.windows.net/diffusion/jul-2021/256x256_diffusion_uncond.pt"
steps:
- name: Checkout Head (Current PR)
uses: actions/checkout@v4
with:
path: head
fetch-depth: 1

- name: Checkout Base (Baseline)
uses: actions/checkout@v4
with:
ref: ${{ env.BASE_REF }}
path: base
fetch-depth: 1

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
pip install lpips datasets numpy pillow blobfile
# We don't need to install LanPaint as a package because we use sys.path in the script

- name: Cache HF + torch + checkpoints
uses: actions/cache@v4
with:
path: |
~/.cache/huggingface
~/.cache/torch
~/.cache/lanpaintbench
key: ${{ runner.os }}-lpips-imagenet-${{ hashFiles('head/tests/benchmark_lpips.py', 'head/tests/compare_benchmarks.py', 'head/tests/render_benchmark_gallery.py') }}

- name: Clone LanPaintBench
run: |
git clone https://github.com/scraed/LanPaintBench.git bench
cd bench
git checkout "$LANPAINTBENCH_REF"

- name: Download ImageNet-256 checkpoint
run: |
CHECKPOINT_PATH="$HOME/.cache/lanpaintbench/checkpoints/256x256_diffusion_uncond.pt"
mkdir -p ~/.cache/lanpaintbench/checkpoints
if [ ! -f "$CHECKPOINT_PATH" ]; then
curl -L "$CHECKPOINT_URL" -o "$CHECKPOINT_PATH"
fi
ls -lh "$CHECKPOINT_PATH"

- name: Run Baseline Benchmark
run: |
CHECKPOINT_PATH="$HOME/.cache/lanpaintbench/checkpoints/256x256_diffusion_uncond.pt"
# Use the benchmark script from the HEAD branch to ensure test consistency
python head/tests/benchmark_lpips.py \
--mode guided_diffusion_e2e \
--src base/src \
--lanpaintbench bench \
--checkpoint "$CHECKPOINT_PATH" \
--output baseline.json \
--dataset benjamin-paine/imagenet-1k-256x256 \
--split train \
--subset "${{ inputs.subset }}" \
--seed "${{ inputs.seed }}" \
--shuffle-buffer "${{ inputs.shuffle_buffer }}" \
--no-shuffle \
--mask-type box \
--n-steps "${{ inputs.n_steps }}" \
--sample-steps "${{ inputs.sample_steps }}" \
--device cpu \
--min-seconds "${{ inputs.min_seconds }}" \
--semantic-threshold 0.0

- name: Run Head Benchmark (early-stop OFF)
run: |
CHECKPOINT_PATH="$HOME/.cache/lanpaintbench/checkpoints/256x256_diffusion_uncond.pt"
python head/tests/benchmark_lpips.py \
--mode guided_diffusion_e2e \
--src head/src \
--lanpaintbench bench \
--checkpoint "$CHECKPOINT_PATH" \
--output head_off.json \
--dataset benjamin-paine/imagenet-1k-256x256 \
--split train \
--subset "${{ inputs.subset }}" \
--seed "${{ inputs.seed }}" \
--shuffle-buffer "${{ inputs.shuffle_buffer }}" \
--no-shuffle \
--mask-type box \
--n-steps "${{ inputs.n_steps }}" \
--sample-steps "${{ inputs.sample_steps }}" \
--device cpu \
--min-seconds "${{ inputs.min_seconds }}" \
--semantic-threshold 0.0 \
--save-images-dir head_off_images

- name: Run Head Benchmark (early-stop ON)
run: |
CHECKPOINT_PATH="$HOME/.cache/lanpaintbench/checkpoints/256x256_diffusion_uncond.pt"
python head/tests/benchmark_lpips.py \
--mode guided_diffusion_e2e \
--src head/src \
--lanpaintbench bench \
--checkpoint "$CHECKPOINT_PATH" \
--output head_on.json \
--dataset benjamin-paine/imagenet-1k-256x256 \
--split train \
--subset "${{ inputs.subset }}" \
--seed "${{ inputs.seed }}" \
--shuffle-buffer "${{ inputs.shuffle_buffer }}" \
--no-shuffle \
--mask-type box \
--n-steps "${{ inputs.n_steps }}" \
--sample-steps "${{ inputs.sample_steps }}" \
--device cpu \
--min-seconds "${{ inputs.min_seconds }}" \
--semantic-threshold "${{ inputs.semantic_threshold }}" \
--semantic-patience "${{ inputs.semantic_patience }}" \
--save-images-dir head_on_images

- name: Compare baseline vs head_off
continue-on-error: true
run: |
python head/tests/compare_benchmarks.py --baseline baseline.json --current head_off.json --max-rel-diff 0.01 --max-abs-diff 0.0

- name: Compare head_off vs head_on
run: |
python head/tests/compare_benchmarks.py \
--baseline head_off.json \
--current head_on.json \
--max-rel-diff 0.01 \
--max-abs-diff 0.0 \
--case-metric lpips_inpaint_ctx \
--max-case-mean-delta "${{ inputs.max_case_mean_delta }}" \
--max-case-p95-delta "${{ inputs.max_case_p95_delta }}" \
--max-case-max-delta "${{ inputs.max_case_max_delta }}"

- name: Compare baseline vs head_on
continue-on-error: true
run: |
python head/tests/compare_benchmarks.py --baseline baseline.json --current head_on.json --max-rel-diff 0.01 --max-abs-diff 0.0

- name: Render worst-case gallery (head_off vs head_on)
if: ${{ always() }}
run: |
python head/tests/render_benchmark_gallery.py \
--baseline-json head_off.json \
--current-json head_on.json \
--baseline-images head_off_images \
--current-images head_on_images \
--out-dir gallery \
--metric lpips_inpaint_ctx \
--top-k "${{ inputs.gallery_topk }}"

- name: Upload benchmark JSON
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: lpips-benchmark-artifacts
path: |
baseline.json
head_off.json
head_on.json
head_off_images
head_on_images
gallery
if-no-files-found: warn
Loading