Files
hermes-skills/creative/comfyui/references/ideogram4.md
T

11 KiB

Ideogram 4 in ComfyUI

Two distinct execution paths exist. Pick the right one based on auth and hardware.

Path A: Cloud API (IdeogramV4 node)

  • Node: IdeogramV4 (built-in, from comfy_api_nodes.nodes_ideogram)
  • Inputs: prompt (STRING), resolution (COMBO), rendering_speed (COMBO), seed (INT)
  • Hidden inputs: auth_token_comfy_org, api_key_comfy_org
  • Requires: comfy.org account + API key (set via auth_token or api_key_comfy_org)

Common error

Unauthorized: Please login first to use this node.

→ Set the api_key_comfy_org hidden input or login in the ComfyUI web UI.

Path B: Local inference (Ideogram4Scheduler node)

  • Node: Ideogram4Scheduler (built-in, comfy_extras.nodes_ideogram4)
  • Outputs: SIGMAS — feed into SamplerCustomAdvanced
  • Workflow structure: Ideogram4SchedulerSamplerCustomAdvancedVAEDecodeSaveImage

Why you can't just "use the blueprint directly"

The official blueprint at blueprints/Text to Image (Ideogram v4).json is in editor format ("nodes" / "links" arrays with a top-level definitions.subgraphs array). The ComfyUI REST API (POST /api/prompt) does NOT accept editor format. Submitting it produces HTTP 400 or 500 with no useful message. You must either:

  1. Load the blueprint in the ComfyUI web UI and re-export it as API format (Workflow → Export API), OR
  2. Manually construct an API-format workflow using the verified node wiring below.

Critical: Ideogram 4 needs asymmetric CFG wiring

Unlike standard pipelines that use BasicGuider, Ideogram 4 requires CFGOverride + DualModelGuider for asymmetric classifier-free guidance:

  • CFGOverride sits on the main model to override the CFG value in a specific timestep range
  • DualModelGuider takes the CFG-overridden main model, the positive conditioning, a separate unconditional model (via model_negative), and the zeroed-out conditioning (via ConditioningZeroOut)
  • The guider output (GUIDER) feeds into SamplerCustomAdvanced

Do NOT use: plain BasicGuider (only supports single model + pos/neg), standard KSampler (doesn't use Ideogram4Scheduler sigmas), or model_1 as the DualModelGuider input name (it's model_negative).

Node input names for API-format workflows (verified against /object_info)

Node Required inputs Optional inputs Notes
Ideogram4Scheduler steps, width, height, mu, std Outputs SIGMAS for SamplerCustomAdvanced
CFGOverride model, cfg, start_percent, end_percent Place on main UNet between UNETLoader and DualModelGuider
DualModelGuider model, positive, cfg model_negative (MODEL), negative (CONDITIONING) model_negative is the unconditional UNet (NOT model_1)
CLIPLoader clip_name type (use "ideogram4"), device Must use type: "ideogram4" for Qwen3VL text encoder
EmptyFlux2LatentImage width, height, batch_size 128-channel latent (NOT standard EmptyLatentImage)
SamplerCustomAdvanced noise, guider, sampler, sigmas, latent_image guider comes from DualModelGuider, sigmas from Ideogram4Scheduler

Required models

Component Filename Path Size Source
UNet (main) ideogram4_fp8_scaled.safetensors models/diffusion_models/ ~8.6 GB Comfy-Org/Ideogram-4
UNet (unconditional) ideogram4_unconditional_fp8_scaled.safetensors models/diffusion_models/ ~8.6 GB Comfy-Org/Ideogram-4
VAE flux2-vae.safetensors models/vae/ ~320 MB Comfy-Org/flux2-dev
Text encoder (A) qwen3vl_8b_fp8_scaled.safetensors models/text_encoders/ ~9.8 GB Comfy-Org/Qwen3-VL
Text encoder (B) gemma4_e4b_it_fp8_scaled.safetensors models/text_encoders/ ~? GB Comfy-Org/gemma-4

Known corruption: qwen3vl_8b_fp8_scaled.safetensors

A truncated download of this ~9.8 GB file produces:

Error while deserializing header: incomplete metadata, file not fully covered

at ComfyUI's CLIPLoader (node load_clipcomfy.utils.load_torch_file).

Quick integrity check (run inside the ComfyUI container or on the host):

python3 -c "
import struct, json, os
p = 'models/text_encoders/qwen3vl_8b_fp8_scaled.safetensors'
f = open(p, 'rb'); hl = struct.unpack('<Q', f.read(8))[0]
m = json.loads(f.read(hl)); t = 8 + hl
for k,v in m.items():
    if isinstance(v, dict) and 'data_offsets' in v:
        t += v['data_offsets'][1] - v['data_offsets'][0]
a = os.path.getsize(p); print(f'expected={t} actual={a} ok={t==a}')
"

If ok=False, the download is incomplete. Re-download via:

curl -L -o models/text_encoders/qwen3vl_8b_fp8_scaled.safetensors \
  "https://huggingface.co/Comfy-Org/Qwen3-VL/resolve/main/text_encoders/qwen3vl_8b_fp8_scaled.safetensors"

Alternative: safetensors.safe_open also catches corruption early:

from safetensors import safe_open
f = safe_open("qwen3vl_8b_fp8_scaled.safetensors", framework="pt", device="cpu")
print(len(f.keys()))  # Should be ~1254; crashes on truncated files

Pitfall: wrong input name for DualModelGuider

The Blueprint user's Subgraphs-based DualModelGuider uses the internal editor-format link name model_1, but the API-format input name is model_negative:

# WRONG (editor convention leaked into API format):
"inputs": {"model": ["X", 0], "model_1": ["Y", 0], ...}

# RIGHT (API format):
"inputs": {"model": ["X", 0], "model_negative": ["Y", 0], ...}

Error if wrong: TypeError: DualModelGuider.execute() got an unexpected keyword argument 'model_1'

Full local workflow blueprint (extracted from ComfyUI /history)

The official blueprint at blueprints/Text to Image (Ideogram v4).json is in editor format ("nodes" / "links" arrays). It must be converted to API format ("class_type" per node) before execution. Below is a verified API-format workflow:

{
  "37": {
    "inputs": {"aspect_ratio": "1:1 (Square)", "megapixels": 1.0, "multiple": 8},
    "class_type": "ResolutionSelector"
  },
  "98:9":  {"inputs": {"vae_name": "flux2-vae.safetensors"},                          "class_type": "VAELoader"},
  "98:11": {"inputs": {"width": ["98:31", 1], "height": ["98:32", 1], "batch_size": 1}, "class_type": "EmptyFlux2LatentImage"},
  "98:12": {"inputs": {"noise": ["98:18", 0], "guider": ["98:155", 0], "sampler": ["98:16", 0], "sigmas": ["98:17", 0], "latent_image": ["98:11", 0]}, "class_type": "SamplerCustomAdvanced"},
  "98:13": {"inputs": {"samples": ["98:12", 0], "vae": ["98:9", 0]},                     "class_type": "VAEDecode"},
  "98:14": {"inputs": {"clip_name": "qwen3vl_8b_fp8_scaled.safetensors", "type": "ideogram4", "device": "default"}, "class_type": "CLIPLoader"},
  "98:16": {"inputs": {"sampler_name": "euler"},                                      "class_type": "KSamplerSelect"},
  "98:17": {"inputs": {"steps": ["98:151", 1], "width": ["98:31", 1], "height": ["98:32", 1], "mu": ["98:144", 0], "std": ["98:146", 0]}, "class_type": "Ideogram4Scheduler"},
  "98:18": {"inputs": {"noise_seed": 42},                                              "class_type": "RandomNoise"},
  "98:23": {"inputs": {"unet_name": "ideogram4_fp8_scaled.safetensors", "weight_dtype": "default"}, "class_type": "UNETLoader"},
  "98:24": {"inputs": {"text": "PROMPT_HERE", "clip": ["98:14", 0]},                     "class_type": "CLIPTextEncode"},
  "98:10": {"inputs": {"conditioning": ["98:24", 0]},                                    "class_type": "ConditioningZeroOut"},
  "98:31": {"inputs": {"expression": "max(((a + 15) // 16) * 16, 256)", "values.a": ["98:27", 0]}, "class_type": "ComfyMathExpression"},
  "98:32": {"inputs": {"expression": "max(((a + 15) // 16) * 16, 256)", "values.a": ["98:28", 0]}, "class_type": "ComfyMathExpression"},
  "98:27": {"inputs": {"value": ["37", 0]},                                             "class_type": "PrimitiveInt"},
  "98:28": {"inputs": {"value": ["37", 1]},                                             "class_type": "PrimitiveInt"},
  "98:144": {"inputs": {"value": ["98:145", 0]},                                        "class_type": "ComfyNumberConvert"},
  "98:145": {"inputs": {"json_string": ["98:148", 0], "key": "mu"},                    "class_type": "JsonExtractString"},
  "98:146": {"inputs": {"value": ["98:150", 0]},                                        "class_type": "ComfyNumberConvert"},
  "98:147": {"inputs": {"json_string": "{\\\"Quality\\\":{\\\"num_steps\\\":48,\\\"mu\\\":0.0,\\\"std\\\":1.5,\\\"preset\\\":\\\"Quality\\\"}}", "key": "Quality"}, "class_type": "JsonExtractString"},
  "98:148": {"inputs": {"string": ["98:147", 0], "find": "'", "replace": "\\""},        "class_type": "StringReplace"},
  "98:149": {"inputs": {"json_string": ["98:148", 0], "key": "num_steps"},              "class_type": "JsonExtractString"},
  "98:150": {"inputs": {"json_string": ["98:148", 0], "key": "std"},                    "class_type": "JsonExtractString"},
  "98:151": {"inputs": {"value": ["98:149", 0]},                                        "class_type": "ComfyNumberConvert"},
  "98:154": {"inputs": {"unet_name": "ideogram4_unconditional_fp8_scaled.safetensors", "weight_dtype": "default"}, "class_type": "UNETLoader"},
  "98:155": {"inputs": {"cfg": 7.0, "model": ["98:157", 0], "positive": ["98:24", 0], "model_negative": ["98:154", 0], "negative": ["98:10", 0]}, "class_type": "DualModelGuider"},
  "98:156": {"inputs": {"choice": "Quality", "index": 1, "option1": "Quality", "option2": "Default", "option3": "Turbo", "option4": ""}, "class_type": "CustomCombo"},
  "98:157": {"inputs": {"cfg": 3.0, "start_percent": 0.7, "end_percent": 1.0, "model": ["98:23", 0]}, "class_type": "CFGOverride"},
  "158":   {"inputs": {"filename_prefix": "Ideogram_4.0", "images": ["98:13", 0]},         "class_type": "SaveImage"}
}

Replace "PROMPT_HERE" (node 98:24, field text) with your prompt. Ideogram 4 accepts either plain text or structured JSON prompts.

Accessing a ComfyUI LXC container via Proxmox VE

If ComfyUI runs inside a Proxmox LXC container without direct SSH:

# Proxmox node shell → run inside container
pct exec CTID -- bash -c "COMMAND"

# Push a file into the container (avoid nested quote hell)
pct push CTID /local/script.py /tmp/script.py
pct exec CTID -- python3 /tmp/script.py

# Common CTID in this environment: 204 (ComfyUI+FLUX.2/ROCm)

Key differences from FLUX.1

  • Text encoder: qwen3vl_8b_fp8_scaled or gemma4_e4b_it_fp8_scaled — NOT CLIP-L + T5XXL
  • Latent: EmptyFlux2LatentImage (128 channels)
  • VAE: flux2-vae.safetensors — NOT ae.safetensors
  • CFG: Uses asymmetric CFG (unconditional conditioning is zeroed out via ConditioningZeroOut, not a separate negative prompt string)

Quick verification checklist

  1. curl $HOST:8188/models/diffusion_models | grep ideogram4 — models present?
  2. Check file integrity of qwen3vl_8b_fp8_scaled.safetensors with the Python check above.
  3. For cloud: ensure api_key_comfy_org is set or the user is logged into comfy.org via the web UI.
  4. For local: verify the blueprint was exported as API format, not editor format (which has top-level "nodes" / "links" arrays).