feat(loaders): load .gguf diffusion models and text encoders

Core lists no .gguf, so the files were invisible. Backend is whichever is
installed: ComfyUI-GGUF keeps weights quantized, the gguf package
dequantizes at load. Family detection reads GGUF containers too.
This commit is contained in:
digital-garbage
2026-08-22 23:13:46 +03:00
parent 13bcdb24f3
commit b57db12eaf
6 changed files with 513 additions and 14 deletions
+9
View File
@@ -3,6 +3,15 @@
## [Unreleased]
### Added
- **FunPack's loaders take `.gguf` files**, for diffusion models and text encoders alike, and
a text-encoder list may mix a `.gguf` with `.safetensors` slots (the usual LTX-2.3 shape).
Core's extension set has no `.gguf`, so those files were on disk and invisible to every
picker. The runtime is whichever backend is present: ComfyUI-GGUF keeps the weights
quantized in VRAM, which is the point of GGUF; failing that, the `gguf` package is used to
dequantize at load — the file loads, but at full size, and the status output says which of
the two happened. With neither, the loader names both remedies instead of failing obscurely.
Model-family detection reads GGUF containers too, using the same architecture signatures.
- **Pin up to three shortcuts to the timeline toolbar.** Settings is a window with a sidebar,
and its useful places — a specific node's page, one Engine category — are several clicks
deep: fine to walk once, tedious while dialling something in. `📌 Pin to a button` in the
+230
View File
@@ -0,0 +1,230 @@
"""Load .gguf model files through FunPack's own loaders.
GGUF is two problems, and only the first is small:
1. **Reading the container.** A header, tensor metadata, then quantized blocks. The `gguf`
package (llama.cpp's own) does this, and ships a reference dequantizer for every quant
type it defines.
2. **Using the weights without dequantizing them.** The reason people reach for GGUF is that
a Q4 model occupies a quarter of the VRAM — which only holds if the weights STAY
quantized and are expanded per-layer during the forward pass. That needs a custom
torch ops class, and hand-written kernels per quant type.
So this module does not hand-roll (2). It picks the best backend actually present:
* **ComfyUI-GGUF installed** — use it. It is the mature implementation of (2), and its
output feeds `comfy.sd.load_diffusion_model_state_dict` exactly like ours does, so
FunPack's loader stays the only UI while its runtime does the quantized maths.
* **`gguf` package only** — read and dequantize at LOAD time, then hand over an ordinary
state dict. Fully native and correct, but the model occupies its dequantized size, so
the VRAM saving is gone. Said out loud rather than left to be discovered.
* **Neither** — refuse with the two ways to fix it, instead of a stack trace.
Nothing here downloads or installs anything.
"""
from __future__ import annotations
import importlib.util
import os
from typing import Optional
GGUF_EXT = ".gguf"
def is_gguf(name: str) -> bool:
return str(name or "").lower().endswith(GGUF_EXT)
# ── finding .gguf files ───────────────────────────────────────────────────────
def gguf_names(folder: str) -> list[str]:
"""`.gguf` files under a ComfyUI model folder, relative-named like get_filename_list().
Core's `supported_pt_extensions` has no `.gguf`, so `get_filename_list` never returns
one — the files are on disk and simply invisible to every picker. Scanned directly
rather than by mutating core's global extension set, which would change what every
other node in the install offers.
"""
try:
import folder_paths
except ImportError:
return []
out: list[str] = []
seen: set[str] = set()
roots = list(folder_paths.get_folder_paths(folder) or [])
# ComfyUI-GGUF registers its own folders; include them under the same picker so a user
# who already keeps files there does not have to move them.
for extra in ("unet_gguf" if folder == "diffusion_models" else "clip_gguf",):
try:
roots += list(folder_paths.get_folder_paths(extra) or [])
except Exception:
pass
for root in roots:
if not root or not os.path.isdir(root):
continue
for dirpath, _dirs, files in os.walk(root, followlinks=True):
for f in files:
if not is_gguf(f):
continue
rel = os.path.relpath(os.path.join(dirpath, f), root)
if rel not in seen:
seen.add(rel)
out.append(rel)
return sorted(out)
def gguf_path(folder: str, name: str) -> Optional[str]:
"""Absolute path of a `.gguf` picked out of `gguf_names`."""
try:
import folder_paths
except ImportError:
return None
roots = list(folder_paths.get_folder_paths(folder) or [])
for extra in ("unet_gguf" if folder == "diffusion_models" else "clip_gguf",):
try:
roots += list(folder_paths.get_folder_paths(extra) or [])
except Exception:
pass
for root in roots:
cand = os.path.join(root or "", name)
if os.path.isfile(cand):
return cand
return None
# ── backends ──────────────────────────────────────────────────────────────────
def _load_pack_module(dirname: str, mod: str):
"""Import one module out of a custom-node package by path.
Custom node folders are not importable by name (``ComfyUI-GGUF`` is not an identifier),
so the file is loaded directly.
"""
spec = importlib.util.spec_from_file_location(
f"_funpack_gguf_{mod}", os.path.join(dirname, f"{mod}.py"))
if spec is None or spec.loader is None:
return None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _pack_dir() -> Optional[str]:
"""ComfyUI-GGUF's directory, identified by what it CONTAINS rather than by its name —
the folder gets renamed by Manager, by forks, and by hand."""
try:
import folder_paths
base = os.path.join(folder_paths.base_path, "custom_nodes")
except Exception:
return None
if not os.path.isdir(base):
return None
for entry in sorted(os.listdir(base)):
d = os.path.join(base, entry)
loader = os.path.join(d, "loader.py")
if not os.path.isfile(loader) or not os.path.isfile(os.path.join(d, "ops.py")):
continue
try:
with open(loader, "r", encoding="utf-8", errors="replace") as fh:
if "gguf_sd_loader" in fh.read():
return d
except OSError:
continue
return None
def backend() -> Optional[str]:
""""pack" (quantized in VRAM), "native" (dequantized at load), or None."""
if _pack_dir():
return "pack"
if importlib.util.find_spec("gguf") is not None:
return "native"
return None
UNAVAILABLE = (
"No GGUF backend is available. Either install ComfyUI-GGUF (custom node pack — keeps the "
"weights quantized in VRAM, which is the point of GGUF), or `pip install gguf` into "
"ComfyUI's environment for a load-time dequantize that works but gives back no memory."
)
def load_state_dict(path: str) -> tuple[dict, dict, str]:
"""Read one .gguf into (state_dict, model_options, note).
`model_options` carries the custom operations the quantized path needs; it is empty for
the native path, whose tensors are ordinary ones by the time they are returned.
"""
which = backend()
if which is None:
raise RuntimeError(UNAVAILABLE)
if which == "pack":
d = _pack_dir()
loader = _load_pack_module(d, "loader")
ops = _load_pack_module(d, "ops")
sd = loader.gguf_sd_loader(path)
options = {}
ggml_ops = getattr(ops, "GGMLOps", None)
if ggml_ops is not None:
options["custom_operations"] = ggml_ops()
return sd, options, f"gguf: quantized (ComfyUI-GGUF at {os.path.basename(d)})"
return _load_native(path)
def load_clip_state_dict(path: str) -> tuple[dict, dict, str]:
"""Same as `load_state_dict`, for a text encoder.
ComfyUI-GGUF reads encoders through a separate entry point: an encoder's GGUF carries
llama.cpp tensor names that have to be mapped back to the transformer's, which its
diffusion-model reader does not do.
"""
which = backend()
if which is None:
raise RuntimeError(UNAVAILABLE)
if which == "pack":
d = _pack_dir()
loader = _load_pack_module(d, "loader")
ops = _load_pack_module(d, "ops")
read = getattr(loader, "gguf_clip_loader", None) or loader.gguf_sd_loader
sd = read(path)
options = {}
ggml_ops = getattr(ops, "GGMLOps", None)
if ggml_ops is not None:
options["custom_operations"] = ggml_ops()
return sd, options, f"gguf: quantized (ComfyUI-GGUF at {os.path.basename(d)})"
return _load_native(path)
def _load_native(path: str) -> tuple[dict, dict, str]:
"""Dequantize every tensor to torch at load time using gguf's reference implementation.
Correct but not thrifty: the model ends up at its dequantized size, so this buys the
ability to LOAD a .gguf, not GGUF's memory saving. The caller states that.
"""
import gguf
import torch
reader = gguf.GGUFReader(path)
sd: dict = {}
quantized = 0
for tensor in reader.tensors:
name = str(tensor.name)
qtype = tensor.tensor_type
if qtype in (gguf.GGMLQuantizationType.F32, gguf.GGMLQuantizationType.F16):
arr = tensor.data
else:
# gguf.quants.dequantize is the reference implementation for every type the
# package knows — do not reimplement it per quant type.
arr = gguf.quants.dequantize(tensor.data, qtype)
quantized += 1
t = torch.from_numpy(arr.copy())
# GGUF stores shapes reversed relative to torch.
shape = tuple(int(d) for d in reversed(tensor.shape))
if shape and t.numel() == int(torch.tensor(shape).prod()):
t = t.reshape(shape)
sd[name] = t.to(torch.float16) if t.dtype == torch.float32 else t
note = (f"gguf: dequantized at load ({quantized} quantized tensors expanded) — the file "
f"loads, but it occupies its full size in VRAM. Install ComfyUI-GGUF to keep it "
f"quantized.")
return sd, {}, note
+75 -14
View File
@@ -19,11 +19,29 @@ import torch
try:
from .widgets import field, list_widget, parse_rows
from . import sla_attention
from . import gguf_support, sla_attention
except ImportError: # standalone tests import the modules directly
from widgets import field, list_widget, parse_rows
import gguf_support
import sla_attention
def model_file_choices():
"""Diffusion model files, `.gguf` included.
Core's extension set has no `.gguf`, so those files are on disk and invisible to every
picker. Appended rather than merged in sorted order, so an existing pipeline's saved
choice keeps its position in the list and nothing a user already picked moves.
"""
return list(folder_paths.get_filename_list("diffusion_models")) + \
gguf_support.gguf_names("diffusion_models")
def encoder_file_choices():
"""Text encoder files, `.gguf` included. Same reasoning as model_file_choices()."""
return list(folder_paths.get_filename_list("text_encoders")) + \
gguf_support.gguf_names("text_encoders")
WEIGHT_DTYPES = ["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2", "fp16", "bf16", "fp32"]
COMPUTE_DTYPES = ["default", "fp16", "bf16", "fp32"]
VAE_DTYPES = ["default", "fp16", "bf16", "fp32"]
@@ -124,8 +142,10 @@ class FunPackDiffusionModelLoader:
def INPUT_TYPES(cls):
return {
"required": {
"model_name": (folder_paths.get_filename_list("diffusion_models"), {
"tooltip": "The diffusion model file, from ComfyUI/models/diffusion_models."}),
"model_name": (model_file_choices(), {
"tooltip": "The diffusion model file, from ComfyUI/models/diffusion_models. "
".gguf files are listed too (see the status output for which "
"GGUF backend loaded it)."}),
"weight_dtype": (WEIGHT_DTYPES, {
"default": "default",
"tooltip": "How weights are stored in VRAM. fp8_e4m3fn roughly halves the "
@@ -220,8 +240,20 @@ class FunPackDiffusionModelLoader:
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)
if gguf_support.is_gguf(model_name):
path = gguf_support.gguf_path("diffusion_models", model_name)
if not path:
raise RuntimeError(f"ERROR: {model_name} is no longer where it was listed from.")
state_dict, gguf_options, gguf_note = gguf_support.load_state_dict(path)
# The quantized path needs its own torch operations, and they must not be lost to
# the dtype options merged above — a GGUF loaded with stock ops would try to matmul
# block-quantized storage.
model_options = {**model_options, **gguf_options}
metadata = None
notes.append(gguf_note)
else:
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:
@@ -283,7 +315,7 @@ class FunPackCLIPLoader:
@classmethod
def INPUT_TYPES(cls):
encoders = folder_paths.get_filename_list("text_encoders")
encoders = encoder_file_choices()
types = cls.clip_types()
return {
"required": {
@@ -292,7 +324,8 @@ class FunPackCLIPLoader:
[field("clip_name", "combo", label="file", choices=encoders)],
add_label="+ Add slot",
tooltip="Text encoder files, in load order. One slot for LTX-2.5 (Gemma4); "
"two for LTX-2.3 (Gemma3 + its connector)."),
"two for LTX-2.3 (Gemma3 + its connector). .gguf files are listed "
"too, and may be mixed with .safetensors ones."),
"type": (types, {
"default": "ltxv" if "ltxv" in types else (types[0] if types else "ltxv"),
"tooltip": "Which model family the encoder is for. LTX-2 (all point "
@@ -317,15 +350,43 @@ class FunPackCLIPLoader:
if device == "cpu":
model_options["load_device"] = model_options["offload_device"] = torch.device("cpu")
paths = [folder_paths.get_full_path_or_raise("text_encoders", n) for n in names]
clip = comfy.sd.load_clip(
ckpt_paths=paths,
embedding_directory=folder_paths.get_folder_paths("embeddings"),
clip_type=clip_type,
model_options=model_options,
)
gguf_notes = []
if any(gguf_support.is_gguf(n) for n in names):
# A .gguf encoder cannot go through load_clip(), which reads files itself. Every
# slot becomes a state dict instead, so a GGUF and a .safetensors connector can
# sit in the same list — which is the normal LTX-2.3 shape.
state_dicts = []
for n in names:
if gguf_support.is_gguf(n):
gpath = gguf_support.gguf_path("text_encoders", n)
if not gpath:
raise RuntimeError(f"FunPack CLIP Loader: {n} is no longer where it "
f"was listed from.")
sd, gopts, gnote = gguf_support.load_clip_state_dict(gpath)
state_dicts.append(sd)
model_options = {**model_options, **gopts}
gguf_notes.append(f"{n}: {gnote}")
else:
state_dicts.append(comfy.utils.load_torch_file(
folder_paths.get_full_path_or_raise("text_encoders", n)))
clip = comfy.sd.load_text_encoder_state_dicts(
state_dicts,
embedding_directory=folder_paths.get_folder_paths("embeddings"),
clip_type=clip_type,
model_options=model_options,
)
else:
paths = [folder_paths.get_full_path_or_raise("text_encoders", n) for n in names]
clip = comfy.sd.load_clip(
ckpt_paths=paths,
embedding_directory=folder_paths.get_folder_paths("embeddings"),
clip_type=clip_type,
model_options=model_options,
)
status = "FunPack CLIP Loader | type={} device={}\n{}".format(
type, device, "\n".join(f" {i + 1}. {n}" for i, n in enumerate(names)))
for gn in gguf_notes:
status += f"\n {gn}"
return (clip, status)
+32
View File
@@ -56,6 +56,25 @@ def read_safetensors_keys(path: str | Path) -> Optional[list[str]]:
return [k for k in header if k != "__metadata__"]
def read_gguf_keys(path: str | Path) -> Optional[list[str]]:
"""Tensor names from a GGUF file, or None when they cannot be read.
Delegated to llama.cpp's own `gguf` package rather than hand-parsing the container:
the header is a typed key-value section of variable-length records, and getting that
subtly wrong would misidentify a model rather than fail. The package is optional, so
None here means "cannot tell", never "wrong family".
"""
try:
import gguf # noqa: PLC0415
except ImportError:
return None
try:
reader = gguf.GGUFReader(str(path))
return [str(t.name) for t in reader.tensors]
except Exception: # noqa: BLE001 — any malformed container is simply unreadable
return None
def detect_arch(keys: Iterable[str]) -> Optional[str]:
"""`minimax_h3` | `ltxav` | `ltxv`, or None when the keys match no architecture we wire.
@@ -86,6 +105,13 @@ def detect_family(path: str | Path) -> dict:
if not p.is_file():
return {"family": None, "arch": None, "detected": False,
"reason": f"{p.name}: file not found"}
if p.suffix.lower() == ".gguf":
keys = read_gguf_keys(p)
if keys is None:
return {"family": None, "arch": None, "detected": False,
"reason": f"{p.name}: a .gguf can only be inspected with the `gguf` package "
f"installed — set the family from Models ▸ Model family instead"}
return _from_keys(p, keys)
if p.suffix.lower() != ".safetensors":
# .ckpt / .pth are pickles; reading them means executing them, which is not worth it
# for a family probe. The declared family stands and the caller is told why.
@@ -95,6 +121,12 @@ def detect_family(path: str | Path) -> dict:
if keys is None:
return {"family": None, "arch": None, "detected": False,
"reason": f"{p.name}: not a readable safetensors header"}
return _from_keys(p, keys)
def _from_keys(p: Path, keys: Iterable[str]) -> dict:
"""Same verdict from either container: the signatures are key names, so it does not
matter which format they were read out of."""
arch = detect_arch(keys)
if arch is None:
return {"family": None, "arch": None, "detected": False,
+38
View File
@@ -148,3 +148,41 @@ def test_an_unidentifiable_checkpoint_proposes_no_family(tmp_path):
out = model_probe.probe_models({"slots": [slot(model_name="wan.safetensors")]},
resolve=lambda name: str(f))
assert out["family"] is None
def test_gguf_without_the_library_says_so_and_proposes_no_family(tmp_path, monkeypatch):
"""A .gguf that cannot be read must never fall through to a guess — the previous family
stands, and the panel says why plus where to set it by hand."""
import movie_editor.backend.model_probe as mp
p = tmp_path / "ltx-Q4_K_M.gguf"
p.write_bytes(b"GGUF\x03\x00\x00\x00")
monkeypatch.setattr(mp, "read_gguf_keys", lambda _p: None)
out = mp.detect_family(p)
assert out["family"] is None
assert out["detected"] is False
assert "gguf" in out["reason"].lower()
assert "Model family" in out["reason"]
def test_gguf_is_identified_from_the_same_signatures(tmp_path, monkeypatch):
"""The architecture signatures are KEY NAMES, so the container they came out of is
irrelevant — a GGUF and a safetensors of the same model must agree."""
import movie_editor.backend.model_probe as mp
p = tmp_path / "h3-Q8_0.gguf"
p.write_bytes(b"GGUF")
monkeypatch.setattr(mp, "read_gguf_keys",
lambda _p: ["video_patch_proj.weight", "audio_patch_proj.weight"])
out = mp.detect_family(p)
assert out["arch"] == "minimax_h3"
assert out["family"] == "minimax_h3"
assert out["detected"] is True
def test_gguf_with_no_known_signature_proposes_nothing(tmp_path, monkeypatch):
import movie_editor.backend.model_probe as mp
p = tmp_path / "something.gguf"
p.write_bytes(b"GGUF")
monkeypatch.setattr(mp, "read_gguf_keys", lambda _p: ["blk.0.attn_q.weight"])
out = mp.detect_family(p)
assert out["family"] is None
assert "no LTX or MiniMax H3 signature" in out["reason"]
+129
View File
@@ -0,0 +1,129 @@
"""GGUF backend selection and file discovery.
Nothing here needs a .gguf file or the gguf package: what is testable is the routing —
which backend gets picked, what happens when none is available, and whether .gguf files
are found at all (core's extension set excludes them, which is the whole reason this
module exists).
"""
import sys
import types
import pytest
sys.path.insert(0, ".")
import gguf_support # noqa: E402
@pytest.fixture
def fake_folders(tmp_path, monkeypatch):
"""A stand-in folder_paths with two model roots on disk."""
diff = tmp_path / "diffusion_models"
enc = tmp_path / "text_encoders"
sub = diff / "nested"
for d in (diff, enc, sub):
d.mkdir(parents=True, exist_ok=True)
(diff / "ltx.safetensors").write_bytes(b"x")
(diff / "ltx-Q4_K_M.gguf").write_bytes(b"x")
(sub / "h3-Q8_0.gguf").write_bytes(b"x")
(enc / "gemma.safetensors").write_bytes(b"x")
(enc / "gemma-Q5_K.gguf").write_bytes(b"x")
mod = types.ModuleType("folder_paths")
mod.base_path = str(tmp_path)
roots = {"diffusion_models": [str(diff)], "text_encoders": [str(enc)]}
mod.get_folder_paths = lambda name: roots.get(name, [])
monkeypatch.setitem(sys.modules, "folder_paths", mod)
return tmp_path
def test_is_gguf_is_case_insensitive():
assert gguf_support.is_gguf("model.gguf")
assert gguf_support.is_gguf("MODEL.GGUF")
assert not gguf_support.is_gguf("model.safetensors")
assert not gguf_support.is_gguf("")
assert not gguf_support.is_gguf(None)
def test_finds_gguf_files_core_cannot_list(fake_folders):
names = gguf_support.gguf_names("diffusion_models")
assert "ltx-Q4_K_M.gguf" in names
# Nested, because model folders are routinely organised into subdirectories.
assert any(n.endswith("h3-Q8_0.gguf") for n in names)
# Only .gguf — the ordinary files still come from core's own listing.
assert not any(n.endswith(".safetensors") for n in names)
def test_encoders_are_listed_separately(fake_folders):
assert gguf_support.gguf_names("text_encoders") == ["gemma-Q5_K.gguf"]
def test_missing_folder_is_empty_not_an_error(fake_folders):
assert gguf_support.gguf_names("vae") == []
def test_path_resolves_a_listed_name(fake_folders):
p = gguf_support.gguf_path("diffusion_models", "ltx-Q4_K_M.gguf")
assert p and p.endswith("ltx-Q4_K_M.gguf")
assert gguf_support.gguf_path("diffusion_models", "not-there.gguf") is None
def test_no_backend_refuses_with_both_remedies(monkeypatch):
monkeypatch.setattr(gguf_support, "_pack_dir", lambda: None)
monkeypatch.setattr(gguf_support.importlib.util, "find_spec", lambda name: None)
assert gguf_support.backend() is None
with pytest.raises(RuntimeError) as e:
gguf_support.load_state_dict("/nope.gguf")
msg = str(e.value)
# Both ways out must be named: they have different consequences for VRAM.
assert "ComfyUI-GGUF" in msg
assert "pip install gguf" in msg
def test_the_pack_wins_when_both_are_available(monkeypatch):
monkeypatch.setattr(gguf_support, "_pack_dir", lambda: "/fake/ComfyUI-GGUF")
monkeypatch.setattr(gguf_support.importlib.util, "find_spec", lambda name: object())
# Quantized-in-VRAM is the point of GGUF; the dequantizing fallback is second best.
assert gguf_support.backend() == "pack"
def test_native_is_used_when_only_the_library_is_present(monkeypatch):
monkeypatch.setattr(gguf_support, "_pack_dir", lambda: None)
monkeypatch.setattr(gguf_support.importlib.util, "find_spec",
lambda name: object() if name == "gguf" else None)
assert gguf_support.backend() == "native"
def test_pack_is_found_by_content_not_by_folder_name(tmp_path, monkeypatch):
"""Manager, forks and hand-installs all rename the folder; the marker is the code."""
cn = tmp_path / "custom_nodes"
good = cn / "some-renamed-fork"
decoy = cn / "ComfyUI-GGUF-Themed-Nodes" # named right, contains nothing
for d in (good, decoy):
d.mkdir(parents=True)
(good / "loader.py").write_text("def gguf_sd_loader(path):\n return {}\n")
(good / "ops.py").write_text("class GGMLOps:\n pass\n")
(decoy / "loader.py").write_text("# nothing useful\n")
mod = types.ModuleType("folder_paths")
mod.base_path = str(tmp_path)
mod.get_folder_paths = lambda name: []
monkeypatch.setitem(sys.modules, "folder_paths", mod)
assert gguf_support._pack_dir() == str(good)
def test_pack_load_passes_custom_operations_through(tmp_path, monkeypatch):
"""The quantized weights are unusable without their ops — losing them would produce a
matmul against block-quantized storage rather than a clear failure."""
d = tmp_path / "pack"
d.mkdir()
(d / "loader.py").write_text(
"def gguf_sd_loader(path):\n return {'w': 'quantized'}\n")
(d / "ops.py").write_text("class GGMLOps:\n pass\n")
monkeypatch.setattr(gguf_support, "_pack_dir", lambda: str(d))
sd, options, note = gguf_support.load_state_dict("/whatever.gguf")
assert sd == {"w": "quantized"}
assert "custom_operations" in options
assert type(options["custom_operations"]).__name__ == "GGMLOps"
assert "quantized" in note