189 lines
7.0 KiB
Python
189 lines
7.0 KiB
Python
# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
|
|
# Copyright (C) 2026 Artificial Sweetener and contributors
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
"""Verify benchmark-only outer Attention Coupling phase profiling."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from types import SimpleNamespace
|
|
from typing import Any, ClassVar
|
|
|
|
import torch
|
|
|
|
from simple_syrup.domain.conditioning_batch import ConditioningBatch
|
|
from simple_syrup.domain.regional_attention_execution import (
|
|
RegionalAttentionExecutionMode,
|
|
)
|
|
from simple_syrup.domain.regional_mask_bank import RegionalMaskBank
|
|
from simple_syrup.services.attention_coupling_model_preparation_service import (
|
|
AttentionCouplingModelPreparationService,
|
|
)
|
|
from simple_syrup.services.prepared_attention_coupling_model import (
|
|
PreparedAttentionCouplingModel,
|
|
)
|
|
from tools.attention_coupling_benchmark.comfy_probe import (
|
|
attention_coupling_phase_profile,
|
|
)
|
|
|
|
ProfiledAttentionCouplingSamplingService = (
|
|
attention_coupling_phase_profile.ProfiledAttentionCouplingSamplingService
|
|
)
|
|
|
|
_LOGGER_NAME = "simple_syrup.runtime.regional_lora.standard_unet_cold_path"
|
|
|
|
|
|
class _SamplingService:
|
|
"""Record exact delegate inputs and return one recognizable latent."""
|
|
|
|
calls: ClassVar[list[dict[str, object]]] = []
|
|
output: ClassVar[dict[str, Any]] = {"samples": torch.ones((1, 4, 2, 2))}
|
|
|
|
def sample(self, **arguments: object) -> dict[str, Any]:
|
|
"""Retain all delegate arguments."""
|
|
|
|
type(self).calls.append(arguments)
|
|
return self.output
|
|
|
|
|
|
def test_profiled_service_preserves_two_call_delegation_and_emits_phases(
|
|
caplog: Any,
|
|
) -> None:
|
|
"""Add timing evidence without changing preparation or sampling values."""
|
|
|
|
preparation_calls: list[dict[str, object]] = []
|
|
|
|
class _PreparationService(AttentionCouplingModelPreparationService):
|
|
"""Return one fixed prepared value and retain regional arguments."""
|
|
|
|
def prepare(
|
|
self,
|
|
**arguments: object,
|
|
) -> PreparedAttentionCouplingModel:
|
|
"""Record exact arguments and return a derived model triple."""
|
|
|
|
preparation_calls.append(arguments)
|
|
masks = torch.ones((1, 2, 2))
|
|
return PreparedAttentionCouplingModel(
|
|
"derived",
|
|
"prepared-positive",
|
|
"prepared-negative",
|
|
RegionalMaskBank(masks, masks.clone(), 2, 2),
|
|
)
|
|
|
|
original_preparation = (
|
|
ProfiledAttentionCouplingSamplingService.model_preparation_service_class
|
|
)
|
|
original_sampling = ProfiledAttentionCouplingSamplingService.sampling_service_class
|
|
ProfiledAttentionCouplingSamplingService.model_preparation_service_class = (
|
|
_PreparationService
|
|
)
|
|
ProfiledAttentionCouplingSamplingService.sampling_service_class = _SamplingService # type: ignore[assignment]
|
|
_SamplingService.calls = []
|
|
latent = {"samples": torch.zeros((1, 4, 2, 2))}
|
|
model = SimpleNamespace(load_device=torch.device("cpu"))
|
|
positive = ConditioningBatch(("global-positive", "regional-positive"))
|
|
negative = ConditioningBatch(("global-negative", "regional-negative"))
|
|
try:
|
|
with caplog.at_level(logging.DEBUG, logger=_LOGGER_NAME):
|
|
output = ProfiledAttentionCouplingSamplingService().sample(
|
|
model=model,
|
|
seed=7,
|
|
steps=30,
|
|
cfg=5.0,
|
|
sampler_name="sampler",
|
|
scheduler="scheduler",
|
|
positive=positive,
|
|
negative=negative,
|
|
region_masks="masks",
|
|
regional_prompt_weight=1.0,
|
|
region_mask_feather=0,
|
|
latent_image=latent,
|
|
denoise=1.0,
|
|
)
|
|
finally:
|
|
ProfiledAttentionCouplingSamplingService.model_preparation_service_class = (
|
|
original_preparation
|
|
)
|
|
ProfiledAttentionCouplingSamplingService.sampling_service_class = (
|
|
original_sampling
|
|
)
|
|
|
|
assert output is _SamplingService.output
|
|
assert preparation_calls[0]["execution_mode"] is RegionalAttentionExecutionMode.FULL
|
|
assert preparation_calls[0]["model"] is model
|
|
delegate = _SamplingService.calls[0]
|
|
assert delegate["model"] == "derived"
|
|
assert delegate["positive"] == "prepared-positive"
|
|
assert delegate["negative"] == "prepared-negative"
|
|
assert delegate["latent_image"] is latent
|
|
stages = [
|
|
record.cold_path_diagnostics["stage"]
|
|
for record in caplog.records
|
|
if hasattr(record, "cold_path_diagnostics")
|
|
]
|
|
assert stages == ["model_preparation_total", "ksampler_delegate_total"]
|
|
|
|
|
|
def test_profiled_service_bypasses_preparation_and_times_only_sampling(
|
|
caplog: Any,
|
|
) -> None:
|
|
"""Keep benchmark routing behavior identical to the production service."""
|
|
|
|
class _PreparationService(AttentionCouplingModelPreparationService):
|
|
"""Reject preparation for an ordinary request."""
|
|
|
|
def prepare(self, **arguments: object) -> PreparedAttentionCouplingModel:
|
|
"""Fail if the bypass enters the regional preparation owner."""
|
|
|
|
raise AssertionError(f"unexpected preparation: {arguments}")
|
|
|
|
original_preparation = (
|
|
ProfiledAttentionCouplingSamplingService.model_preparation_service_class
|
|
)
|
|
original_sampling = ProfiledAttentionCouplingSamplingService.sampling_service_class
|
|
ProfiledAttentionCouplingSamplingService.model_preparation_service_class = (
|
|
_PreparationService
|
|
)
|
|
ProfiledAttentionCouplingSamplingService.sampling_service_class = _SamplingService # type: ignore[assignment]
|
|
_SamplingService.calls = []
|
|
latent = {"samples": torch.zeros((1, 4, 2, 2))}
|
|
model = SimpleNamespace(load_device=torch.device("cpu"))
|
|
try:
|
|
with caplog.at_level(logging.DEBUG, logger=_LOGGER_NAME):
|
|
output = ProfiledAttentionCouplingSamplingService().sample(
|
|
model=model,
|
|
seed=11,
|
|
steps=20,
|
|
cfg=4.0,
|
|
sampler_name="sampler",
|
|
scheduler="scheduler",
|
|
positive="positive",
|
|
negative="negative",
|
|
region_masks=None,
|
|
regional_prompt_weight=0.5,
|
|
region_mask_feather=0,
|
|
latent_image=latent,
|
|
denoise=0.6,
|
|
)
|
|
finally:
|
|
ProfiledAttentionCouplingSamplingService.model_preparation_service_class = (
|
|
original_preparation
|
|
)
|
|
ProfiledAttentionCouplingSamplingService.sampling_service_class = (
|
|
original_sampling
|
|
)
|
|
|
|
assert output is _SamplingService.output
|
|
assert _SamplingService.calls[0]["model"] is model
|
|
assert _SamplingService.calls[0]["positive"] == "positive"
|
|
assert _SamplingService.calls[0]["negative"] == "negative"
|
|
assert _SamplingService.calls[0]["denoise"] == 0.6
|
|
stages = [
|
|
record.cold_path_diagnostics["stage"]
|
|
for record in caplog.records
|
|
if hasattr(record, "cold_path_diagnostics")
|
|
]
|
|
assert stages == ["ksampler_delegate_total"]
|