Files
olivv-cs-ComfyUI-FunPack/modules/loaders/diffusion_model/nodes.py
T
digital-garbageandClaude Opus 5 74671329f9 log: a severity, a source, and a message
[Log] Info: FunPack Diffusion Model Loader | flux.safetensors loaded, attention sage
    [Log] Alert: MiniMaxH3ImageToVideo node | no "first_image" input is connected
    [Log] Warning: FunPack LoRA Loader | style.safetensors is empty and will not load
    [Log] Critical: torch | cannot allocate 14 GB (3 GB free); the run will not start

The difference between the middle two is the one that matters. Alert is a
choice you may not have made on purpose. Warning is something that will NOT
do what its name says -- the level v4 needed and did not have, which is how
560 handlers came to swallow a failure and carry on, so a feature could be
switched on, do nothing, and never say so.

Info is not decoration either: "loaded on GPU using sage attention" is how
an attention setting that did nothing gets noticed months before the
pictures look wrong enough to investigate.

Every call site moved -- there is no note() left to be vague through -- and
the loaders now say what they actually loaded rather than only what they
were asked for. Records are kept in a bounded ring as well as printed, so a
long rental session cannot turn the log into a memory leak, and served at
/funpack/api/log so a person can read it somewhere other than a terminal
full of ComfyUI's own output. There is a page for it.

Also dropped the preflight this was traded for: min and max are already
declared and already enforced at both doors, and a second protection layer
around a check that exists is ceremony.

Two mistakes worth keeping: a log line reached into the model's shape to
describe it and could have failed the load it was only narrating; and both
new pages passed `children` to an element that takes `rows`, which the kit
accepted and silently dropped. It throws on an unknown element and not on an
unknown prop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 17:09:33 +03:00

102 lines
5.2 KiB
Python

"""The diffusion model loader.
Picking a file and how to read it. Every option here changes what the loaded
model IS, which is why they are node widgets and not settings: nobody downstream
can supply them, and a saved workflow has to record them.
"""
import comfy.sd
import comfy.utils
import folder_paths
from comfy_api.latest import io
from ..._core import log
from ..common import (COMPUTE_DTYPES, WEIGHT_DTYPES, attention_choices,
attention_override, dtype_of, set_fp16_accumulation,
weight_model_options)
class FunPackDiffusionModelLoader(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="FunPackDiffusionModelLoader",
display_name="FunPack Diffusion Model Loader",
category="FunPack/Loaders",
description="Load a diffusion model, choosing precision and attention backend.",
inputs=[
io.Combo.Input("model_name", options=folder_paths.get_filename_list("diffusion_models")),
io.Combo.Input("weight_dtype", options=WEIGHT_DTYPES, default="default",
tooltip="How the weights are stored. 'default' keeps whatever "
"the file already is."),
io.Combo.Input("compute_dtype", options=COMPUTE_DTYPES, default="default",
tooltip="What the maths runs in, independently of storage."),
io.Combo.Input("attention", options=attention_choices(), default="default",
tooltip="Attention backend. Only what this machine can run is "
"listed; 'default' keeps ComfyUI's launch choice."),
io.Boolean.Input("fp16_accumulation", default=False, optional=True,
tooltip="Faster fp16 matmuls where the torch build supports it."),
],
outputs=[
io.Model.Output(display_name="model"),
io.String.Output(display_name="status"),
],
)
@classmethod
def execute(cls, model_name: str, weight_dtype: str, compute_dtype: str,
attention: str, fp16_accumulation: bool = False) -> io.NodeOutput:
notes = [f"FunPack Diffusion Model Loader | {model_name}"]
accumulation = set_fp16_accumulation(fp16_accumulation)
if fp16_accumulation and accumulation is None:
notes.append("fp16_accumulation: unsupported by this torch build, ignored")
elif accumulation is not None:
notes.append(f"fp16_accumulation: {accumulation}")
model_options = weight_model_options(weight_dtype)
notes.append(f"weight dtype: {weight_dtype}")
path = folder_paths.get_full_path_or_raise("diffusion_models", model_name)
state_dict, metadata = comfy.utils.load_torch_file(path, return_metadata=True)
model = comfy.sd.load_diffusion_model_state_dict(
state_dict, model_options=model_options, metadata=metadata)
if model is None:
# comfy returns None rather than raising, and a None reaching the
# sampler fails much further away than the mistake.
raise RuntimeError(
f"Could not detect a diffusion model in {model_name}. Loading a text "
f"encoder or a VAE file here is the usual cause.")
dtype = dtype_of(compute_dtype)
if dtype is not None and hasattr(model, "set_model_compute_dtype"):
# Do NOT clear force_cast_weights afterwards. set_model_compute_dtype
# sets it deliberately, and it is what casts each layer's WEIGHTS to
# match. Without it the model still casts its INPUT to the requested
# dtype (_apply_model reads manual_cast_dtype), so the first Linear
# gets a bf16 activation against fp32 weights and sampling dies with
# "mat1 and mat2 must have the same dtype". v4 cleared it and shipped
# that way; it survives on constrained hardware only because the
# low-VRAM path forces the cast back on regardless.
model.set_model_compute_dtype(dtype)
notes.append(f"compute dtype: {compute_dtype}")
elif dtype is not None:
notes.append("compute dtype: unsupported by this ComfyUI, ignored")
override = attention_override(attention)
if override is not None:
model.model_options.setdefault("transformer_options", {})[
"optimized_attention_override"] = override
notes.append(f"attention: {attention}")
else:
notes.append("attention: default (as launched)")
# Never reach into the model's shape to describe it: a log line that
# assumes structure can fail the load it was only meant to narrate.
kind = type(getattr(model, "model", model)).__name__
log.info("FunPack Diffusion Model Loader",
f"{model_name} loaded as {kind}, weights "
f"{weight_dtype}, compute {compute_dtype}, attention "
f"{attention if override is not None else 'default (as launched)'}")
return io.NodeOutput(model, "\n".join(notes))