- nvfp4: _quantization_metadata -> comfy_quant synthesis, SlotEntry extra params (block_scale), dual-scale LoRA fold, orig_dtype fixup - LoRA: plain-mode block LoRAs no longer silently dropped; folded on GPU after weights land - loader: parallel positioned reads, pipelined group load, no per-tensor clone, header-only dtype probes - block swap: read_block intra-block parallel + disk_workers node option; async D2H write-back on transfer stream; lazy evict_all skips wasted write-back - arch: split models/loader.py out of wan_model.py; single utils/keys.py; LoRA folding consolidated in utils/lora.py; global utils/log.py ([BerniniR][Tag] format, BERNINI_LOG_LEVEL)
228 lines
8.4 KiB
Python
228 lines
8.4 KiB
Python
"""
|
|
Centralised injection context for the Bernini-R sampling pipeline.
|
|
|
|
``InjectionContext`` is built once at the start of ``bernini_sample()`` /
|
|
``bernini_sample_dual()`` and holds all conditioning-extracted data, config
|
|
values, and parsed arguments that previously were scattered across ~100 lines
|
|
of ad-hoc extraction and ``extra_model_options`` mutation.
|
|
|
|
Having a single source of truth:
|
|
* Eliminates the duplicated extraction in the single- and dual-expert paths.
|
|
* Makes ``extra_model_options`` construction a single ``apply_options()`` call.
|
|
* Lets ``BerniniModelWrapper`` receive pre-extracted state instead of reading
|
|
from raw conditioning dicts and the diffusion model directly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
import torch
|
|
|
|
from .parsing import parse_stg_block_indices
|
|
from .types import (
|
|
BerniniBlockSwap,
|
|
BerniniContext,
|
|
BerniniGuidanceConfig,
|
|
Conditioning,
|
|
)
|
|
|
|
from .log import get_logger as _get_logger
|
|
|
|
logger = _get_logger("Inject")
|
|
@dataclass
|
|
class InjectionContext:
|
|
"""All injection data extracted once at sampling start.
|
|
|
|
Built by :meth:`build` from the raw node inputs, then consumed by
|
|
:meth:`apply_options`, :meth:`apply_block_swap`, and
|
|
:meth:`apply_noise`.
|
|
|
|
Attributes prefixed with ``_dd_`` feed the differential-diffusion path
|
|
inside :class:`~.bernini_sampling.BerniniModelWrapper`.
|
|
"""
|
|
|
|
# -- extracted from positive conditioning ---------------------------------
|
|
nag_context: torch.Tensor | None = None
|
|
nag_params: dict[str, Any] | None = None
|
|
|
|
# -- differential-diffusion (extracted from conditioning, consumed by
|
|
# BerniniModelWrapper) --------------------------------------------------
|
|
dd_edit_mask: torch.Tensor | None = None
|
|
dd_mask_mode: str = "anneal"
|
|
dd_src_latent: torch.Tensor | None = None
|
|
|
|
# -- from config nodes ----------------------------------------------------
|
|
freenoise: bool = False
|
|
block_to_swap: int = 0
|
|
block_swap_prefetch: bool = True
|
|
block_swap_prefetch_count: int = 1
|
|
block_swap_pin_memory: bool = False
|
|
block_swap_loading_mode: str = "Streaming"
|
|
context_window_wrapper: callable | None = None
|
|
|
|
# -- parsed STG -----------------------------------------------------------
|
|
stg_blocks: list[int] = field(default_factory=list)
|
|
stg_mode: str = "A"
|
|
|
|
# ------------------------------------------------------------------
|
|
# Factory
|
|
# ------------------------------------------------------------------
|
|
|
|
@classmethod
|
|
def build(
|
|
cls,
|
|
positive,
|
|
context_options: BerniniContext | None,
|
|
block_swap_args: BerniniBlockSwap | None,
|
|
guidance_config: BerniniGuidanceConfig | None,
|
|
total_blocks: int,
|
|
context_window_wrapper: callable | None = None,
|
|
) -> "InjectionContext":
|
|
"""Extract all injection data from node inputs in one call.
|
|
|
|
Parameters
|
|
----------
|
|
positive:
|
|
ComfyUI CONDITIONING list (``[[tensor, dict], ...]``).
|
|
context_options:
|
|
:class:`BerniniContext` or ``None``.
|
|
block_swap_args:
|
|
:class:`BerniniBlockSwap` or ``None``.
|
|
guidance_config:
|
|
:class:`BerniniGuidanceConfig` or ``None`` (defaults to CFG).
|
|
total_blocks:
|
|
Number of transformer blocks in the model (for STG parsing).
|
|
context_window_wrapper:
|
|
Pre-built context-window wrapper callable, or ``None``.
|
|
"""
|
|
ctx = cls()
|
|
|
|
# -- conditioning extraction ------------------------------------------
|
|
if positive:
|
|
pos_cond = Conditioning.from_comfy(positive)
|
|
|
|
ctx.nag_context = pos_cond.get_extra("nag_prompt_embeds")
|
|
ctx.nag_params = pos_cond.get_extra("nag_params")
|
|
if ctx.nag_context is not None and ctx.nag_params:
|
|
logger.info(
|
|
"NAG enabled: scale=%.1f, tau=%.1f, alpha=%.2f",
|
|
ctx.nag_params.get("nag_scale", 0),
|
|
ctx.nag_params.get("nag_tau", 0),
|
|
ctx.nag_params.get("nag_alpha", 0),
|
|
)
|
|
|
|
# Differential-diffusion
|
|
_edit_mask = pos_cond.get_extra("edit_mask")
|
|
if _edit_mask is not None:
|
|
ctx.dd_edit_mask = _edit_mask
|
|
ctx.dd_mask_mode = pos_cond.get_extra("mask_mode", "anneal")
|
|
_src = pos_cond.get_extra("context_latents")
|
|
if _src is not None:
|
|
if hasattr(_src, "cond"):
|
|
_src = _src.cond
|
|
if _src:
|
|
ctx.dd_src_latent = _src[0]
|
|
|
|
# -- config nodes -----------------------------------------------------
|
|
if context_options is not None:
|
|
ctx.freenoise = context_options.freenoise
|
|
if block_swap_args is not None:
|
|
ctx.block_to_swap = block_swap_args.block_to_swap
|
|
ctx.block_swap_prefetch = block_swap_args.prefetch
|
|
ctx.block_swap_prefetch_count = block_swap_args.prefetch_count
|
|
ctx.block_swap_pin_memory = block_swap_args.pin_memory
|
|
ctx.block_swap_loading_mode = block_swap_args.loading_mode
|
|
|
|
ctx.context_window_wrapper = context_window_wrapper
|
|
|
|
# -- parsed STG / guidance -------------------------------------------
|
|
if guidance_config is not None:
|
|
if guidance_config.stg_block_idx.strip():
|
|
ctx.stg_blocks = parse_stg_block_indices(
|
|
guidance_config.stg_block_idx, total_blocks)
|
|
ctx.stg_mode = (
|
|
guidance_config.mode.value[4:]
|
|
if guidance_config.mode.value.startswith("STG") else "A"
|
|
)
|
|
else:
|
|
ctx.stg_mode = "A"
|
|
|
|
return ctx
|
|
|
|
# ------------------------------------------------------------------
|
|
# Apply methods — each mutates its target in one shot
|
|
# ------------------------------------------------------------------
|
|
|
|
def apply_options(
|
|
self,
|
|
extra_model_options: dict,
|
|
sigmas: torch.Tensor,
|
|
batch: int,
|
|
) -> None:
|
|
"""Apply all ``transformer_options`` / ``model_options`` injections.
|
|
|
|
Call this **once** after ``create_model_options_clone()`` and before
|
|
``model.pre_run()``.
|
|
"""
|
|
to = extra_model_options.setdefault("transformer_options", {})
|
|
|
|
# 1. sample_sigmas — always injected (TeaCache + hooks need it)
|
|
to["sample_sigmas"] = sigmas
|
|
|
|
# 2. NAG — inject into cloned options so cache doesn't leak
|
|
if self.nag_context is not None and self.nag_params:
|
|
nag_ctx = self.nag_context
|
|
if batch > 1 and nag_ctx.shape[0] == 1:
|
|
nag_ctx = nag_ctx.repeat(batch, 1, 1)
|
|
to.update({
|
|
"nag_context": nag_ctx,
|
|
"nag_params": self.nag_params,
|
|
})
|
|
|
|
# 3. Context window wrapper — on clone only, no patcher mutation
|
|
if self.context_window_wrapper is not None:
|
|
extra_model_options["model_function_wrapper"] = (
|
|
self.context_window_wrapper
|
|
)
|
|
|
|
def apply_block_swap(
|
|
self,
|
|
extra_model_options: dict,
|
|
inner_model,
|
|
) -> None:
|
|
"""Disable torch.compile if block swap is active (incompatible).
|
|
|
|
The ``BlockSwapManager`` itself reads config from ``_block_swap_config``
|
|
on the diffusion model, not from transformer_options.
|
|
"""
|
|
if self.block_to_swap <= 0:
|
|
return
|
|
|
|
dm = getattr(inner_model, "diffusion_model", inner_model)
|
|
total_blocks = len(dm.blocks) if hasattr(dm, "blocks") else 0
|
|
if total_blocks <= 0:
|
|
return
|
|
|
|
# Dynamic GPU↔CPU block loading breaks the traced graph.
|
|
_orig = getattr(dm, "_original_transformer_forward", None)
|
|
if _orig is not None:
|
|
dm.transformer_forward = _orig
|
|
logger.warning(
|
|
"Block swap is incompatible with "
|
|
"torch.compile — compile disabled for this run."
|
|
)
|
|
|
|
def apply_noise(
|
|
self,
|
|
noise: torch.Tensor,
|
|
context_options: BerniniContext | None,
|
|
seed: int,
|
|
) -> torch.Tensor:
|
|
"""Apply FreeNoise shuffling if enabled. Returns (possibly new) noise."""
|
|
if not self.freenoise or context_options is None:
|
|
return noise
|
|
from ..nodes.sampler import _apply_freenoise
|
|
return _apply_freenoise(noise, context_options, seed=seed)
|