diffeval 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffeval/__init__.py +7 -0
- diffeval/__main__.py +6 -0
- diffeval/aggregate/__init__.py +1 -0
- diffeval/aggregate/mean.py +7 -0
- diffeval/aggregate/names.py +11 -0
- diffeval/aggregate/row.py +26 -0
- diffeval/aggregate/sample_std.py +9 -0
- diffeval/aggregate/series.py +22 -0
- diffeval/aggregate/table.py +8 -0
- diffeval/backends/__init__.py +4 -0
- diffeval/backends/diffusers/__init__.py +1 -0
- diffeval/backends/diffusers/attention_apply.py +16 -0
- diffeval/backends/diffusers/attention_eager.py +6 -0
- diffeval/backends/diffusers/attention_name.py +15 -0
- diffeval/backends/diffusers/attention_sdpa.py +6 -0
- diffeval/backends/diffusers/attention_xformers.py +5 -0
- diffeval/backends/diffusers/backend.py +29 -0
- diffeval/backends/diffusers/call_kwargs.py +15 -0
- diffeval/backends/diffusers/coerce_dtype.py +10 -0
- diffeval/backends/diffusers/describe.py +6 -0
- diffeval/backends/diffusers/dtype_name.py +23 -0
- diffeval/backends/diffusers/from_config.py +17 -0
- diffeval/backends/diffusers/generate.py +18 -0
- diffeval/backends/diffusers/kwargs_generator.py +6 -0
- diffeval/backends/diffusers/kwargs_prompt.py +11 -0
- diffeval/backends/diffusers/kwargs_sampling.py +18 -0
- diffeval/backends/diffusers/kwargs_size.py +18 -0
- diffeval/backends/diffusers/load_kwargs.py +10 -0
- diffeval/backends/diffusers/load_pipe.py +23 -0
- diffeval/backends/diffusers/missing_dep.py +10 -0
- diffeval/backends/diffusers/one_batch.py +11 -0
- diffeval/backends/diffusers/pick_device.py +13 -0
- diffeval/backends/diffusers/presets.py +8 -0
- diffeval/backends/diffusers/sampling.py +18 -0
- diffeval/backends/dummy/__init__.py +1 -0
- diffeval/backends/dummy/backend.py +29 -0
- diffeval/backends/dummy/box.py +9 -0
- diffeval/backends/dummy/colors.py +10 -0
- diffeval/backends/dummy/describe.py +18 -0
- diffeval/backends/dummy/draw.py +16 -0
- diffeval/backends/dummy/from_config.py +14 -0
- diffeval/backends/dummy/generate.py +17 -0
- diffeval/backends/dummy/render.py +18 -0
- diffeval/backends/noop_prepare.py +5 -0
- diffeval/backends/protocol.py +15 -0
- diffeval/bench/__init__.py +1 -0
- diffeval/bench/cmmd_check.py +20 -0
- diffeval/bench/cmmd_samples.py +15 -0
- diffeval/bench/coco30k_cli.py +23 -0
- diffeval/bench/coco30k_export.py +19 -0
- diffeval/bench/coco30k_files.py +16 -0
- diffeval/bench/coco30k_images.py +21 -0
- diffeval/bench/coco30k_suite.py +16 -0
- diffeval/bench/fetch.py +12 -0
- diffeval/bench/published.py +10 -0
- diffeval/bench/table_cli.py +16 -0
- diffeval/bench/table_row.py +24 -0
- diffeval/cli/__init__.py +1 -0
- diffeval/cli/command_list.py +13 -0
- diffeval/cli/command_run.py +25 -0
- diffeval/cli/config.py +14 -0
- diffeval/cli/csv_ints.py +8 -0
- diffeval/cli/csv_names.py +8 -0
- diffeval/cli/main.py +12 -0
- diffeval/cli/output_dir.py +12 -0
- diffeval/cli/parser.py +17 -0
- diffeval/cli/print_paths.py +12 -0
- diffeval/cli/print_registered.py +6 -0
- diffeval/cli/run_parser_eval.py +14 -0
- diffeval/cli/run_parser_model.py +13 -0
- diffeval/metrics/__init__.py +7 -0
- diffeval/metrics/clip/__init__.py +1 -0
- diffeval/metrics/clip/features.py +14 -0
- diffeval/metrics/clip/formula.py +6 -0
- diffeval/metrics/clip/load.py +17 -0
- diffeval/metrics/clip/metric.py +20 -0
- diffeval/metrics/clip/protocol.py +10 -0
- diffeval/metrics/clip/sample_image.py +10 -0
- diffeval/metrics/clip/score.py +25 -0
- diffeval/metrics/cmmd/__init__.py +1 -0
- diffeval/metrics/cmmd/embed_batch.py +26 -0
- diffeval/metrics/cmmd/embed_paths.py +30 -0
- diffeval/metrics/cmmd/metric.py +20 -0
- diffeval/metrics/cmmd/mmd.py +23 -0
- diffeval/metrics/cmmd/protocol.py +11 -0
- diffeval/metrics/cmmd/resize.py +17 -0
- diffeval/metrics/cmmd/score.py +24 -0
- diffeval/metrics/count_warning.py +7 -0
- diffeval/metrics/distribution/__init__.py +1 -0
- diffeval/metrics/distribution/call.py +24 -0
- diffeval/metrics/distribution/kwargs_folder.py +16 -0
- diffeval/metrics/distribution/kwargs_stats.py +8 -0
- diffeval/metrics/distribution/protocol.py +12 -0
- diffeval/metrics/distribution/seed_numpy.py +12 -0
- diffeval/metrics/fid/__init__.py +1 -0
- diffeval/metrics/fid/guard.py +13 -0
- diffeval/metrics/fid/metric.py +20 -0
- diffeval/metrics/geneval/__init__.py +1 -0
- diffeval/metrics/geneval/metric.py +22 -0
- diffeval/metrics/geneval/protocol.py +5 -0
- diffeval/metrics/geneval/tags.py +3 -0
- diffeval/metrics/images.py +12 -0
- diffeval/metrics/kid/__init__.py +1 -0
- diffeval/metrics/kid/guard.py +3 -0
- diffeval/metrics/kid/metric.py +20 -0
- diffeval/metrics/ok.py +9 -0
- diffeval/metrics/protocol.py +17 -0
- diffeval/metrics/skipped.py +10 -0
- diffeval/plugins.py +7 -0
- diffeval/registry/__init__.py +1 -0
- diffeval/registry/lookup.py +9 -0
- diffeval/registry/names.py +5 -0
- diffeval/registry/register.py +11 -0
- diffeval/registry/tables.py +5 -0
- diffeval/registry/unregister.py +5 -0
- diffeval/report/__init__.py +1 -0
- diffeval/report/console.py +18 -0
- diffeval/report/console_widths.py +5 -0
- diffeval/report/direction.py +7 -0
- diffeval/report/html_figure.py +9 -0
- diffeval/report/html_grid.py +19 -0
- diffeval/report/html_page.py +15 -0
- diffeval/report/html_row.py +12 -0
- diffeval/report/markdown.py +11 -0
- diffeval/report/markdown_row.py +10 -0
- diffeval/report/number.py +7 -0
- diffeval/report/templates/page.html +35 -0
- diffeval/runner/__init__.py +1 -0
- diffeval/runner/generate_save.py +19 -0
- diffeval/runner/metric_device.py +16 -0
- diffeval/runner/one_seed.py +20 -0
- diffeval/runner/options.py +23 -0
- diffeval/runner/payload.py +26 -0
- diffeval/runner/relpath.py +7 -0
- diffeval/runner/requests.py +7 -0
- diffeval/runner/run.py +24 -0
- diffeval/runner/safe_metric.py +14 -0
- diffeval/runner/save_png.py +8 -0
- diffeval/runner/seeds.py +23 -0
- diffeval/runner/write_json.py +11 -0
- diffeval/runner/write_reports.py +14 -0
- diffeval/system/__init__.py +1 -0
- diffeval/system/describe.py +10 -0
- diffeval/system/gpu.py +11 -0
- diffeval/system/gpu_cuda.py +9 -0
- diffeval/system/gpu_mps.py +8 -0
- diffeval/system/sdp_flags.py +15 -0
- diffeval/system/version_of.py +10 -0
- diffeval/system/versions.py +20 -0
- diffeval/tasks/__init__.py +3 -0
- diffeval/tasks/build_suite.py +7 -0
- diffeval/tasks/core.py +15 -0
- diffeval/tasks/errors.py +5 -0
- diffeval/tasks/fields_metrics.py +14 -0
- diffeval/tasks/fields_paths.py +25 -0
- diffeval/tasks/fields_prompts.py +15 -0
- diffeval/tasks/fields_seeds.py +14 -0
- diffeval/tasks/fields_size.py +12 -0
- diffeval/tasks/fields_text.py +13 -0
- diffeval/tasks/from_mapping.py +22 -0
- diffeval/tasks/load_bundled.py +9 -0
- diffeval/tasks/load_path.py +12 -0
- diffeval/tasks/one_prompt.py +17 -0
- diffeval/tasks/prompt_id.py +13 -0
- diffeval/tasks/read_yaml.py +14 -0
- diffeval/tasks/resolve.py +18 -0
- diffeval/tasks/suites/core.yaml +12 -0
- diffeval/tasks/suites/references/core/blue_sphere.png +0 -0
- diffeval/tasks/suites/references/core/green_triangle.png +0 -0
- diffeval/tasks/suites/references/core/red_cube.png +0 -0
- diffeval/tasks/suites/references/core/yellow_star.png +0 -0
- diffeval/types/__init__.py +1 -0
- diffeval/types/context.py +15 -0
- diffeval/types/prompt.py +10 -0
- diffeval/types/request.py +13 -0
- diffeval/types/result.py +14 -0
- diffeval/types/result_rules.py +13 -0
- diffeval/types/sample.py +15 -0
- diffeval/types/suite.py +21 -0
- diffeval-0.1.0.dist-info/METADATA +378 -0
- diffeval-0.1.0.dist-info/RECORD +185 -0
- diffeval-0.1.0.dist-info/WHEEL +5 -0
- diffeval-0.1.0.dist-info/entry_points.txt +6 -0
- diffeval-0.1.0.dist-info/licenses/LICENSE +190 -0
- diffeval-0.1.0.dist-info/top_level.txt +1 -0
diffeval/__init__.py
ADDED
diffeval/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Mean and sample standard deviation across seeds."""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""One aggregate row."""
|
|
2
|
+
|
|
3
|
+
from diffeval.aggregate.mean import mean
|
|
4
|
+
from diffeval.aggregate.sample_std import sample_std
|
|
5
|
+
from diffeval.aggregate.series import series
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _status(statuses: list) -> str:
|
|
9
|
+
ok_n = sum(item == "ok" for item in statuses)
|
|
10
|
+
if statuses and ok_n == len(statuses):
|
|
11
|
+
return "ok"
|
|
12
|
+
if ok_n:
|
|
13
|
+
return "partial"
|
|
14
|
+
if statuses and not any(item == "error" for item in statuses):
|
|
15
|
+
return "skipped"
|
|
16
|
+
return "error"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def row(per_seed: list[dict], name: str) -> dict:
|
|
20
|
+
data = series(per_seed, name)
|
|
21
|
+
values = [item for item in data["per_seed"] if item is not None]
|
|
22
|
+
data.update(mean=mean(values), std=sample_std(values), n=len(values))
|
|
23
|
+
data["status"] = _status(data.pop("statuses"))
|
|
24
|
+
data["name"] = name
|
|
25
|
+
data["std_ddof"] = 1
|
|
26
|
+
return data
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Per-seed values and statuses for one metric."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def series(per_seed: list[dict], name: str) -> dict:
|
|
5
|
+
values = []
|
|
6
|
+
statuses = []
|
|
7
|
+
higher = None
|
|
8
|
+
for seed in per_seed:
|
|
9
|
+
metrics = seed.get("metrics", [])
|
|
10
|
+
match = next((item for item in metrics if item["name"] == name), None)
|
|
11
|
+
if match is None:
|
|
12
|
+
values.append(None)
|
|
13
|
+
statuses.append("skipped")
|
|
14
|
+
elif match["status"] != "ok":
|
|
15
|
+
values.append(None)
|
|
16
|
+
statuses.append(match["status"])
|
|
17
|
+
else:
|
|
18
|
+
values.append(match["value"])
|
|
19
|
+
statuses.append("ok")
|
|
20
|
+
if match is not None and higher is None:
|
|
21
|
+
higher = match.get("higher_is_better")
|
|
22
|
+
return {"per_seed": values, "statuses": statuses, "higher_is_better": higher}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Diffusers text-to-image backend."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Dispatch sdpa, xformers, eager, or detect."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.diffusers.attention_eager import apply_eager
|
|
4
|
+
from diffeval.backends.diffusers.attention_name import attention_name
|
|
5
|
+
from diffeval.backends.diffusers.attention_sdpa import apply_sdpa
|
|
6
|
+
from diffeval.backends.diffusers.attention_xformers import apply_xformers
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def apply_attention(pipe, choice: str | None) -> str:
|
|
10
|
+
if choice == "sdpa":
|
|
11
|
+
apply_sdpa(pipe)
|
|
12
|
+
elif choice == "eager":
|
|
13
|
+
apply_eager(pipe)
|
|
14
|
+
elif choice == "xformers":
|
|
15
|
+
apply_xformers(pipe)
|
|
16
|
+
return attention_name(pipe)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Classify the current attention processor."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def attention_name(pipe) -> str:
|
|
5
|
+
processors = getattr(pipe, "attn_processors", None) or {}
|
|
6
|
+
if not processors:
|
|
7
|
+
return "none"
|
|
8
|
+
cls = type(next(iter(processors.values()))).__name__
|
|
9
|
+
if "xformers" in cls.lower():
|
|
10
|
+
return "xformers"
|
|
11
|
+
if cls == "AttnProcessor2_0":
|
|
12
|
+
return "sdpa"
|
|
13
|
+
if cls == "AttnProcessor":
|
|
14
|
+
return "eager"
|
|
15
|
+
return cls
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Bind and register the diffusers backend."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.diffusers.describe import describe as describe_state
|
|
4
|
+
from diffeval.backends.diffusers.from_config import from_config as read_config
|
|
5
|
+
from diffeval.backends.diffusers.generate import generate as render
|
|
6
|
+
from diffeval.registry.register import register
|
|
7
|
+
from diffeval.registry.tables import BACKENDS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register(BACKENDS, "diffusers")
|
|
11
|
+
class DiffusersBackend:
|
|
12
|
+
description = "Diffusers AutoPipelineForText2Image."
|
|
13
|
+
|
|
14
|
+
def __init__(self, config: dict):
|
|
15
|
+
self.config, self.state = config, {}
|
|
16
|
+
|
|
17
|
+
def prepare(self) -> None:
|
|
18
|
+
from diffeval.backends.diffusers.load_pipe import prepare_pipe
|
|
19
|
+
self.state = prepare_pipe(self.config)
|
|
20
|
+
|
|
21
|
+
def generate(self, requests):
|
|
22
|
+
return render(self.state["pipe"], requests, self.config["batch_size"], self.state["sampling"]["steps"], self.state["sampling"]["guidance"])
|
|
23
|
+
|
|
24
|
+
def describe(self) -> dict:
|
|
25
|
+
return describe_state(self.config, self.state)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def from_config(config: dict):
|
|
29
|
+
return DiffusersBackend(read_config(config))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Compose pipeline call kwargs."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.diffusers.kwargs_generator import generators
|
|
4
|
+
from diffeval.backends.diffusers.kwargs_prompt import prompt_kwargs
|
|
5
|
+
from diffeval.backends.diffusers.kwargs_sampling import sampling_kwargs
|
|
6
|
+
from diffeval.backends.diffusers.kwargs_size import size_kwargs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def call_kwargs(pipe, requests, steps, guidance) -> dict:
|
|
10
|
+
body = prompt_kwargs(requests)
|
|
11
|
+
body.update(sampling_kwargs(pipe, steps, guidance))
|
|
12
|
+
body.update(size_kwargs(pipe, requests[0].width, requests[0].height))
|
|
13
|
+
gens = generators([item.seed for item in requests])
|
|
14
|
+
body["generator"] = gens[0] if len(requests) == 1 else gens
|
|
15
|
+
return body
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Load float16 and bfloat16 as float32 on CPU."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.diffusers.dtype_name import parse_dtype, torch_dtype
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def coerce_dtype(requested: str | None, device: str):
|
|
7
|
+
name = parse_dtype(requested)
|
|
8
|
+
if device == "cpu" and name in ("float16", "bfloat16"):
|
|
9
|
+
name = "float32"
|
|
10
|
+
return torch_dtype(name), name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""describe() after the pipeline loads."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def describe(config: dict, state: dict) -> dict:
|
|
5
|
+
sampling = state.get("sampling") or {"steps": None, "guidance": None, "steps_source": "pipeline-default", "guidance_source": "pipeline-default"}
|
|
6
|
+
return {"name": "diffusers", "pipeline": config.get("pipeline"), "dtype": state.get("dtype_text"), "requested_dtype": config.get("dtype"), "steps": sampling["steps"], "guidance": sampling["guidance"], "steps_source": sampling["steps_source"], "guidance_source": sampling["guidance_source"], "batch_size": config.get("batch_size", 1), "device": state.get("device"), "attention_backend": state.get("attention"), "variant": config.get("variant")}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Map dtype strings to torch dtypes."""
|
|
2
|
+
|
|
3
|
+
_NAMES = {"float32": "float32", "fp32": "float32", "float16": "float16", "fp16": "float16", "bfloat16": "bfloat16", "bf16": "bfloat16"}
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_dtype(text: str | None) -> str | None:
|
|
7
|
+
if text is None:
|
|
8
|
+
return None
|
|
9
|
+
if text not in _NAMES:
|
|
10
|
+
raise ValueError(f"unknown dtype {text}")
|
|
11
|
+
return _NAMES[text]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def torch_dtype(name: str | None):
|
|
15
|
+
import torch
|
|
16
|
+
table = {"float32": torch.float32, "float16": torch.float16, "bfloat16": torch.bfloat16}
|
|
17
|
+
return None if name is None else table[name]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def label(dtype) -> str | None:
|
|
21
|
+
if dtype is None:
|
|
22
|
+
return None
|
|
23
|
+
return str(dtype).split(".")[-1]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Require --pipeline and store backend settings."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def from_config(config: dict) -> dict:
|
|
5
|
+
pipeline = config.get("pipeline")
|
|
6
|
+
if not isinstance(pipeline, str) or not pipeline:
|
|
7
|
+
raise ValueError("diffusers backend requires --pipeline")
|
|
8
|
+
raw = config.get("batch_size", 1)
|
|
9
|
+
if type(raw) is not int or raw < 1:
|
|
10
|
+
raise ValueError("batch_size must be a positive int")
|
|
11
|
+
attention = config.get("attention")
|
|
12
|
+
if attention not in (None, "sdpa", "xformers", "eager"):
|
|
13
|
+
raise ValueError("attention must be sdpa, xformers, or eager")
|
|
14
|
+
keys = ("dtype", "steps", "guidance", "variant", "device")
|
|
15
|
+
stored = {key: config.get(key) for key in keys}
|
|
16
|
+
stored.update(pipeline=pipeline, batch_size=raw, attention=attention)
|
|
17
|
+
return stored
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Walk requests in batches."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.diffusers.one_batch import one_batch
|
|
4
|
+
from diffeval.types.sample import Sample
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _sample(request, image) -> Sample:
|
|
8
|
+
return Sample(request.prompt_id, request.prompt, request.seed, request.width, request.height, image, None, request.negative_prompt)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate(pipe, requests, batch_size, steps, guidance):
|
|
12
|
+
samples = []
|
|
13
|
+
size = max(1, int(batch_size))
|
|
14
|
+
for start in range(0, len(requests), size):
|
|
15
|
+
chunk = list(requests[start:start + size])
|
|
16
|
+
images = one_batch(pipe, chunk, steps, guidance)
|
|
17
|
+
samples.extend(_sample(request, image) for request, image in zip(chunk, images))
|
|
18
|
+
return samples
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Prompt and negative prompt arguments."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def prompt_kwargs(requests) -> dict:
|
|
5
|
+
prompts = [item.prompt for item in requests]
|
|
6
|
+
body = {"prompt": prompts[0] if len(requests) == 1 else prompts}
|
|
7
|
+
negatives = [item.negative_prompt for item in requests]
|
|
8
|
+
if any(negatives):
|
|
9
|
+
filled = [text or "" for text in negatives]
|
|
10
|
+
body["negative_prompt"] = filled[0] if len(requests) == 1 else filled
|
|
11
|
+
return body
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Steps and guidance when the pipeline accepts them."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _accepts(pipe, name: str) -> bool:
|
|
7
|
+
params = inspect.signature(pipe.__call__).parameters
|
|
8
|
+
varied = any(item.kind == inspect.Parameter.VAR_KEYWORD for item in params.values())
|
|
9
|
+
return name in params or varied
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def sampling_kwargs(pipe, steps, guidance) -> dict:
|
|
13
|
+
body = {}
|
|
14
|
+
if steps is not None and _accepts(pipe, "num_inference_steps"):
|
|
15
|
+
body["num_inference_steps"] = steps
|
|
16
|
+
if guidance is not None and _accepts(pipe, "guidance_scale"):
|
|
17
|
+
body["guidance_scale"] = guidance
|
|
18
|
+
return body
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Width and height when the pipeline accepts them."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _accepts(pipe, name: str) -> bool:
|
|
7
|
+
params = inspect.signature(pipe.__call__).parameters
|
|
8
|
+
varied = any(item.kind == inspect.Parameter.VAR_KEYWORD for item in params.values())
|
|
9
|
+
return name in params or varied
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def size_kwargs(pipe, width, height) -> dict:
|
|
13
|
+
body = {}
|
|
14
|
+
if _accepts(pipe, "width"):
|
|
15
|
+
body["width"] = width
|
|
16
|
+
if _accepts(pipe, "height"):
|
|
17
|
+
body["height"] = height
|
|
18
|
+
return body
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Load AutoPipelineForText2Image and move it to the device."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def load_pipe(pipeline_id, kwargs, device):
|
|
5
|
+
from diffusers import AutoPipelineForText2Image
|
|
6
|
+
return AutoPipelineForText2Image.from_pretrained(pipeline_id, **kwargs).to(device)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def prepare_pipe(config: dict) -> dict:
|
|
10
|
+
from diffeval.backends.diffusers.attention_apply import apply_attention
|
|
11
|
+
from diffeval.backends.diffusers.coerce_dtype import coerce_dtype
|
|
12
|
+
from diffeval.backends.diffusers.dtype_name import label
|
|
13
|
+
from diffeval.backends.diffusers.load_kwargs import load_kwargs
|
|
14
|
+
from diffeval.backends.diffusers.missing_dep import require_diffusers
|
|
15
|
+
from diffeval.backends.diffusers.pick_device import pick_device
|
|
16
|
+
from diffeval.backends.diffusers.sampling import sampling
|
|
17
|
+
require_diffusers()
|
|
18
|
+
device = pick_device(config.get("device"))
|
|
19
|
+
dtype, name = coerce_dtype(config.get("dtype"), device)
|
|
20
|
+
pipe = load_pipe(config["pipeline"], load_kwargs(config, dtype), device)
|
|
21
|
+
seen = label(getattr(pipe, "dtype", None)) or name
|
|
22
|
+
attention = apply_attention(pipe, config.get("attention"))
|
|
23
|
+
return {"pipe": pipe, "device": device, "dtype_text": seen, "sampling": sampling(config), "attention": attention}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Error telling the user to install the extra."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def require_diffusers() -> None:
|
|
5
|
+
try:
|
|
6
|
+
import diffusers
|
|
7
|
+
import torch
|
|
8
|
+
except ImportError as exc:
|
|
9
|
+
raise RuntimeError("install the diffusers extra: pip install 'diffeval[diffusers]'") from exc
|
|
10
|
+
return diffusers, torch
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""One pipeline call for a same-size batch."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.diffusers.call_kwargs import call_kwargs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def one_batch(pipe, requests, steps, guidance):
|
|
7
|
+
if len({(item.width, item.height) for item in requests}) != 1:
|
|
8
|
+
raise ValueError("a batch must share width and height")
|
|
9
|
+
result = pipe(**call_kwargs(pipe, requests, steps, guidance))
|
|
10
|
+
images = result.images if hasattr(result, "images") else result[0]
|
|
11
|
+
return list(images)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Pick cuda, mps, or cpu."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def pick_device(requested: str | None) -> str:
|
|
5
|
+
if requested:
|
|
6
|
+
return requested
|
|
7
|
+
import torch
|
|
8
|
+
if torch.cuda.is_available():
|
|
9
|
+
return "cuda"
|
|
10
|
+
mps = getattr(torch.backends, "mps", None)
|
|
11
|
+
if mps is not None and mps.is_available():
|
|
12
|
+
return "mps"
|
|
13
|
+
return "cpu"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Resolve steps and guidance to cli, preset, or pipeline-default."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.diffusers.presets import preset_for
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _one(cli_value, preset_value):
|
|
7
|
+
if cli_value is not None:
|
|
8
|
+
return cli_value, "cli"
|
|
9
|
+
if preset_value is not None:
|
|
10
|
+
return preset_value, "preset"
|
|
11
|
+
return None, "pipeline-default"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sampling(config: dict) -> dict:
|
|
15
|
+
found = preset_for(config["pipeline"])
|
|
16
|
+
steps, steps_source = _one(config.get("steps"), found.get("steps"))
|
|
17
|
+
guidance, guidance_source = _one(config.get("guidance"), found.get("guidance"))
|
|
18
|
+
return {"steps": steps, "guidance": guidance, "steps_source": steps_source, "guidance_source": guidance_source}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Deterministic RGB images for CPU tests."""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Bind and register the dummy backend."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.dummy.describe import describe as describe_config
|
|
4
|
+
from diffeval.backends.dummy.from_config import from_config as read_config
|
|
5
|
+
from diffeval.backends.dummy.generate import generate as render_batch
|
|
6
|
+
from diffeval.backends.noop_prepare import prepare as noop
|
|
7
|
+
from diffeval.registry.register import register
|
|
8
|
+
from diffeval.registry.tables import BACKENDS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@register(BACKENDS, "dummy")
|
|
12
|
+
class DummyBackend:
|
|
13
|
+
description = "Deterministic RGB images for CPU tests."
|
|
14
|
+
|
|
15
|
+
def __init__(self, config: dict):
|
|
16
|
+
self.config = config
|
|
17
|
+
|
|
18
|
+
def prepare(self) -> None:
|
|
19
|
+
noop()
|
|
20
|
+
|
|
21
|
+
def generate(self, requests):
|
|
22
|
+
return render_batch(requests, self.config["batch_size"])
|
|
23
|
+
|
|
24
|
+
def describe(self) -> dict:
|
|
25
|
+
return describe_config(self.config)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def from_config(config: dict):
|
|
29
|
+
return DummyBackend(read_config(config))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Seed-shifted box inside the image."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def box(seed: int, width: int, height: int) -> tuple[int, int, int, int]:
|
|
5
|
+
span_w = max(1, width // 4)
|
|
6
|
+
span_h = max(1, height // 4)
|
|
7
|
+
left = abs(seed) % max(1, width - span_w + 1)
|
|
8
|
+
top = abs(seed // 3) % max(1, height - span_h + 1)
|
|
9
|
+
return left, top, left + span_w, top + span_h
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Background and foreground from a prompt hash."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def colors(prompt: str) -> tuple[tuple[int, int, int], tuple[int, int, int]]:
|
|
7
|
+
digest = hashlib.sha256(prompt.encode("utf-8")).digest()
|
|
8
|
+
background = (digest[0], digest[1], digest[2])
|
|
9
|
+
foreground = ((digest[3] + 128) % 256, digest[4], digest[5])
|
|
10
|
+
return background, foreground
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""describe() for the dummy backend."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def describe(config: dict) -> dict:
|
|
5
|
+
return {
|
|
6
|
+
"name": "dummy",
|
|
7
|
+
"pipeline": config.get("pipeline"),
|
|
8
|
+
"dtype": config.get("dtype"),
|
|
9
|
+
"requested_dtype": config.get("dtype"),
|
|
10
|
+
"steps": config.get("steps"),
|
|
11
|
+
"guidance": config.get("guidance"),
|
|
12
|
+
"steps_source": "pipeline-default",
|
|
13
|
+
"guidance_source": "pipeline-default",
|
|
14
|
+
"batch_size": config["batch_size"],
|
|
15
|
+
"device": "cpu",
|
|
16
|
+
"attention_backend": "none",
|
|
17
|
+
"variant": config.get("variant"),
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Draw a rectangle, ellipse, or triangle."""
|
|
2
|
+
|
|
3
|
+
from PIL import ImageDraw
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def draw(image, shape: str, box, color):
|
|
7
|
+
pen = ImageDraw.Draw(image)
|
|
8
|
+
if shape == "ellipse":
|
|
9
|
+
pen.ellipse(box, fill=color)
|
|
10
|
+
elif shape == "triangle":
|
|
11
|
+
left, top, right, bottom = box
|
|
12
|
+
mid = (left + right) // 2
|
|
13
|
+
pen.polygon([(left, bottom), (mid, top), (right, bottom)], fill=color)
|
|
14
|
+
else:
|
|
15
|
+
pen.rectangle(box, fill=color)
|
|
16
|
+
return image
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Batch size and an unused pipeline id."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def from_config(config: dict) -> dict:
|
|
5
|
+
raw = config.get("batch_size", 1)
|
|
6
|
+
if type(raw) is not int or raw < 1:
|
|
7
|
+
raise ValueError("batch_size must be a positive int")
|
|
8
|
+
pipeline = config.get("pipeline")
|
|
9
|
+
if pipeline is not None and not isinstance(pipeline, str):
|
|
10
|
+
raise ValueError("pipeline must be a string")
|
|
11
|
+
kept = ("dtype", "steps", "guidance", "variant")
|
|
12
|
+
stored = {key: config.get(key) for key in kept}
|
|
13
|
+
stored.update(batch_size=raw, pipeline=pipeline)
|
|
14
|
+
return stored
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Generate samples in batch-size slices."""
|
|
2
|
+
|
|
3
|
+
from diffeval.backends.dummy.render import render
|
|
4
|
+
from diffeval.types.sample import Sample
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _one(request) -> Sample:
|
|
8
|
+
image = render(request.prompt, request.seed, request.width, request.height)
|
|
9
|
+
return Sample(request.prompt_id, request.prompt, request.seed, request.width, request.height, image, None, request.negative_prompt)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate(requests, batch_size: int) -> list:
|
|
13
|
+
size = max(1, batch_size)
|
|
14
|
+
samples = []
|
|
15
|
+
for start in range(0, len(requests), size):
|
|
16
|
+
samples.extend(_one(item) for item in requests[start:start + size])
|
|
17
|
+
return samples
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""One RGB image for a prompt and a seed."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
from PIL import Image
|
|
6
|
+
|
|
7
|
+
from diffeval.backends.dummy.box import box
|
|
8
|
+
from diffeval.backends.dummy.colors import colors
|
|
9
|
+
from diffeval.backends.dummy.draw import draw
|
|
10
|
+
|
|
11
|
+
_SHAPES = ("rectangle", "ellipse", "triangle")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def render(prompt: str, seed: int, width: int, height: int) -> Image.Image:
|
|
15
|
+
background, foreground = colors(prompt)
|
|
16
|
+
image = Image.new("RGB", (width, height), background)
|
|
17
|
+
kind = _SHAPES[hashlib.sha256(prompt.encode("utf-8")).digest()[6] % 3]
|
|
18
|
+
return draw(image, kind, box(seed, width, height), foreground)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Backend contract."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from diffeval.types.request import GenerationRequest
|
|
6
|
+
from diffeval.types.sample import Sample
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Backend(Protocol):
|
|
10
|
+
description: str
|
|
11
|
+
|
|
12
|
+
def from_config(self, config: dict) -> "Backend": ...
|
|
13
|
+
def prepare(self) -> None: ...
|
|
14
|
+
def generate(self, requests: list[GenerationRequest]) -> list[Sample]: ...
|
|
15
|
+
def describe(self) -> dict: ...
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Benchmark helpers: the CMMD check, the COCO-30k suite, and the results table."""
|