Instructions to use unsloth/MiniMax-H3-Image-GGUF with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Local Apps Settings
- Unsloth Desktop
Missing pdd_sigmas configuration when loading the GGUF model in ComfyUI
Loading the GGUF model in ComfyUI results in the following error:
[INFO] got prompt
[INFO] [Queue Manager] Executing workflow: H3Image_minimax_h3_image_edit at 27
[INFO] gguf qtypes: F32 (6), BF16 (263), F16 (51), Q6_K (52), Q5_K (96), Q4_K (60)
[ERROR] !!! Exception during processing !!! MiniMaxH3ImageModel.__init__() missing 1 required positional argument: 'pdd_sigmas'
[ERROR] Traceback (most recent call last):
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\execution.py", line 556, in execute
output_data, output_ui, has_subgraph, has_pending_tasks = await get_output_data(prompt_id, unique_id, obj, input_data_all, execution_block_cb=execution_block_cb, pre_execute_cb=pre_execute_cb, v3_data=v3_data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\execution.py", line 353, in get_output_data
return_values = await _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, obj.FUNCTION, allow_interrupt=True, execution_block_cb=execution_block_cb, pre_execute_cb=pre_execute_cb, v3_data=v3_data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\execution.py", line 327, in _async_map_node_over_list
await process_inputs(input_dict, i)
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\execution.py", line 315, in process_inputs
result = f(**inputs)
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\custom_nodes\ComfyUI-GGUF_leejet\nodes.py", line 176, in load_unet
model = comfy.sd.load_diffusion_model_state_dict(
sd, model_options={"custom_operations": ops}, **kwargs,
)
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\comfy\sd.py", line 2556, in load_diffusion_model_state_dict
model = model_config.get_model(new_sd, "")
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\comfy\supported_models.py", line 1023, in get_model
return model_base.MiniMaxH3Image(self, device=device)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\comfy\model_base.py", line 2431, in __init__
super().__init__(model_config, model_type, device=device, unet_model=comfy.ldm.minimax.model.MiniMaxH3ImageModel)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\comfy\model_base.py", line 2228, in __init__
super().__init__(model_config, model_type, device=device, unet_model=unet_model)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "G:\cht\src\ComfyUI_windows_portable\ComfyUI\comfy\model_base.py", line 190, in __init__
self.diffusion_model = unet_model(**unet_config, device=device, operations=operations)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: MiniMaxH3ImageModel.__init__() missing 1 required positional argument: 'pdd_sigmas'
The traceback indicates that MiniMaxH3ImageModel requires the pdd_sigmas argument during model initialization, but this argument is missing from the configuration passed by the GGUF loader.
The official MiniMax-H3-Image weights provided by Comfy include the required six-step PDD sampling configuration in their metadata:
{
"transformer": {
"pdd_steps": 6,
"pdd_sigmas": [
1.0,
0.9675236940383911,
0.8875485062599182,
0.75,
0.5327739119529724,
0.23200887441635132,
0.0
]
}
}
This configuration can be confirmed in the metadata of the official MiniMax-H3-Image BF16 checkpoint.
For anyone using ComfyUI who encounters the same error with a GGUF file that does not include this configuration, one possible workaround is to add the corresponding pdd_steps and pdd_sigmas values under config.transformer in the GGUF metadata.
This approach worked in my testing. However, it also depends on the GGUF loader correctly parsing the embedded config.transformer metadata and passing these values to MiniMaxH3ImageModel.
add_minimax_h3_pdd_config.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Add the official MiniMax-H3-Image 6-step PDD schedule to one target GGUF.
The target's quantized tensors are copied byte-for-byte into a new GGUF.
Only the JSON metadata field named "config" is added or updated.
Requires:
py -m pip install -U gguf numpy tqdm
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any, NamedTuple
try:
import gguf
except ImportError:
print("[ERROR] Missing package: gguf")
print("Install with: py -m pip install -U gguf numpy tqdm")
input("Press Enter to exit...")
raise SystemExit(1)
try:
from tqdm import tqdm
except ImportError:
tqdm = None
PDD_STEPS = 6
PDD_SIGMAS = [
1.0,
0.9675236940383911,
0.8875485062599182,
0.75,
0.5327739119529724,
0.23200887441635132,
0.0,
]
CONFIG_KEY = "config"
class MetadataValue(NamedTuple):
value_type: Any
value: Any
sub_type: Any | None = None
def ask_existing_file(prompt: str) -> Path:
while True:
raw = input(prompt).strip().strip('"')
path = Path(raw).expanduser()
if path.is_file():
return path.resolve()
print(f"[ERROR] File not found: {path}")
def field_value(field: Any) -> Any:
return field.contents()
def metadata_from_field(field: Any) -> MetadataValue:
value_type = field.types[0]
sub_type = field.types[-1] if value_type == gguf.GGUFValueType.ARRAY else None
return MetadataValue(value_type, field_value(field), sub_type)
def normalize_string(value: Any) -> str:
if isinstance(value, bytes):
return value.decode("utf-8", errors="strict")
if isinstance(value, (list, tuple)) and len(value) == 1:
return normalize_string(value[0])
return str(value)
def get_value(reader: Any, key: str) -> Any:
field = reader.get_field(key)
return field_value(field) if field else None
def build_config_metadata(reader: Any) -> MetadataValue:
field = reader.get_field(CONFIG_KEY)
if field is None:
config: dict[str, Any] = {}
value_type = gguf.GGUFValueType.STRING
else:
raw = normalize_string(field_value(field))
try:
decoded = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError(
f"Target metadata {CONFIG_KEY!r} is not valid JSON: {exc}"
) from exc
if not isinstance(decoded, dict):
raise ValueError(f"Target metadata {CONFIG_KEY!r} is not a JSON object")
config = decoded
value_type = field.types[0]
transformer = config.get("transformer")
if transformer is None:
transformer = {}
config["transformer"] = transformer
if not isinstance(transformer, dict):
raise ValueError("config.transformer exists but is not a JSON object")
transformer["pdd_steps"] = PDD_STEPS
transformer["pdd_sigmas"] = PDD_SIGMAS
compact = json.dumps(config, ensure_ascii=False, separators=(",", ":"))
return MetadataValue(value_type, compact, None)
def write_repaired_gguf(reader: Any, output_path: Path, new_config: MetadataValue) -> None:
arch_raw = get_value(reader, gguf.Keys.General.ARCHITECTURE)
if arch_raw is None:
raise RuntimeError("Target GGUF has no general.architecture metadata")
arch = normalize_string(arch_raw)
writer = gguf.GGUFWriter(
output_path,
arch=arch,
endianess=reader.endianess,
)
alignment = get_value(reader, gguf.Keys.General.ALIGNMENT)
if alignment is not None:
writer.data_alignment = int(alignment)
config_written = False
for field in reader.fields.values():
name = field.name
if name == gguf.Keys.General.ARCHITECTURE or name.startswith("GGUF."):
continue
selected = new_config if name == CONFIG_KEY else metadata_from_field(field)
if name == CONFIG_KEY:
config_written = True
writer.add_key_value(
name,
selected.value,
selected.value_type,
sub_type=selected.sub_type,
)
if not config_written:
writer.add_key_value(
CONFIG_KEY,
new_config.value,
new_config.value_type,
sub_type=new_config.sub_type,
)
total_bytes = sum(t.n_bytes for t in reader.tensors)
for tensor in reader.tensors:
writer.add_tensor_info(
tensor.name,
tensor.data.shape,
tensor.data.dtype,
tensor.data.nbytes,
tensor.tensor_type,
)
writer.write_header_to_file()
writer.write_kv_data_to_file()
writer.write_ti_data_to_file()
bar = tqdm(total=total_bytes, unit="B", unit_scale=True, desc="Writing") if tqdm else None
try:
for tensor in reader.tensors:
writer.write_tensor_data(
tensor.data,
tensor_endianess=reader.endianess,
)
if bar:
bar.update(tensor.n_bytes)
finally:
if bar:
bar.close()
writer.close()
def verify_output(output_path: Path) -> None:
reader = gguf.GGUFReader(output_path, "r")
field = reader.get_field(CONFIG_KEY)
if field is None:
raise RuntimeError("Verification failed: config metadata is missing")
config = json.loads(normalize_string(field_value(field)))
transformer = config.get("transformer", {})
if transformer.get("pdd_steps") != PDD_STEPS:
raise RuntimeError("Verification failed: pdd_steps differs")
if transformer.get("pdd_sigmas") != PDD_SIGMAS:
raise RuntimeError("Verification failed: pdd_sigmas differs")
def main() -> int:
print("MiniMax-H3-Image GGUF 6-step PDD config writer")
print("The input GGUF will not be modified. A new GGUF will be created.\n")
target_path = ask_existing_file("Target GGUF path: ")
default_output = target_path.with_name(target_path.stem + "-pdd-fixed.gguf")
raw_output = input(f"Output path [default: {default_output}]: ").strip().strip('"')
output_path = Path(raw_output).expanduser().resolve() if raw_output else default_output.resolve()
if output_path == target_path:
print("[ERROR] In-place overwrite is blocked. Choose a new output file.")
return 2
if output_path.exists():
answer = input(f"Output exists:\n{output_path}\nType YES to replace it: ")
if answer != "YES":
print("Cancelled.")
return 0
output_path.unlink()
if len(PDD_SIGMAS) != PDD_STEPS + 1:
print("[ERROR] Internal schedule is invalid.")
return 3
if os.name != "nt":
stat = os.statvfs(output_path.parent)
free_bytes = stat.f_bavail * stat.f_frsize
if free_bytes < target_path.stat().st_size + 256 * 1024 * 1024:
print("[ERROR] Not enough free disk space for the new GGUF.")
return 4
print("\n[INFO] Opening target GGUF...")
reader = gguf.GGUFReader(target_path, "r")
new_config = build_config_metadata(reader)
print(f"[INFO] pdd_steps: {PDD_STEPS}")
print(f"[INFO] pdd_sigmas: {PDD_SIGMAS}")
print(f"[INFO] Writing: {output_path}")
print("[INFO] Quantized tensors are copied without dequantization or requantization.")
try:
write_repaired_gguf(reader, output_path, new_config)
verify_output(output_path)
except Exception:
if output_path.exists():
try:
output_path.unlink()
except OSError:
pass
raise
print("\n[SUCCESS] Created and verified:")
print(output_path)
print("The output config now contains transformer.pdd_steps and transformer.pdd_sigmas.")
return 0
if __name__ == "__main__":
try:
exit_code = main()
except KeyboardInterrupt:
print("\nCancelled by user.")
exit_code = 130
except Exception as exc:
print(f"\n[FATAL] {type(exc).__name__}: {exc}")
exit_code = 1
input("\nPress Enter to exit...")
raise SystemExit(exit_code)