151 lines
5.5 KiB
Python
151 lines
5.5 KiB
Python
"""Run isolated Krea 2 RAW parity against pinned Diffusers 0.39.0."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import ast
|
|
import importlib
|
|
import importlib.metadata
|
|
import importlib.util
|
|
import sys
|
|
from collections.abc import Callable, Sequence
|
|
from pathlib import Path
|
|
from typing import Any, cast
|
|
|
|
from scripts.parity.krea2_raw_report import (
|
|
CASE_SPECS,
|
|
build_parity_report,
|
|
canonical_json,
|
|
raw_case_id,
|
|
)
|
|
from scripts.parity.krea2_turbo_report import (
|
|
DIFFUSERS_VERSION,
|
|
NUMPY_VERSION,
|
|
TORCH_VERSION,
|
|
)
|
|
|
|
|
|
def _require_distribution_version(distribution: str, expected: str) -> str:
|
|
try:
|
|
actual = importlib.metadata.version(distribution)
|
|
except importlib.metadata.PackageNotFoundError as error:
|
|
raise RuntimeError(
|
|
f"missing parity dependency {distribution}=={expected}; "
|
|
"install requirements/parity-krea2-turbo.txt in an isolated environment"
|
|
) from error
|
|
# IMPORTANT: official CPU wheels use a PEP 440 local suffix; keep report metadata canonical.
|
|
cpu_build = distribution == "torch" and actual == f"{expected}+cpu"
|
|
if actual != expected and not cpu_build:
|
|
raise RuntimeError(f"parity dependency {distribution} must be {expected}, found {actual}")
|
|
return expected
|
|
|
|
|
|
def _load_pinned_calculate_shift() -> Callable[..., float]:
|
|
"""Execute only Diffusers' pinned pure helper without pipeline-only dependencies."""
|
|
|
|
spec = importlib.util.find_spec("diffusers.pipelines.krea2.pipeline_krea2")
|
|
if spec is None or spec.origin is None:
|
|
raise RuntimeError("pinned Diffusers Krea 2 pipeline source is unavailable")
|
|
source_path = Path(spec.origin)
|
|
tree = ast.parse(source_path.read_text(encoding="utf-8"), filename=str(source_path))
|
|
matches = [
|
|
node
|
|
for node in tree.body
|
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
and node.name == "calculate_shift"
|
|
]
|
|
if len(matches) != 1 or not isinstance(matches[0], ast.FunctionDef):
|
|
raise RuntimeError("pinned Diffusers calculate_shift definition is unavailable")
|
|
module = ast.Module(body=[matches[0]], type_ignores=[])
|
|
ast.fix_missing_locations(module)
|
|
namespace: dict[str, object] = {}
|
|
exec(compile(module, str(source_path), "exec"), namespace) # noqa: S102
|
|
function = namespace.get("calculate_shift")
|
|
if not callable(function):
|
|
raise RuntimeError("pinned Diffusers calculate_shift is not executable")
|
|
return cast(Callable[..., float], function)
|
|
|
|
|
|
def _load_diffusers_cases() -> tuple[dict[str, dict[str, object]], dict[str, str]]:
|
|
versions = {
|
|
"device": "cpu",
|
|
"diffusers": _require_distribution_version("diffusers", DIFFUSERS_VERSION),
|
|
"numpy": _require_distribution_version("numpy", NUMPY_VERSION),
|
|
"torch": _require_distribution_version("torch", TORCH_VERSION),
|
|
}
|
|
numpy_module = importlib.import_module("numpy")
|
|
importlib.import_module("torch")
|
|
scheduler_module = importlib.import_module(
|
|
"diffusers.schedulers.scheduling_flow_match_euler_discrete"
|
|
)
|
|
scheduler_type = cast(Any, scheduler_module.FlowMatchEulerDiscreteScheduler)
|
|
linspace = cast(Any, numpy_module.linspace)
|
|
calculate_shift = _load_pinned_calculate_shift()
|
|
|
|
cases: dict[str, dict[str, object]] = {}
|
|
for recipe_id, steps, width, height in CASE_SPECS:
|
|
effective_width = ((width + 15) // 16) * 16
|
|
effective_height = ((height + 15) // 16) * 16
|
|
image_seq_len = (effective_width // 16) * (effective_height // 16)
|
|
mu = calculate_shift(
|
|
image_seq_len,
|
|
base_seq_len=256,
|
|
max_seq_len=6400,
|
|
base_shift=0.5,
|
|
max_shift=1.15,
|
|
)
|
|
scheduler = scheduler_type(
|
|
num_train_timesteps=1000,
|
|
shift=1.0,
|
|
use_dynamic_shifting=True,
|
|
base_shift=0.5,
|
|
max_shift=1.15,
|
|
base_image_seq_len=256,
|
|
max_image_seq_len=6400,
|
|
invert_sigmas=False,
|
|
shift_terminal=None,
|
|
use_karras_sigmas=False,
|
|
use_exponential_sigmas=False,
|
|
use_beta_sigmas=False,
|
|
time_shift_type="exponential",
|
|
stochastic_sampling=False,
|
|
)
|
|
base_sigmas = cast(list[float], linspace(1.0, 1.0 / steps, steps).tolist())
|
|
scheduler.set_timesteps(
|
|
num_inference_steps=steps,
|
|
device="cpu",
|
|
sigmas=base_sigmas,
|
|
mu=mu,
|
|
)
|
|
raw_values = cast(list[float], scheduler.sigmas.tolist())
|
|
cases[raw_case_id(recipe_id, width, height)] = {
|
|
"mu": float(mu),
|
|
"sigmas": tuple(float(value) for value in raw_values),
|
|
}
|
|
return cases, versions
|
|
|
|
|
|
def main(arguments: Sequence[str] | None = None) -> int:
|
|
"""Generate canonical RAW evidence or fail without writing a partial report."""
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
parsed = parser.parse_args(arguments)
|
|
|
|
try:
|
|
cases, environment = _load_diffusers_cases()
|
|
report = build_parity_report(cases, environment=environment)
|
|
encoded = canonical_json(report)
|
|
except (ImportError, ModuleNotFoundError, RuntimeError, ValueError) as error:
|
|
print(f"PARITY=FAIL\n{error}", file=sys.stderr)
|
|
return 2
|
|
|
|
parsed.output.parent.mkdir(parents=True, exist_ok=True)
|
|
parsed.output.write_text(encoded, encoding="utf-8", newline="\n")
|
|
print(f"PARITY=PASS\nREPORT={parsed.output}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|