Files
hao-ai-lab-FastVideo/fastvideo/tests/eval/test_evaluator_multi_gpu.py
T

108 lines
3.4 KiB
Python

"""Multi-replica eval through the public ``Evaluator`` API.
Skipped automatically when fewer than 2 CUDA devices are visible.
"""
from __future__ import annotations
import pytest
import torch
from fastvideo.eval import create_evaluator
pytestmark = pytest.mark.skipif(
not torch.cuda.is_available() or torch.cuda.device_count() < 2,
reason="multi-GPU evaluator tests require at least 2 visible CUDA devices",
)
_T, _C, _H, _W = 6, 3, 32, 32
def _make_samples(n: int) -> list[dict]:
torch.manual_seed(7)
samples: list[dict] = []
for i in range(n):
gen = torch.rand(_T, _C, _H, _W)
# Slightly perturb each row's reference so scores vary by index.
ref = gen + 0.01 * (i + 1) * torch.rand_like(gen)
samples.append({"video": gen, "reference": ref})
return samples
@pytest.fixture
def baseline_scores():
"""Reference scores computed on a single-GPU evaluator. The multi-GPU
runs must reproduce these exactly when handed the same input list —
that's the only way to verify round-robin dispatch isn't dropping or
reordering samples."""
samples = _make_samples(8)
ev = create_evaluator(
metrics=["common.psnr", "common.ssim"],
device="cuda:0",
num_gpus=1,
)
try:
out = ev.evaluate(samples=samples)
finally:
ev.shutdown()
return samples, out
def test_multi_gpu_evaluator_reports_two_workers():
ev = create_evaluator(metrics=["common.psnr"], num_gpus=2)
try:
assert ev.num_gpus == 2
finally:
ev.shutdown()
def test_multi_gpu_dispatch_preserves_order_and_scores(baseline_scores):
"""Same samples, multi-GPU dispatch — results must match the single-GPU
baseline element-for-element. This verifies (a) the round-robin doesn't
reorder, (b) every sample is scored exactly once, (c) the workers
don't share mutable state."""
samples, expected = baseline_scores
ev = create_evaluator(
metrics=["common.psnr", "common.ssim"],
num_gpus=2,
)
try:
got = ev.evaluate(samples=samples)
finally:
ev.shutdown()
assert len(got) == len(expected)
for i, (g, e) in enumerate(zip(got, expected)):
assert set(g.keys()) == {"common.psnr", "common.ssim"}, f"row {i}"
assert g["common.psnr"].score == pytest.approx(e["common.psnr"].score), \
f"row {i} psnr drift"
assert g["common.ssim"].score == pytest.approx(e["common.ssim"].score), \
f"row {i} ssim drift"
def test_multi_gpu_evaluator_kwargs_form_runs_on_one_replica():
"""The kwargs form (single sample) is documented to always hit worker
0; this test pins the contract so future refactors don't accidentally
fan out a single call."""
ev = create_evaluator(metrics=["common.psnr"], num_gpus=2)
try:
torch.manual_seed(0)
gen = torch.rand(_T, _C, _H, _W)
out = ev.evaluate(video=gen, reference=gen)
assert out["common.psnr"].score > 50.0 # PSNR(x, x) is huge
finally:
ev.shutdown()
def test_multi_gpu_release_cuda_memory_runs_clean():
"""``release_cuda_memory`` must hit every replica without crashing."""
ev = create_evaluator(metrics=["common.psnr"], num_gpus=2)
try:
samples = _make_samples(2)
_ = ev.evaluate(samples=samples)
ev.release_cuda_memory() # should not raise
finally:
ev.shutdown()