Files
exportAnything-ComfyUI-Diff…/tools/migrate_minimax_h3_fast_test.py
danrh 8f23c95546 Add synchronized music-video production planning
Add measured-audio, timed-lyrics, H3 lane planning, identity relay, upload-song routing, model-specific camera contracts, LTX long-horizon planning, workflow migrations, the validated V6 example, regression coverage, and an Unreleased changelog.
2026-08-23 08:03:39 -05:00

1206 lines
46 KiB
Python

#!/usr/bin/env python3
"""Create a non-destructive MiniMax-H3 Ref2VA Turbo fast-test sibling.
The source workflow is never rewritten. The tool fails closed when the H3
runtime topology is ambiguous, applies the official four-step Turbo contract,
adds a conditioning-side VRAM cleanup seam to every lazy H3 lane, validates the
result, and writes a new sibling with exclusive-create semantics.
The fast-test defaults (five seconds and 0.4 megapixels) remain ordinary user
controls. Reopening the untouched source workflow is the rollback path; its
exact SHA-256 is persisted in the migrated workflow.
"""
from __future__ import annotations
import argparse
import copy
import hashlib
import json
import os
from pathlib import Path
from typing import Any, Iterable
MIGRATION_SCHEMA = "diffusiongemma.minimax_h3_fast_test"
MIGRATION_VERSION = 1
DEFAULT_SOURCE = Path(
r"C:\Users\danrh\Desktop\14_minimax_ref2va+AUDIO_IN_PROCESS_SYNC_organized_LIVE_UNSAVED_CAPTURE_20260822_0057.json"
)
DEFAULT_OUTPUT = Path(
r"C:\Users\danrh\Desktop\14_minimax_ref2va+AUDIO_IN_PROCESS_SYNC_organized_BASELINE_FAST_H3.json"
)
SOURCE_CAPTURE_SHA256 = (
"c4dc698ebd05d07c8422333c3e4a498e52e47270207231b6067d7fca33fb30e0"
)
FAST_MODEL = "minimax_h3_ref2va_pruned_int8_convrot.safetensors"
FAST_LORA = "minimax_h3_ref2v_turbo_4step_v0.1_comfyui_bf16.safetensors"
FAST_LORA_SHA256 = (
"5b9ab5ade15d0775676d01a907268a69a1468dc6033b3b0d3ded5502f3ebb84c"
)
FAST_LORA_BYTES = 1_956_193_000
FAST_SAMPLER = "euler"
FAST_SCHEDULER = "simple"
FAST_STEPS = 4
FAST_VIDEO_SHIFT = 12.0
FAST_AUDIO_SHIFT = 3.0
FAST_DURATION_SECONDS = 5.0
FAST_RESOLUTION_MEGAPIXELS = 0.4
LANE_COUNT = 4
CLEANUP_NODE_TYPE = "FL_UnloadAllModels"
CLEANUP_INPUT = "value"
CLEANUP_OUTPUT = "*"
CLEANUP_BACKEND_OUTPUT_NODE = False
class WorkflowError(RuntimeError):
"""Raised when a workflow cannot be changed without guessing."""
def _file_hash(payload: bytes) -> str:
return hashlib.sha256(payload).hexdigest()
def _canonical_hash(value: Any) -> str:
payload = json.dumps(
value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
return _file_hash(payload)
def _sensitive_key_paths(value: Any, prefix: str = "") -> list[str]:
hits: list[str] = []
names = {
"api_key",
"apikey",
"authorization",
"access_token",
"refresh_token",
"password",
"secret",
"client_secret",
}
if isinstance(value, dict):
for key, item in value.items():
path = f"{prefix}.{key}" if prefix else str(key)
if str(key).casefold() in names and item not in (None, "", False):
hits.append(path)
hits.extend(_sensitive_key_paths(item, path))
elif isinstance(value, list):
for index, item in enumerate(value):
hits.extend(_sensitive_key_paths(item, f"{prefix}[{index}]"))
return hits
def _subgraphs(workflow: dict[str, Any]) -> list[dict[str, Any]]:
definitions = workflow.get("definitions")
if not isinstance(definitions, dict):
return []
values = definitions.get("subgraphs")
return values if isinstance(values, list) else []
def _all_nodes(workflow: dict[str, Any]) -> Iterable[dict[str, Any]]:
yield from workflow.get("nodes", [])
for subgraph in _subgraphs(workflow):
yield from subgraph.get("nodes", [])
class IdAllocator:
def __init__(self, workflow: dict[str, Any]) -> None:
node_ids = [int(node["id"]) for node in _all_nodes(workflow)]
link_ids = [int(link[0]) for link in workflow.get("links", [])]
for subgraph in _subgraphs(workflow):
link_ids.extend(int(link["id"]) for link in subgraph.get("links", []))
state = subgraph.get("state") or {}
node_ids.append(int(state.get("lastNodeId", 0) or 0))
link_ids.append(int(state.get("lastLinkId", 0) or 0))
node_ids.append(int(workflow.get("last_node_id", 0) or 0))
link_ids.append(int(workflow.get("last_link_id", 0) or 0))
self._node = max(node_ids or [0])
self._link = max(link_ids or [0])
def node(self) -> int:
self._node += 1
return self._node
def link(self) -> int:
self._link += 1
return self._link
@property
def last_node(self) -> int:
return self._node
@property
def last_link(self) -> int:
return self._link
def _socket(node: dict[str, Any], direction: str, name: str) -> int:
matches = [
index
for index, item in enumerate(node.get(direction, []))
if item.get("name") == name
]
if len(matches) != 1:
raise WorkflowError(
f"Node {node.get('id')} ({node.get('type')}) requires one "
f"{direction[:-1]} {name!r}; found {len(matches)}."
)
return matches[0]
def _one(values: Iterable[dict[str, Any]], predicate, label: str) -> dict[str, Any]:
matches = [value for value in values if predicate(value)]
if len(matches) != 1:
raise WorkflowError(f"Expected one {label}; found {len(matches)}.")
return matches[0]
class MainGraph:
def __init__(self, workflow: dict[str, Any], ids: IdAllocator) -> None:
self.workflow = workflow
self.nodes: list[dict[str, Any]] = workflow["nodes"]
self.links: list[list[Any]] = workflow["links"]
self.ids = ids
def node(self, node_id: int) -> dict[str, Any]:
return _one(
self.nodes,
lambda node: int(node["id"]) == int(node_id),
f"main-graph node {node_id}",
)
def input_origin(
self, target: dict[str, Any], input_name: str
) -> tuple[dict[str, Any], int]:
input_slot = _socket(target, "inputs", input_name)
link_id = target["inputs"][input_slot].get("link")
if link_id is None:
raise WorkflowError(
f"Node {target.get('id')} input {input_name!r} is unconnected."
)
link = _one(
self.links,
lambda item: int(item[0]) == int(link_id),
f"main-graph link {link_id}",
)
return self.node(int(link[1])), int(link[2])
def remove_link(self, link_id: int) -> None:
matches = [link for link in self.links if int(link[0]) == int(link_id)]
if not matches:
return
if len(matches) != 1:
raise WorkflowError(f"Duplicate main-graph link id {link_id}.")
link = matches[0]
_, origin_id, origin_slot, target_id, target_slot, _ = link
origin = self.node(int(origin_id))
target = self.node(int(target_id))
output = origin["outputs"][int(origin_slot)]
output["links"] = [
value
for value in (output.get("links") or [])
if int(value) != int(link_id)
]
target_input = target["inputs"][int(target_slot)]
if target_input.get("link") is not None and int(target_input["link"]) == int(
link_id
):
target_input["link"] = None
self.links.remove(link)
def disconnect(self, node: dict[str, Any]) -> None:
node_id = int(node["id"])
for link in list(self.links):
if int(link[1]) == node_id or int(link[3]) == node_id:
self.remove_link(int(link[0]))
def add(self, node: dict[str, Any]) -> dict[str, Any]:
if any(int(value["id"]) == int(node["id"]) for value in _all_nodes(self.workflow)):
raise WorkflowError(f"Duplicate node id {node['id']}.")
node["order"] = max(
(int(value.get("order", 0)) for value in self.nodes), default=0
) + 1
self.nodes.append(node)
return node
def connect(
self,
origin: dict[str, Any],
output_name: str,
target: dict[str, Any],
input_name: str,
link_type: str,
) -> int:
output_slot = _socket(origin, "outputs", output_name)
input_slot = _socket(target, "inputs", input_name)
existing = target["inputs"][input_slot].get("link")
if existing is not None:
self.remove_link(int(existing))
link_id = self.ids.link()
self.links.append(
[
link_id,
int(origin["id"]),
output_slot,
int(target["id"]),
input_slot,
link_type,
]
)
target["inputs"][input_slot]["link"] = link_id
output = origin["outputs"][output_slot]
output.setdefault("links", []).append(link_id)
return link_id
class SubgraphEditor:
def __init__(self, subgraph: dict[str, Any], ids: IdAllocator) -> None:
self.subgraph = subgraph
self.nodes: list[dict[str, Any]] = subgraph["nodes"]
self.links: list[dict[str, Any]] = subgraph["links"]
self.ids = ids
def node(self, node_id: int) -> dict[str, Any]:
return _one(
self.nodes,
lambda node: int(node["id"]) == int(node_id),
f"subgraph node {node_id}",
)
def input_origin(
self, target: dict[str, Any], input_name: str
) -> tuple[dict[str, Any], int]:
slot = _socket(target, "inputs", input_name)
link_id = target["inputs"][slot].get("link")
if link_id is None:
raise WorkflowError(
f"Subgraph node {target.get('id')} input {input_name!r} is unconnected."
)
link = _one(
self.links,
lambda value: int(value["id"]) == int(link_id),
f"subgraph link {link_id}",
)
origin_id = int(link["origin_id"])
if origin_id < 0:
raise WorkflowError(
f"Subgraph node {target.get('id')} input {input_name!r} comes from "
"the subgraph boundary, not a node."
)
return self.node(origin_id), int(link["origin_slot"])
def remove_link(self, link_id: int) -> None:
matches = [link for link in self.links if int(link["id"]) == int(link_id)]
if not matches:
return
if len(matches) != 1:
raise WorkflowError(f"Duplicate subgraph link id {link_id}.")
link = matches[0]
origin_id = int(link["origin_id"])
target_id = int(link["target_id"])
if origin_id < 0 or target_id < 0:
raise WorkflowError(
"The fast migration never rewires subgraph boundary links."
)
origin = self.node(origin_id)
target = self.node(target_id)
output = origin["outputs"][int(link["origin_slot"])]
output["links"] = [
value
for value in (output.get("links") or [])
if int(value) != int(link_id)
]
target_input = target["inputs"][int(link["target_slot"])]
if target_input.get("link") is not None and int(target_input["link"]) == int(
link_id
):
target_input["link"] = None
self.links.remove(link)
def add(self, node: dict[str, Any]) -> dict[str, Any]:
if any(int(value["id"]) == int(node["id"]) for value in self.nodes):
raise WorkflowError(f"Duplicate subgraph node id {node['id']}.")
node["order"] = max(
(int(value.get("order", 0)) for value in self.nodes), default=0
) + 1
self.nodes.append(node)
return node
def connect(
self,
origin: dict[str, Any],
output_name: str,
target: dict[str, Any],
input_name: str,
link_type: str,
) -> int:
output_slot = _socket(origin, "outputs", output_name)
input_slot = _socket(target, "inputs", input_name)
existing = target["inputs"][input_slot].get("link")
if existing is not None:
self.remove_link(int(existing))
link_id = self.ids.link()
self.links.append(
{
"id": link_id,
"origin_id": int(origin["id"]),
"origin_slot": output_slot,
"target_id": int(target["id"]),
"target_slot": input_slot,
"type": link_type,
}
)
target["inputs"][input_slot]["link"] = link_id
origin["outputs"][output_slot].setdefault("links", []).append(link_id)
return link_id
def _input(name: str, type_name: str, *, widget: bool = False) -> dict[str, Any]:
result: dict[str, Any] = {
"localized_name": name,
"name": name,
"type": type_name,
"link": None,
}
if widget:
result["widget"] = {"name": name}
return result
def _output(name: str, type_name: str) -> dict[str, Any]:
return {
"localized_name": name,
"name": name,
"type": type_name,
"links": [],
}
def _sigma_shift_node(ids: IdAllocator, pos: list[float]) -> dict[str, Any]:
return {
"id": ids.node(),
"type": "MiniMaxH3SigmaShift",
"pos": [float(pos[0]), float(pos[1])],
"size": [270.0, 82.0],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
_input("model", "MODEL"),
_input("shift_video", "FLOAT", widget=True),
_input("shift_audio", "FLOAT", widget=True),
],
"outputs": [_output("MODEL", "MODEL")],
"title": "H3 Turbo sigma shifts — video 12 / audio 3",
"properties": {
"cnr_id": "comfy-core",
"ver": "0.30.0",
"Node name for S&R": "MiniMaxH3SigmaShift",
},
"widgets_values": [FAST_VIDEO_SHIFT, FAST_AUDIO_SHIFT],
"color": "#100a73",
"bgcolor": "#252b3e",
}
def _cleanup_node(ids: IdAllocator, lane: int, pos: list[float]) -> dict[str, Any]:
return {
"id": ids.node(),
"type": CLEANUP_NODE_TYPE,
"pos": [float(pos[0]), float(pos[1])],
"size": [285.0, 58.0],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [_input(CLEANUP_INPUT, "*")],
"outputs": [_output(CLEANUP_OUTPUT, "*")],
"title": (
f"H3 LANE {lane} — release encoder/VAEs after conditioning"
),
"properties": {
"Node name for S&R": CLEANUP_NODE_TYPE,
"cnr_id": "comfyui_fill-nodes",
"ver": "2.9.2",
},
"widgets_values": [],
}
def _find_h3_subgraph(workflow: dict[str, Any]) -> dict[str, Any]:
candidates = []
for subgraph in _subgraphs(workflow):
nodes = subgraph.get("nodes", [])
if (
sum(node.get("type") == "MiniMaxH3ReferenceToVideo" for node in nodes)
== LANE_COUNT
and sum(node.get("type") == "BasicScheduler" for node in nodes) == 1
and sum(node.get("type") == "KSamplerSelect" for node in nodes) == 1
):
candidates.append(subgraph)
if len(candidates) != 1:
raise WorkflowError(
f"Expected one four-lane H3 sampling subgraph; found {len(candidates)}."
)
return candidates[0]
def _main_nodes(workflow: dict[str, Any]) -> list[dict[str, Any]]:
values = workflow.get("nodes")
if not isinstance(values, list):
raise WorkflowError("The workflow has no editable main graph.")
if not isinstance(workflow.get("links"), list):
raise WorkflowError("The workflow has no editable main-graph links.")
return values
def _find_runtime(
workflow: dict[str, Any], ids: IdAllocator
) -> dict[str, Any]:
nodes = _main_nodes(workflow)
graph = MainGraph(workflow, ids)
subgraph = _find_h3_subgraph(workflow)
subeditor = SubgraphEditor(subgraph, ids)
outer = _one(
nodes,
lambda node: str(node.get("type")) == str(subgraph.get("id")),
"main-graph H3 subgraph node",
)
loader = _one(
nodes,
lambda node: node.get("type") == "UNETLoader"
and "minimax_h3_ref2va" in str((node.get("widgets_values") or [""])[0]),
"MiniMax-H3 Ref2VA UNET loader",
)
lora = _one(
nodes,
lambda node: node.get("type") == "LoraLoaderModelOnly"
and "minimax_h3_ref2v_turbo" in str(
(node.get("widgets_values") or [""])[0]
),
"MiniMax-H3 Turbo LoRA loader",
)
sage = _one(
nodes,
lambda node: node.get("type") == "PathchSageAttentionKJ",
"KJ SageAttention patch",
)
cache = _one(
nodes,
lambda node: node.get("type") == "EasyCache",
"EasyCache experiment",
)
compile_node = _one(
nodes,
lambda node: node.get("type") == "TorchCompileModel",
"TorchCompile experiment",
)
sigma_nodes = [
node for node in nodes if node.get("type") == "MiniMaxH3SigmaShift"
]
sampler = _one(
subeditor.nodes,
lambda node: node.get("type") == "KSamplerSelect",
"H3 KSamplerSelect",
)
scheduler = _one(
subeditor.nodes,
lambda node: node.get("type") == "BasicScheduler",
"H3 BasicScheduler",
)
h3_nodes = sorted(
[
node
for node in subeditor.nodes
if node.get("type") == "MiniMaxH3ReferenceToVideo"
],
key=lambda node: float((node.get("pos") or [0.0, 0.0])[1]),
)
guiders = sorted(
[node for node in subeditor.nodes if node.get("type") == "BasicGuider"],
key=lambda node: float((node.get("pos") or [0.0, 0.0])[1]),
)
samplers = sorted(
[
node
for node in subeditor.nodes
if node.get("type") == "SamplerCustomAdvanced"
],
key=lambda node: float((node.get("pos") or [0.0, 0.0])[1]),
)
if not (len(h3_nodes) == len(guiders) == len(samplers) == LANE_COUNT):
raise WorkflowError(
"The H3 subgraph must contain four reference nodes, guiders, and samplers."
)
return {
"graph": graph,
"subgraph": subgraph,
"subeditor": subeditor,
"outer": outer,
"loader": loader,
"lora": lora,
"sage": sage,
"cache": cache,
"compile": compile_node,
"sigma_nodes": sigma_nodes,
"sampler": sampler,
"scheduler": scheduler,
"h3_nodes": h3_nodes,
"guiders": guiders,
"samplers": samplers,
}
def _require_source_chain(runtime: dict[str, Any]) -> None:
graph: MainGraph = runtime["graph"]
expected = (
(runtime["lora"], "model", runtime["loader"]),
(runtime["sage"], "model", runtime["lora"]),
(runtime["cache"], "model", runtime["sage"]),
(runtime["compile"], "model", runtime["cache"]),
(runtime["outer"], "model", runtime["compile"]),
)
for target, input_name, wanted_origin in expected:
origin, _ = graph.input_origin(target, input_name)
if int(origin["id"]) != int(wanted_origin["id"]):
raise WorkflowError(
"The live H3 acceleration chain changed after capture; refusing "
f"to guess at node {target.get('id')} input {input_name!r}."
)
if runtime["sigma_nodes"]:
raise WorkflowError(
"An unmarked MiniMaxH3SigmaShift already exists; refusing to guess "
"whether a partial fast migration should be replaced."
)
subeditor: SubgraphEditor = runtime["subeditor"]
for lane, (h3, guider, sampler) in enumerate(
zip(runtime["h3_nodes"], runtime["guiders"], runtime["samplers"]), start=1
):
origin, slot = subeditor.input_origin(guider, "conditioning")
if int(origin["id"]) != int(h3["id"]) or slot != _socket(
h3, "outputs", "positive"
):
raise WorkflowError(
f"H3 lane {lane} conditioning is no longer a direct reference-node output."
)
origin, slot = subeditor.input_origin(sampler, "latent_image")
if int(origin["id"]) != int(h3["id"]) or slot != _socket(
h3, "outputs", "LATENT"
):
raise WorkflowError(
f"H3 lane {lane} latent path is no longer direct and cannot be patched safely."
)
def _set_widget(node: dict[str, Any], index: int, value: Any, label: str) -> None:
widgets = node.get("widgets_values")
if not isinstance(widgets, list) or len(widgets) <= index:
raise WorkflowError(
f"Node {node.get('id')} is missing the expected {label} widget."
)
widgets[index] = value
def _apply_fast_defaults(workflow: dict[str, Any]) -> None:
nodes = _main_nodes(workflow)
duration = _one(
nodes,
lambda node: int(node.get("id", -1)) == 178
and node.get("type") == "PrimitiveFloat",
"master video duration control",
)
splitter = _one(
nodes,
lambda node: int(node.get("id", -1)) == 187
and node.get("type") == "DiffusionGemmaJSONSplitter",
"H3 resolution control",
)
_set_widget(duration, 0, FAST_DURATION_SECONDS, "duration")
_set_widget(splitter, 1, FAST_RESOLUTION_MEGAPIXELS, "megapixel")
duration["title"] = (
"2. FAST TEST VIDEO duration — 5s default; configurable for production"
)
splitter["title"] = (
"7. Route H3 prompt + resolution — FAST TEST defaults to 0.4 MP"
)
project = [
node
for node in nodes
if node.get("type") == "DiffusionGemmaProjectMasterContract"
]
if len(project) == 1:
values = project[0].get("widgets_values")
if isinstance(values, list) and len(values) >= 4:
values[0] = FAST_DURATION_SECONDS
values[3] = FAST_DURATION_SECONDS
def migrate_workflow(
workflow: dict[str, Any], *, source_file_sha256: str | None = None
) -> dict[str, Any]:
"""Return a migrated deep copy without modifying the source object."""
if not isinstance(workflow, dict):
raise WorkflowError("Workflow root must be a JSON object.")
sensitive = _sensitive_key_paths(workflow)
if sensitive:
raise WorkflowError(
"Refusing to process non-empty sensitive-looking fields at "
+ ", ".join(sensitive)
+ "."
)
result = copy.deepcopy(workflow)
marker = result.get("extra", {}).get(MIGRATION_SCHEMA)
if marker is not None:
if not isinstance(marker, dict) or int(marker.get("version", 0)) != MIGRATION_VERSION:
raise WorkflowError("The MiniMax-H3 fast-test marker is unsupported.")
validate_workflow(result)
return result
_validate_link_integrity(result)
ids = IdAllocator(result)
runtime = _find_runtime(result, ids)
_require_source_chain(runtime)
graph: MainGraph = runtime["graph"]
subeditor: SubgraphEditor = runtime["subeditor"]
_set_widget(runtime["loader"], 0, FAST_MODEL, "H3 model")
runtime["loader"]["title"] = "MiniMax-H3 Ref2VA — pruned INT8 fast-test model"
_set_widget(runtime["lora"], 0, FAST_LORA, "Turbo LoRA")
_set_widget(runtime["lora"], 1, 1.0, "Turbo LoRA strength")
runtime["lora"]["title"] = "Official Ref2VA Turbo 4-step LoRA — ComfyUI format"
runtime["sage"]["widgets_values"] = ["auto", False]
runtime["sage"]["title"] = "SageAttention auto — compile disabled"
graph.disconnect(runtime["cache"])
graph.disconnect(runtime["compile"])
for node, title, y in (
(
runtime["cache"],
"EXPERIMENT DISABLED — EasyCache is bypassed for four-step Turbo",
-2890.0,
),
(
runtime["compile"],
"EXPERIMENT DISABLED — Torch compile is outside the fast baseline",
-2890.0,
),
):
node["mode"] = 4
node["title"] = title
if isinstance(node.get("pos"), list) and len(node["pos"]) >= 2:
node["pos"][1] = y
sigma = graph.add(
_sigma_shift_node(ids, [4246.0, -2668.0])
)
graph.connect(runtime["sage"], "MODEL", sigma, "model", "MODEL")
graph.connect(sigma, "MODEL", runtime["outer"], "model", "MODEL")
runtime["sampler"]["widgets_values"] = [FAST_SAMPLER]
runtime["sampler"]["title"] = "Shared H3 sampler — Euler Turbo baseline"
runtime["scheduler"]["widgets_values"] = [
FAST_SCHEDULER,
FAST_STEPS,
1.0,
]
runtime["scheduler"]["title"] = "Shared H3 schedule — simple / 4-step Turbo"
runtime["subgraph"]["name"] = "MiniMax H3 Ref2VA — FAST 4-step"
runtime["outer"]["title"] = "FAST H3 Ref2VA — pruned INT8 / Turbo 4-step"
cleanup_nodes: list[dict[str, Any]] = []
for lane, (h3, guider) in enumerate(
zip(runtime["h3_nodes"], runtime["guiders"]), start=1
):
h3_pos = h3.get("pos") or [4198.0, float(lane - 1) * 970.0]
cleanup = subeditor.add(
_cleanup_node(
ids,
lane,
[4198.0, float(h3_pos[1]) - 70.0],
)
)
subeditor.connect(
h3, "positive", cleanup, CLEANUP_INPUT, "CONDITIONING"
)
subeditor.connect(
cleanup, CLEANUP_OUTPUT, guider, "conditioning", "CONDITIONING"
)
cleanup_nodes.append(cleanup)
_apply_fast_defaults(result)
result["last_node_id"] = ids.last_node
result["last_link_id"] = ids.last_link
result["revision"] = int(result.get("revision", 0) or 0) + 1
for subgraph in _subgraphs(result):
state = subgraph.setdefault("state", {})
state["lastNodeId"] = ids.last_node
state["lastLinkId"] = ids.last_link
runtime["subgraph"]["revision"] = int(
runtime["subgraph"].get("revision", 0) or 0
) + 1
extra = result.setdefault("extra", {})
extra[MIGRATION_SCHEMA] = {
"version": MIGRATION_VERSION,
"profile": "ref2va_turbo_4step_fast_test",
"source": {
"file_sha256": source_file_sha256,
"workflow_id": workflow.get("id"),
"workflow_revision": workflow.get("revision"),
"rollback_policy": "reopen the unchanged source capture",
},
"assets": {
"diffusion_model": {
"filename": FAST_MODEL,
"folder": "diffusion_models",
"format": "pruned_int8_convrot",
"required": True,
},
"turbo_lora": {
"filename": FAST_LORA,
"folder": "loras",
"format": "comfyui_bf16",
"required": True,
"expected_bytes": FAST_LORA_BYTES,
"expected_sha256": FAST_LORA_SHA256,
},
},
"sampling_contract": {
"sampler": FAST_SAMPLER,
"scheduler": FAST_SCHEDULER,
"steps": FAST_STEPS,
"denoise": 1.0,
"shift_video": FAST_VIDEO_SHIFT,
"shift_audio": FAST_AUDIO_SHIFT,
"sage_attention": "auto",
"sage_allow_compile": False,
"easycache_active": False,
"torch_compile_active": False,
},
"benchmark_defaults": {
"duration_seconds": FAST_DURATION_SECONDS,
"resolution_megapixels": FAST_RESOLUTION_MEGAPIXELS,
"controls_remain_configurable": True,
},
"generation_semantics": {
"lazy_generation_lane_capacity": LANE_COUNT,
"project_and_native_shot_contract_preserved": True,
"conditioning_cleanup_per_lane": True,
"cleanup_node_type": CLEANUP_NODE_TYPE,
"cleanup_backend_output_node": CLEANUP_BACKEND_OUTPUT_NODE,
"latent_path_unchanged": True,
},
"nodes": {
"loader": int(runtime["loader"]["id"]),
"lora": int(runtime["lora"]["id"]),
"sage": int(runtime["sage"]["id"]),
"sigma_shift": int(sigma["id"]),
"disabled_easycache": int(runtime["cache"]["id"]),
"disabled_torch_compile": int(runtime["compile"]["id"]),
"h3_subgraph": str(runtime["subgraph"]["id"]),
"h3_outer": int(runtime["outer"]["id"]),
"conditioning_cleanup": [int(node["id"]) for node in cleanup_nodes],
},
}
validate_workflow(result, require_benchmark_defaults=True)
sensitive_after = _sensitive_key_paths(result)
if sensitive_after:
raise WorkflowError(
"Migration introduced non-empty sensitive-looking fields at "
+ ", ".join(sensitive_after)
+ "."
)
return result
def _validate_main_links(workflow: dict[str, Any]) -> None:
nodes = {int(node["id"]): node for node in _main_nodes(workflow)}
link_ids: set[int] = set()
expected_outputs: dict[tuple[int, int], list[int]] = {}
expected_inputs: dict[tuple[int, int], int] = {}
for link in workflow["links"]:
if not isinstance(link, list) or len(link) != 6:
raise WorkflowError("Malformed main-graph link.")
link_id, origin_id, origin_slot, target_id, target_slot, _ = link
link_id = int(link_id)
origin_id = int(origin_id)
target_id = int(target_id)
origin_slot = int(origin_slot)
target_slot = int(target_slot)
if link_id in link_ids:
raise WorkflowError(f"Duplicate main-graph link id {link_id}.")
link_ids.add(link_id)
if origin_id not in nodes or target_id not in nodes:
raise WorkflowError(f"Main-graph link {link_id} references a missing node.")
if origin_slot >= len(nodes[origin_id].get("outputs", [])):
raise WorkflowError(f"Main-graph link {link_id} has an invalid origin slot.")
if target_slot >= len(nodes[target_id].get("inputs", [])):
raise WorkflowError(f"Main-graph link {link_id} has an invalid target slot.")
key = (target_id, target_slot)
if key in expected_inputs:
raise WorkflowError(f"Main-graph input {key} has multiple incoming links.")
expected_inputs[key] = link_id
expected_outputs.setdefault((origin_id, origin_slot), []).append(link_id)
for node_id, node in nodes.items():
for slot, item in enumerate(node.get("inputs", [])):
actual = item.get("link")
expected = expected_inputs.get((node_id, slot))
if actual != expected:
raise WorkflowError(
f"Main-graph input backlink mismatch at node {node_id} slot {slot}."
)
for slot, item in enumerate(node.get("outputs", [])):
actual = sorted(int(value) for value in (item.get("links") or []))
expected = sorted(expected_outputs.get((node_id, slot), []))
if actual != expected:
raise WorkflowError(
f"Main-graph output backlink mismatch at node {node_id} slot {slot}."
)
def _validate_subgraph_links(subgraph: dict[str, Any]) -> None:
nodes = {int(node["id"]): node for node in subgraph.get("nodes", [])}
input_node_id = int((subgraph.get("inputNode") or {}).get("id", -10))
output_node_id = int((subgraph.get("outputNode") or {}).get("id", -20))
expected_node_inputs: dict[tuple[int, int], int] = {}
expected_node_outputs: dict[tuple[int, int], list[int]] = {}
expected_boundary_inputs: dict[int, list[int]] = {}
expected_boundary_outputs: dict[int, list[int]] = {}
seen: set[int] = set()
for link in subgraph.get("links", []):
link_id = int(link["id"])
if link_id in seen:
raise WorkflowError(f"Duplicate subgraph link id {link_id}.")
seen.add(link_id)
origin_id = int(link["origin_id"])
target_id = int(link["target_id"])
origin_slot = int(link["origin_slot"])
target_slot = int(link["target_slot"])
if origin_id == input_node_id:
if origin_slot >= len(subgraph.get("inputs", [])):
raise WorkflowError(f"Subgraph link {link_id} has an invalid boundary input.")
expected_boundary_inputs.setdefault(origin_slot, []).append(link_id)
elif origin_id in nodes:
if origin_slot >= len(nodes[origin_id].get("outputs", [])):
raise WorkflowError(f"Subgraph link {link_id} has an invalid origin slot.")
expected_node_outputs.setdefault((origin_id, origin_slot), []).append(link_id)
else:
raise WorkflowError(f"Subgraph link {link_id} has a missing origin.")
if target_id == output_node_id:
if target_slot >= len(subgraph.get("outputs", [])):
raise WorkflowError(f"Subgraph link {link_id} has an invalid boundary output.")
expected_boundary_outputs.setdefault(target_slot, []).append(link_id)
elif target_id in nodes:
if target_slot >= len(nodes[target_id].get("inputs", [])):
raise WorkflowError(f"Subgraph link {link_id} has an invalid target slot.")
key = (target_id, target_slot)
if key in expected_node_inputs:
raise WorkflowError(f"Subgraph input {key} has multiple incoming links.")
expected_node_inputs[key] = link_id
else:
raise WorkflowError(f"Subgraph link {link_id} has a missing target.")
for node_id, node in nodes.items():
for slot, item in enumerate(node.get("inputs", [])):
if item.get("link") != expected_node_inputs.get((node_id, slot)):
raise WorkflowError(
f"Subgraph input backlink mismatch at node {node_id} slot {slot}."
)
for slot, item in enumerate(node.get("outputs", [])):
actual = sorted(int(value) for value in (item.get("links") or []))
expected = sorted(expected_node_outputs.get((node_id, slot), []))
if actual != expected:
raise WorkflowError(
f"Subgraph output backlink mismatch at node {node_id} slot {slot}."
)
for slot, item in enumerate(subgraph.get("inputs", [])):
actual = sorted(int(value) for value in (item.get("linkIds") or []))
expected = sorted(expected_boundary_inputs.get(slot, []))
if actual != expected:
raise WorkflowError(f"Subgraph boundary input backlink mismatch at slot {slot}.")
for slot, item in enumerate(subgraph.get("outputs", [])):
actual = sorted(int(value) for value in (item.get("linkIds") or []))
expected = sorted(expected_boundary_outputs.get(slot, []))
if actual != expected:
raise WorkflowError(f"Subgraph boundary output backlink mismatch at slot {slot}.")
def _validate_link_integrity(workflow: dict[str, Any]) -> None:
_validate_main_links(workflow)
all_node_ids = [int(node["id"]) for node in _all_nodes(workflow)]
if len(all_node_ids) != len(set(all_node_ids)):
raise WorkflowError("Node ids must be unique across the workflow and subgraphs.")
all_link_ids = [int(link[0]) for link in workflow.get("links", [])]
for subgraph in _subgraphs(workflow):
_validate_subgraph_links(subgraph)
all_link_ids.extend(int(link["id"]) for link in subgraph.get("links", []))
if len(all_link_ids) != len(set(all_link_ids)):
raise WorkflowError("Link ids must be unique across the workflow and subgraphs.")
def _assert_origin(
editor: MainGraph | SubgraphEditor,
target: dict[str, Any],
input_name: str,
origin: dict[str, Any],
output_name: str,
) -> None:
actual, actual_slot = editor.input_origin(target, input_name)
if int(actual["id"]) != int(origin["id"]) or actual_slot != _socket(
origin, "outputs", output_name
):
raise WorkflowError(
f"Node {target.get('id')} input {input_name!r} has the wrong origin."
)
def validate_workflow(
workflow: dict[str, Any], *, require_benchmark_defaults: bool = False
) -> None:
"""Validate the complete fast-test runtime and rollback metadata."""
_validate_link_integrity(workflow)
sensitive = _sensitive_key_paths(workflow)
if sensitive:
raise WorkflowError(
"Workflow contains non-empty sensitive-looking fields at "
+ ", ".join(sensitive)
+ "."
)
marker = workflow.get("extra", {}).get(MIGRATION_SCHEMA)
if not isinstance(marker, dict) or int(marker.get("version", 0)) != MIGRATION_VERSION:
raise WorkflowError("MiniMax-H3 fast-test marker is missing or stale.")
ids = IdAllocator(workflow)
runtime = _find_runtime(workflow, ids)
nodes_marker = marker.get("nodes") or {}
if int(nodes_marker.get("loader", -1)) != int(runtime["loader"]["id"]):
raise WorkflowError("Fast-test loader marker does not match the graph.")
if int(nodes_marker.get("lora", -1)) != int(runtime["lora"]["id"]):
raise WorkflowError("Fast-test LoRA marker does not match the graph.")
if int(nodes_marker.get("sage", -1)) != int(runtime["sage"]["id"]):
raise WorkflowError("Fast-test Sage marker does not match the graph.")
sigma = _one(
runtime["sigma_nodes"],
lambda _node: True,
"active MiniMaxH3SigmaShift",
)
if int(nodes_marker.get("sigma_shift", -1)) != int(sigma["id"]):
raise WorkflowError("Fast-test sigma-shift marker does not match the graph.")
if (runtime["loader"].get("widgets_values") or [None])[0] != FAST_MODEL:
raise WorkflowError("Fast-test workflow does not select the pruned INT8 model.")
if runtime["lora"].get("widgets_values") != [FAST_LORA, 1.0]:
raise WorkflowError("Fast-test workflow does not select the official ComfyUI LoRA.")
if runtime["sage"].get("widgets_values") != ["auto", False]:
raise WorkflowError("SageAttention must be Auto with compilation disabled.")
if sigma.get("widgets_values") != [FAST_VIDEO_SHIFT, FAST_AUDIO_SHIFT]:
raise WorkflowError("MiniMax-H3 sigma shifts must be video 12 / audio 3.")
if runtime["sampler"].get("widgets_values") != [FAST_SAMPLER]:
raise WorkflowError("The fast-test sampler must be Euler.")
if runtime["scheduler"].get("widgets_values") != [
FAST_SCHEDULER,
FAST_STEPS,
1.0,
]:
raise WorkflowError("The fast-test schedule must be simple / four steps.")
graph: MainGraph = runtime["graph"]
_assert_origin(graph, runtime["lora"], "model", runtime["loader"], "MODEL")
_assert_origin(graph, runtime["sage"], "model", runtime["lora"], "MODEL")
_assert_origin(graph, sigma, "model", runtime["sage"], "MODEL")
_assert_origin(graph, runtime["outer"], "model", sigma, "MODEL")
for disabled in (runtime["cache"], runtime["compile"]):
if int(disabled.get("mode", 0)) != 4:
raise WorkflowError(f"Disabled experiment node {disabled.get('id')} is active.")
disabled_id = int(disabled["id"])
if any(
int(link[1]) == disabled_id or int(link[3]) == disabled_id
for link in workflow["links"]
):
raise WorkflowError(
f"Disabled experiment node {disabled_id} remains in the model path."
)
cleanup_ids = [int(value) for value in nodes_marker.get("conditioning_cleanup", [])]
if len(cleanup_ids) != LANE_COUNT or len(set(cleanup_ids)) != LANE_COUNT:
raise WorkflowError("The fast-test marker must name four cleanup seams.")
cleanups = [runtime["subeditor"].node(node_id) for node_id in cleanup_ids]
if any(node.get("type") != CLEANUP_NODE_TYPE for node in cleanups):
raise WorkflowError("A conditioning cleanup marker references the wrong node type.")
if CLEANUP_BACKEND_OUTPUT_NODE:
raise WorkflowError("The configured cleanup seam must not be an execution root.")
subeditor: SubgraphEditor = runtime["subeditor"]
for lane, (h3, cleanup, guider, sampler) in enumerate(
zip(
runtime["h3_nodes"],
cleanups,
runtime["guiders"],
runtime["samplers"],
),
start=1,
):
_assert_origin(subeditor, cleanup, CLEANUP_INPUT, h3, "positive")
_assert_origin(subeditor, guider, "conditioning", cleanup, CLEANUP_OUTPUT)
_assert_origin(subeditor, sampler, "latent_image", h3, "LATENT")
cleanup_output = cleanup["outputs"][
_socket(cleanup, "outputs", CLEANUP_OUTPUT)
]
if len(cleanup_output.get("links") or []) != 1:
raise WorkflowError(
f"H3 lane {lane} cleanup must feed only its matching guider."
)
semantics = marker.get("generation_semantics") or {}
if semantics != {
"lazy_generation_lane_capacity": LANE_COUNT,
"project_and_native_shot_contract_preserved": True,
"conditioning_cleanup_per_lane": True,
"cleanup_node_type": CLEANUP_NODE_TYPE,
"cleanup_backend_output_node": CLEANUP_BACKEND_OUTPUT_NODE,
"latent_path_unchanged": True,
}:
raise WorkflowError("Fast-test generation-semantics metadata is stale.")
source = marker.get("source") or {}
if not source.get("file_sha256"):
raise WorkflowError("Fast-test rollback source SHA-256 is missing.")
if source.get("rollback_policy") != "reopen the unchanged source capture":
raise WorkflowError("Fast-test rollback policy is missing or stale.")
if require_benchmark_defaults:
duration = _one(
_main_nodes(workflow),
lambda node: int(node.get("id", -1)) == 178,
"duration control",
)
splitter = _one(
_main_nodes(workflow),
lambda node: int(node.get("id", -1)) == 187,
"resolution control",
)
if (duration.get("widgets_values") or [None])[0] != FAST_DURATION_SECONDS:
raise WorkflowError("The initial fast benchmark must default to five seconds.")
if (splitter.get("widgets_values") or [None, None])[1] != FAST_RESOLUTION_MEGAPIXELS:
raise WorkflowError("The initial fast benchmark must default to 0.4 MP.")
def _exclusive_json_write(path: Path, workflow: dict[str, Any]) -> None:
"""Create a new JSON file and refuse to overwrite any existing sibling."""
path = path.resolve()
path.parent.mkdir(parents=True, exist_ok=True)
payload = json.dumps(workflow, ensure_ascii=False, indent=2).encode("utf-8") + b"\n"
descriptor: int | None = None
created = False
try:
descriptor = os.open(
path,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0),
0o600,
)
created = True
with os.fdopen(descriptor, "wb") as handle:
descriptor = None
handle.write(payload)
handle.flush()
os.fsync(handle.fileno())
except FileExistsError as exc:
raise WorkflowError(
f"Refusing to overwrite existing fast sibling: {path}"
) from exc
except Exception:
if descriptor is not None:
os.close(descriptor)
if created and path.exists() and path.stat().st_size != len(payload):
path.unlink()
raise
def migrate_file(
source_path: Path,
*,
output_path: Path,
write: bool,
expected_source_sha256: str | None = None,
) -> tuple[dict[str, Any], str, Path | None]:
source_path = source_path.resolve()
output_path = output_path.resolve()
if source_path == output_path:
raise WorkflowError("Fast-test output must be a new sibling, never the source.")
if not source_path.is_file():
raise WorkflowError(f"Workflow does not exist: {source_path}")
source_bytes = source_path.read_bytes()
source_hash = _file_hash(source_bytes)
if expected_source_sha256 and source_hash.casefold() != expected_source_sha256.casefold():
raise WorkflowError(
f"Source SHA-256 mismatch: {source_hash} != {expected_source_sha256}."
)
try:
source = json.loads(source_bytes.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise WorkflowError(f"Workflow is not valid UTF-8 JSON: {source_path}") from exc
migrated = migrate_workflow(source, source_file_sha256=source_hash)
validate_workflow(migrated, require_benchmark_defaults=True)
written: Path | None = None
if write:
if _file_hash(source_path.read_bytes()) != source_hash:
raise WorkflowError("Source changed during migration; no sibling was written.")
if output_path.exists():
existing_bytes = output_path.read_bytes()
try:
existing = json.loads(existing_bytes.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise WorkflowError(
f"Refusing to overwrite invalid existing sibling: {output_path}"
) from exc
validate_workflow(existing)
if existing != migrated:
raise WorkflowError(
f"Refusing to overwrite user-edited fast sibling: {output_path}"
)
else:
_exclusive_json_write(output_path, migrated)
written = output_path
if _file_hash(source_path.read_bytes()) != source_hash:
raise WorkflowError("Source changed while writing the sibling.")
return migrated, source_hash, written
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("source", nargs="?", type=Path, default=DEFAULT_SOURCE)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
parser.add_argument(
"--check",
action="store_true",
help="Migrate and validate in memory without writing a sibling.",
)
parser.add_argument(
"--expected-source-sha256",
default=SOURCE_CAPTURE_SHA256,
help="Exact source guard; defaults to the captured unsaved workflow hash.",
)
args = parser.parse_args(argv)
migrated, source_hash, written = migrate_file(
args.source,
output_path=args.output,
write=not args.check,
expected_source_sha256=args.expected_source_sha256,
)
action = "checked" if args.check else ("created" if written else "unchanged")
print(
f"{action} source={args.source.resolve()} source_sha256={source_hash} "
f"result_sha256={_canonical_hash(migrated)} output={args.output.resolve()}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())