anilegin's picture
Upload inference-only latent diffusion model
a04730e verified
Raw
History Blame Contribute Delete
4.04 kB
from __future__ import annotations
import json
import time
from contextlib import ContextDecorator
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
@dataclass
class TimerRecord:
name: str
total: float = 0.0
count: int = 0
@property
def avg(self) -> float:
return self.total / max(1, self.count)
class Timer(ContextDecorator):
"""
It can be used as:
timer = Timer(sync_cuda=True)
with timer("sample"):
images = sample()
timer.print_summary()
timer.save_json("timing.json")
"""
def __init__(
self,
name: Optional[str] = None,
records: Optional[dict[str, TimerRecord]] = None,
enabled: bool = True,
sync_cuda: bool = True,
verbose: bool = False,
):
self.name = name
self.records = records if records is not None else {}
self.enabled = enabled
self.sync_cuda = sync_cuda
self.verbose = verbose
self._start_time: Optional[float] = None
def __call__(self, name: str):
return Timer(
name=name,
records=self.records,
enabled=self.enabled,
sync_cuda=self.sync_cuda,
verbose=self.verbose,
)
def __enter__(self):
if not self.enabled:
return self
self._sync()
self._start_time = time.perf_counter()
return self
def __exit__(self, exc_type, exc, tb):
if not self.enabled:
return False
self._sync()
if self._start_time is None:
elapsed = 0.0
else:
elapsed = time.perf_counter() - self._start_time
name = self.name or "unnamed"
if name not in self.records:
self.records[name] = TimerRecord(name=name)
record = self.records[name]
record.total += elapsed
record.count += 1
if self.verbose:
print(f"[timer] {name}: {elapsed:.4f}s", flush=True)
return False
def reset(self) -> None:
self.records.clear()
def get(self, name: str) -> TimerRecord:
return self.records[name]
def summary(self) -> dict[str, dict[str, float | int]]:
return {
name: {
"total_sec": record.total,
"count": record.count,
"avg_sec": record.avg,
}
for name, record in self.records.items()
}
def save_json(self, path: str | Path) -> None:
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(self.summary(), f, indent=2)
def print_summary(self, sort_by: str = "total", title: str = "Timing summary") -> None:
if not self.records:
print(f"{title}: no records", flush=True)
return
if sort_by == "total":
key_fn = lambda item: item[1].total
elif sort_by == "avg":
key_fn = lambda item: item[1].avg
elif sort_by == "count":
key_fn = lambda item: item[1].count
else:
raise ValueError("sort_by must be 'total', 'avg', or 'count'.")
items = sorted(self.records.items(), key=key_fn, reverse=True)
print("=" * 72, flush=True)
print(title, flush=True)
print("=" * 72, flush=True)
print(f"{'operation':36s} {'count':>8s} {'total(s)':>12s} {'avg(s)':>12s}", flush=True)
print("-" * 72, flush=True)
for name, record in items:
print(
f"{name:36s} {record.count:8d} {record.total:12.4f} {record.avg:12.4f}",
flush=True,
)
print("=" * 72, flush=True)
def _sync(self) -> None:
if not self.sync_cuda:
return
try:
import torch
if torch.cuda.is_available():
torch.cuda.synchronize()
except ImportError:
pass