Files
rookiestar28 fe87f7deac feat: align current ComfyUI host compatibility
Harden model loading and lifecycle behavior, align package metadata and optional dependency guidance, add workflow compatibility safeguards, and provide host-backed Classic and Vue frontend validation with native advanced inputs.
2026-08-09 02:19:16 +08:00

577 lines
19 KiB
Python

#!/usr/bin/env python3
"""Prepare and verify the repo-local TG-058 Transformers compatibility matrix.
Sources:
- https://pypi.org/project/transformers/
- https://github.com/huggingface/transformers/blob/main/MIGRATION_GUIDE_V5.md
- https://huggingface.co/docs/transformers/v5.0.0/en/main_classes/model
"""
from __future__ import annotations
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable
REPO_ROOT = Path(__file__).resolve().parents[1]
MATRIX_RELATIVE_ROOT = Path(".tmp") / "tg058"
PYTHON_VERSIONS = ("3.12", "3.13")
TRANSFORMERS_VERSIONS = ("4.57.3", "5.0.0", "5.14.1")
TORCH_VERSION = "2.10.0"
RESULT_SCHEMA_VERSION = 1
REQUIRED_SURFACES = (
"imports",
"auto_processor",
"auto_image_text_model",
"chat_template",
"load_kwargs",
"safetensors_local_load",
"generation",
"bnb_config_without_bnb",
"project_model_loader",
)
_ALLOWED_RESULT_KEYS = {
"schema_version",
"lane",
"python",
"transformers",
"torch",
"command",
"result",
"surfaces",
"error_category",
}
_PATH_ENV_KEYS = (
"CONDA_PKGS_DIRS",
"PIP_CACHE_DIR",
"HF_HOME",
"HUGGINGFACE_HUB_CACHE",
"TRANSFORMERS_CACHE",
"TORCH_HOME",
"XDG_CACHE_HOME",
"TMP",
"TEMP",
)
_SAFE_INHERITED_ENV_KEYS = (
"COMSPEC",
"LANG",
"NUMBER_OF_PROCESSORS",
"OS",
"PATH",
"PATHEXT",
"PROCESSOR_ARCHITECTURE",
"SYSTEMDRIVE",
"SYSTEMROOT",
"WINDIR",
)
_SAFE_CATEGORY = re.compile(r"^[A-Z][A-Z0-9_]*$")
_WINDOWS_ABSOLUTE_PATH = re.compile(r"[A-Za-z]:[\\/]")
class MatrixValidationError(ValueError):
"""Raised when a lane or output violates the frozen matrix contract."""
class MatrixSetupError(RuntimeError):
"""A categorized setup failure safe to expose without its raw cause."""
def __init__(self, category: str):
super().__init__(category)
self.category = category
@dataclass(frozen=True)
class MatrixRow:
python_version: str
transformers_version: str
@property
def lane_id(self) -> str:
compact_python = self.python_version.replace(".", "")
return f"py{compact_python}-tf{self.transformers_version}"
def matrix_rows() -> tuple[MatrixRow, ...]:
return tuple(
MatrixRow(python_version, transformers_version)
for python_version in PYTHON_VERSIONS
for transformers_version in TRANSFORMERS_VERSIONS
)
def _matrix_root(repo_root: Path) -> Path:
return (Path(repo_root).resolve() / MATRIX_RELATIVE_ROOT).resolve()
def resolve_output_path(repo_root: Path, requested: str | os.PathLike[str]) -> Path:
repo_root = Path(repo_root).resolve()
root = _matrix_root(repo_root)
requested_path = Path(requested)
if not requested_path.is_absolute():
requested_path = repo_root / requested_path
resolved = requested_path.resolve()
if resolved == root or not resolved.is_relative_to(root) or resolved.suffix.lower() != ".json":
raise MatrixValidationError("OUTPUT_PATH_OUTSIDE_MATRIX_ROOT")
return resolved
def build_matrix_environment(repo_root: Path, *, probe_offline: bool) -> dict[str, str]:
repo_root = Path(repo_root).resolve()
root = _matrix_root(repo_root)
environment = {
key: os.environ[key]
for key in _SAFE_INHERITED_ENV_KEYS
if key in os.environ
}
environment.update(
{
"CONDA_PKGS_DIRS": str(root / "cache" / "conda-pkgs"),
"CONDA_ENVS_PATH": str(root / "conda-envs"),
"PIP_CACHE_DIR": str(root / "cache" / "pip"),
"PIP_DISABLE_PIP_VERSION_CHECK": "1",
"PIP_NO_INPUT": "1",
"HF_HOME": str(root / "cache" / "huggingface"),
"HUGGINGFACE_HUB_CACHE": str(root / "cache" / "huggingface" / "hub"),
"TRANSFORMERS_CACHE": str(root / "cache" / "transformers"),
"TORCH_HOME": str(root / "cache" / "torch"),
"XDG_CACHE_HOME": str(root / "cache" / "xdg"),
"TMP": str(root / "temp"),
"TEMP": str(root / "temp"),
"HOME": str(root / "home"),
"USERPROFILE": str(root / "home"),
"APPDATA": str(root / "home" / "AppData" / "Roaming"),
"LOCALAPPDATA": str(root / "home" / "AppData" / "Local"),
"CONDARC": str(root / "config" / "condarc"),
"PYTHONUTF8": "1",
"TOKENIZERS_PARALLELISM": "false",
"HF_HUB_DISABLE_TELEMETRY": "1",
"DO_NOT_TRACK": "1",
"USERNAME": "tg058",
"USER": "tg058",
"LOGNAME": "tg058",
}
)
if probe_offline:
environment["HF_HUB_OFFLINE"] = "1"
environment["TRANSFORMERS_OFFLINE"] = "1"
return environment
def _ensure_matrix_directories(repo_root: Path) -> None:
root = _matrix_root(repo_root)
for relative in (
Path("cache/conda-pkgs"),
Path("cache/pip"),
Path("cache/huggingface/hub"),
Path("cache/transformers"),
Path("cache/torch"),
Path("cache/xdg"),
Path("conda-envs"),
Path("bases"),
Path("lanes"),
Path("work"),
Path("temp"),
Path("home/AppData/Roaming"),
Path("home/AppData/Local"),
Path("config"),
):
(root / relative).mkdir(parents=True, exist_ok=True)
def _python_executable(prefix: Path, *, venv: bool) -> Path:
if os.name == "nt":
return prefix / ("Scripts/python.exe" if venv else "python.exe")
return prefix / ("bin/python")
def _run_setup(command: list[str], environment: dict[str, str], category: str) -> None:
try:
completed = subprocess.run(
command,
env=environment,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
shell=False,
)
except OSError as error:
raise MatrixSetupError(category) from None
if completed.returncode != 0:
raise MatrixSetupError(category)
def _installed_version(python: Path, distribution: str, environment: dict[str, str]) -> str | None:
command = [
str(python),
"-c",
(
"import importlib.metadata as m; "
f"print(m.version({distribution!r}))"
),
]
try:
completed = subprocess.run(
command,
env=environment,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
encoding="utf-8",
check=False,
shell=False,
)
except OSError:
return None
if completed.returncode != 0:
return None
return completed.stdout.strip()
def _prepare_base(repo_root: Path, python_version: str) -> Path:
root = _matrix_root(repo_root)
environment = build_matrix_environment(repo_root, probe_offline=False)
conda = shutil.which("conda", path=environment.get("PATH"))
if not conda:
raise MatrixSetupError("CONDA_UNAVAILABLE")
prefix = root / "bases" / f"py{python_version.replace('.', '')}"
python = _python_executable(prefix, venv=False)
if not python.is_file():
print(f"PREPARE py{python_version.replace('.', '')}-base")
_run_setup(
[
conda,
"create",
"--yes",
"--override-channels",
"--channel",
"conda-forge",
"--prefix",
str(prefix),
f"python={python_version}",
"pip",
],
environment,
"PYTHON_BASE_SETUP_FAILURE",
)
if not python.is_file():
raise MatrixSetupError("PYTHON_BASE_MISSING")
torch_version = _installed_version(python, "torch", environment)
if torch_version is None or not torch_version.startswith(TORCH_VERSION):
print(f"PREPARE py{python_version.replace('.', '')}-torch")
_run_setup(
[
str(python),
"-m",
"pip",
"install",
"--only-binary=:all:",
"--index-url",
"https://download.pytorch.org/whl/cpu",
f"torch=={TORCH_VERSION}",
],
environment,
"TORCH_SETUP_FAILURE",
)
torch_version = _installed_version(python, "torch", environment)
if torch_version is None or not torch_version.startswith(TORCH_VERSION):
raise MatrixSetupError("TORCH_VERSION_MISMATCH")
return prefix
def _prepare_lane(repo_root: Path, row: MatrixRow, base_prefix: Path) -> Path:
root = _matrix_root(repo_root)
environment = build_matrix_environment(repo_root, probe_offline=False)
lane_prefix = root / "lanes" / row.lane_id
base_python = _python_executable(base_prefix, venv=False)
lane_python = _python_executable(lane_prefix, venv=True)
if not lane_python.is_file():
print(f"PREPARE {row.lane_id}-venv")
_run_setup(
[
str(base_python),
"-m",
"venv",
"--system-site-packages",
str(lane_prefix),
],
environment,
"LANE_VENV_SETUP_FAILURE",
)
if not lane_python.is_file():
raise MatrixSetupError("LANE_PYTHON_MISSING")
installed = _installed_version(lane_python, "transformers", environment)
if installed != row.transformers_version:
print(f"PREPARE {row.lane_id}-packages")
_run_setup(
[
str(lane_python),
"-m",
"pip",
"install",
"--only-binary=:all:",
"--upgrade",
f"transformers=={row.transformers_version}",
"jinja2",
],
environment,
"TRANSFORMERS_SETUP_FAILURE",
)
installed = _installed_version(lane_python, "transformers", environment)
if installed != row.transformers_version:
raise MatrixSetupError("TRANSFORMERS_VERSION_MISMATCH")
return lane_prefix
def prepare_matrix(repo_root: Path) -> None:
_ensure_matrix_directories(repo_root)
bases = {
version: _prepare_base(repo_root, version)
for version in PYTHON_VERSIONS
}
for row in matrix_rows():
_prepare_lane(repo_root, row, bases[row.python_version])
def _contains_private_content(value: Any) -> bool:
if isinstance(value, dict):
return any(
_contains_private_content(key) or _contains_private_content(item)
for key, item in value.items()
)
if isinstance(value, (list, tuple)):
return any(_contains_private_content(item) for item in value)
if not isinstance(value, str):
return False
lowered = value.casefold()
forbidden = (
"traceback",
"token=",
"authorization",
"http://",
"https://",
"private-",
"private_",
"userprofile",
)
return (
any(marker in lowered for marker in forbidden)
or bool(_WINDOWS_ABSOLUTE_PATH.search(value))
or value.startswith("/")
)
def validate_worker_result(row: MatrixRow, payload: Any) -> dict[str, Any]:
if not isinstance(payload, dict) or set(payload) != _ALLOWED_RESULT_KEYS:
raise MatrixValidationError("WORKER_SCHEMA_MISMATCH")
if _contains_private_content(payload):
raise MatrixValidationError("WORKER_PRIVACY_VIOLATION")
if payload["schema_version"] != RESULT_SCHEMA_VERSION:
raise MatrixValidationError("WORKER_SCHEMA_VERSION_MISMATCH")
if payload["lane"] != row.lane_id:
raise MatrixValidationError("WORKER_LANE_MISMATCH")
if not isinstance(payload["python"], str) or not payload["python"].startswith(
f"{row.python_version}."
):
raise MatrixValidationError("WORKER_PYTHON_VERSION_MISMATCH")
if payload["transformers"] != row.transformers_version:
raise MatrixValidationError("WORKER_TRANSFORMERS_VERSION_MISMATCH")
if not isinstance(payload["torch"], str) or not payload["torch"].startswith(TORCH_VERSION):
raise MatrixValidationError("WORKER_TORCH_VERSION_MISMATCH")
if payload["command"] != "python scripts/transformers_matrix_probe.py":
raise MatrixValidationError("WORKER_COMMAND_MISMATCH")
surfaces = payload["surfaces"]
if not isinstance(surfaces, dict) or tuple(surfaces) != REQUIRED_SURFACES:
raise MatrixValidationError("WORKER_SURFACE_SCHEMA_MISMATCH")
if any(value not in {"PASS", "FAIL", "SKIP"} for value in surfaces.values()):
raise MatrixValidationError("WORKER_SURFACE_RESULT_INVALID")
if payload["result"] == "PASS":
if any(value != "PASS" for value in surfaces.values()) or payload["error_category"] is not None:
raise MatrixValidationError("WORKER_FALSE_PASS")
elif payload["result"] == "FAIL":
category = payload["error_category"]
if not isinstance(category, str) or not _SAFE_CATEGORY.fullmatch(category):
raise MatrixValidationError("WORKER_FAILURE_CATEGORY_INVALID")
if all(value == "PASS" for value in surfaces.values()):
raise MatrixValidationError("WORKER_FAILURE_WITHOUT_SURFACE")
else:
raise MatrixValidationError("WORKER_RESULT_INVALID")
return payload
def validate_aggregate(rows: Iterable[MatrixRow], payloads: list[dict[str, Any]]) -> None:
expected = tuple(row.lane_id for row in rows)
actual = tuple(payload.get("lane") for payload in payloads)
if len(actual) != len(expected) or set(actual) != set(expected) or len(set(actual)) != len(actual):
raise MatrixValidationError("AGGREGATE_ROW_MISMATCH")
def format_matrix_table(payloads: list[dict[str, Any]]) -> str:
lines = [
"| Lane | Python | Transformers | PyTorch | Surfaces | Result |",
"| --- | --- | --- | --- | ---: | --- |",
]
for payload in payloads:
passed = sum(value == "PASS" for value in payload["surfaces"].values())
lines.append(
f"| {payload['lane']} | {payload['python']} | {payload['transformers']} | "
f"{payload['torch']} | {passed}/{len(REQUIRED_SURFACES)} | {payload['result']} |"
)
return "\n".join(lines)
def write_aggregate(repo_root: Path, output: Path, aggregate: dict[str, Any]) -> None:
output = resolve_output_path(repo_root, output)
if _contains_private_content(aggregate):
raise MatrixValidationError("AGGREGATE_PRIVACY_VIOLATION")
output.parent.mkdir(parents=True, exist_ok=True)
serialized = json.dumps(aggregate, indent=2, sort_keys=True) + "\n"
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
newline="\n",
dir=output.parent,
prefix=".tg058-",
suffix=".json.tmp",
delete=False,
) as handle:
temp_path = Path(handle.name)
handle.write(serialized)
os.replace(temp_path, output)
def _failure_payload(row: MatrixRow, category: str) -> dict[str, Any]:
surfaces = {surface: "SKIP" for surface in REQUIRED_SURFACES}
surfaces[REQUIRED_SURFACES[0]] = "FAIL"
return {
"schema_version": RESULT_SCHEMA_VERSION,
"lane": row.lane_id,
"python": f"{row.python_version}.0",
"transformers": row.transformers_version,
"torch": TORCH_VERSION,
"command": "python scripts/transformers_matrix_probe.py",
"result": "FAIL",
"surfaces": surfaces,
"error_category": category,
}
def run_lane(repo_root: Path, row: MatrixRow) -> dict[str, Any]:
root = _matrix_root(repo_root)
lane_python = _python_executable(root / "lanes" / row.lane_id, venv=True)
if not lane_python.is_file():
return _failure_payload(row, "LANE_MISSING")
work_dir = root / "work" / row.lane_id
work_dir.mkdir(parents=True, exist_ok=True)
command = [
str(lane_python),
str(Path(repo_root).resolve() / "scripts" / "transformers_matrix_probe.py"),
"--lane",
row.lane_id,
"--expected-python",
row.python_version,
"--expected-transformers",
row.transformers_version,
"--expected-torch",
TORCH_VERSION,
"--repo-root",
str(Path(repo_root).resolve()),
"--work-dir",
str(work_dir),
]
environment = build_matrix_environment(repo_root, probe_offline=True)
try:
completed = subprocess.run(
command,
env=environment,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
encoding="utf-8",
check=False,
shell=False,
timeout=180,
)
except subprocess.TimeoutExpired:
return _failure_payload(row, "WORKER_TIMEOUT")
except OSError:
return _failure_payload(row, "WORKER_START_FAILURE")
if completed.returncode not in {0, 1}:
return _failure_payload(row, "WORKER_PROCESS_FAILURE")
try:
payload = json.loads(completed.stdout.strip())
return validate_worker_result(row, payload)
except (json.JSONDecodeError, MatrixValidationError):
return _failure_payload(row, "WORKER_OUTPUT_INVALID")
def verify_matrix(repo_root: Path) -> tuple[list[dict[str, Any]], bool]:
rows = matrix_rows()
payloads = [run_lane(repo_root, row) for row in rows]
validate_aggregate(rows, payloads)
return payloads, all(payload["result"] == "PASS" for payload in payloads)
def _parse_args(argv: list[str] | None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--prepare",
action="store_true",
help="Create/update the fixed repo-local matrix before verification.",
)
parser.add_argument(
"--output",
default=str(MATRIX_RELATIVE_ROOT / "results.json"),
help="Aggregate JSON path; must remain under .tmp/tg058/.",
)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = _parse_args(argv)
try:
output = resolve_output_path(REPO_ROOT, args.output)
_ensure_matrix_directories(REPO_ROOT)
if args.prepare:
prepare_matrix(REPO_ROOT)
payloads, passed = verify_matrix(REPO_ROOT)
aggregate = {
"schema_version": RESULT_SCHEMA_VERSION,
"matrix": {
"python": list(PYTHON_VERSIONS),
"transformers": list(TRANSFORMERS_VERSIONS),
"torch": TORCH_VERSION,
},
"rows": payloads,
"result": "PASS" if passed else "FAIL",
}
write_aggregate(REPO_ROOT, output, aggregate)
print(format_matrix_table(payloads))
return 0 if passed else 1
except MatrixSetupError as error:
print(f"TG058_MATRIX_SETUP_FAILURE:{error.category}", file=sys.stderr)
return 2
except MatrixValidationError as error:
category = str(error) if _SAFE_CATEGORY.fullmatch(str(error)) else "MATRIX_VALIDATION_FAILURE"
print(f"TG058_MATRIX_VALIDATION_FAILURE:{category}", file=sys.stderr)
return 3
if __name__ == "__main__":
raise SystemExit(main())