Files
Faych 5fa366d4d6 feat: Migrating the project into ComfyUI native style (#42)
* docs: design ComfyUI-native BAGEL migration

* docs: plan ComfyUI-native BAGEL migration

* docs: align BAGEL migration with custom node layout

* docs: define BAGEL test environment constraints

* docs: split BAGEL nodes by responsibility

* docs: retain flat custom node entrypoints

* feat: add converted BAGEL runtime loader

* feat: register native BAGEL ComfyUI nodes

* docs: document native BAGEL workflows

* fix: add custom node root to import path

* fix: log skipped BAGEL model discovery entries

* fix: prefer BAGEL model folder for native checkpoints

* feat: add BAGEL sidecar metadata helper

* fix: list BAGEL safetensors without metadata precheck

* fix: preserve BAGEL extra model paths

* fix: set Qwen token ids for BAGEL config fallback

* fix: decouple default Qwen RoPE from transformers registry

* feat: add streaming BAGEL metadata embedder

* feat: add free Colab metadata release workflow

* feat: describe BAGEL variant release metadata

* feat: prepare remaining BAGEL metadata releases

* fix: declare Echo-4o release as fp16

* fix: validate BAGEL latent image geometry

* fix: align BAGEL edit workflow preprocessing

* docs: add native BAGEL workflow figures

* chore: prepare 1.1.0 release contents

* docs: finalize release README and metadata validation

* fix: restore native BAGEL parity and lifecycle

* fix: align native controls with BAGEL app

* fix: migrate native thinking workflow controls

* docs: update native workflow examples

* fix: mark legacy nodes as deprecated
2026-07-31 01:56:08 +01:00

147 lines
5.3 KiB
Python

"""Standalone BAGEL metadata / tokenizer / variant inspector.
Reads ONLY the safetensors header (no tensor values) plus the packaged tokenizer
assets, and prints a JSON descriptor: variant, capability tier, capabilities,
metadata summary, tokenizer vocabulary fingerprint, and required special-token
IDs. Exits non-zero on a metadata/asset mismatch.
Usage:
python scripts/inspect_bagel_metadata.py models/bagel/bagel.safetensors
"""
from __future__ import annotations
import argparse
import importlib
import importlib.util
import json
import os
import sys
import tempfile
import types
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
def _install_stubs() -> None:
"""Stub flash_attn/dfloat11 only when the real GPU modules are absent.
On AutoDL the real modules exist and are used; locally / on Modal the CUDA
chain is unavailable, so we write on-disk import-compatible stubs to a temp
directory and prepend it to ``sys.path``. We never shadow an installed real
module.
"""
need = []
for name in ("flash_attn", "dfloat11"):
try:
importlib.import_module(name)
except Exception:
need.append(name)
if not need:
return
stub_dir = Path(tempfile.mkdtemp(prefix="bagel_stubs_"))
if "flash_attn" in need:
(stub_dir / "flash_attn.py").write_text(
"def flash_attn_varlen_func(*a, **k):\n"
" raise RuntimeError('flash_attn stub')\n"
"__version__ = '0.0.0-stub'\n"
)
if "dfloat11" in need:
(stub_dir / "dfloat11.py").write_text(
"class DFloat11Model:\n"
" @classmethod\n"
" def from_pretrained(cls, *a, **k):\n"
" raise RuntimeError('dfloat11 stub')\n"
"__version__ = '0.0.0-stub'\n"
)
sys.path.insert(0, str(stub_dir))
def _import_package_modules():
sys.path.insert(0, str(REPO))
from modeling.bagel import converted_format, model_types, variants # noqa: E402
from modeling.qwen2 import tokenizer_fingerprint as tf # noqa: E402
from modeling.qwen2.bagel_tokenizer import ASSETS_DIR # noqa: E402
return converted_format, model_types, variants, ASSETS_DIR, tf
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("model", help="converted BAGEL safetensors file")
args = parser.parse_args()
_install_stubs()
(
converted_format,
model_types,
variants,
ASSETS_DIR,
tf,
) = _import_package_modules()
import safetensors.torch
if not Path(args.model).exists():
print(json.dumps({"error": f"model not found: {args.model}"}, indent=2))
return 1
with safetensors.torch.safe_open(args.model, framework="pt", device="cpu") as f:
header_meta = f.metadata() or {}
tensor_keys = list(f.keys())
if "comfyui_bagel" not in header_meta:
print(json.dumps({
"error": "not a converted comfyui_bagel file (missing metadata header)",
"tier": model_types.CapabilityTier.UNSUPPORTED.value,
}, indent=2))
return 1
metadata = converted_format.ConvertedBagelMetadata.from_json(header_meta["comfyui_bagel"])
descriptor = variants.detect_variant(metadata, tensor_keys)
# Pure-stdlib tokenizer validation: canonical fingerprint over the packaged
# assets, per-file hash check against MANIFEST, and special-token cross-check.
tokenizer_fingerprint = tf.tokenizer_fingerprint(str(ASSETS_DIR))
special_token_ids = dict(metadata.special_token_ids or {})
manifest_path = Path(ASSETS_DIR) / "MANIFEST.json"
manifest = {}
if manifest_path.exists():
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
mismatches = []
if metadata.tokenizer_fingerprint and metadata.tokenizer_fingerprint != tokenizer_fingerprint:
mismatches.append(
f"tokenizer_fingerprint mismatch (metadata={metadata.tokenizer_fingerprint!r}, "
f"assets={tokenizer_fingerprint!r})"
)
if manifest.get("vocab_fingerprint_sha256") and \
manifest["vocab_fingerprint_sha256"] != tokenizer_fingerprint:
mismatches.append("tokenizer_fingerprint disagrees with MANIFEST")
for name, expected in (manifest.get("files") or {}).items():
actual = tf.file_hashes(str(ASSETS_DIR)).get(name)
if actual is None:
mismatches.append(f"tokenizer asset missing: {name}")
elif actual != expected.get("sha256"):
mismatches.append(f"tokenizer asset hash mismatch: {name}")
recorded_special = manifest.get("special_token_ids") or {}
for tok in ("<|im_start|>", "<|im_end|>", "<|vision_start|>", "<|vision_end|>"):
if recorded_special.get(tok) != special_token_ids.get(tok):
mismatches.append(f"special-token id mismatch for {tok}")
report = {
"model": args.model,
"descriptor": descriptor.to_dict(),
"metadata": metadata.to_dict(),
"tokenizer_fingerprint": tokenizer_fingerprint,
"manifest_fingerprint": manifest.get("vocab_fingerprint_sha256"),
"special_token_ids": special_token_ids,
"mismatches": mismatches,
}
print(json.dumps(report, indent=2, ensure_ascii=False))
return 1 if mismatches else 0
if __name__ == "__main__":
raise SystemExit(main())