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, fromcomfy_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 intoSamplerCustomAdvanced - Workflow structure:
Ideogram4Scheduler→SamplerCustomAdvanced→VAEDecode→SaveImage
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:
- Load the blueprint in the ComfyUI web UI and re-export it as API format (Workflow → Export API), OR
- 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:
CFGOverridesits on the main model to override the CFG value in a specific timestep rangeDualModelGuidertakes the CFG-overridden main model, the positive conditioning, a separate unconditional model (viamodel_negative), and the zeroed-out conditioning (viaConditioningZeroOut)- The guider output (
GUIDER) feeds intoSamplerCustomAdvanced
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_clip → comfy.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_scaledorgemma4_e4b_it_fp8_scaled— NOT CLIP-L + T5XXL - Latent:
EmptyFlux2LatentImage(128 channels) - VAE:
flux2-vae.safetensors— NOTae.safetensors - CFG: Uses asymmetric CFG (unconditional conditioning is zeroed out via
ConditioningZeroOut, not a separate negative prompt string)
Quick verification checklist
curl $HOST:8188/models/diffusion_models | grep ideogram4— models present?- Check file integrity of
qwen3vl_8b_fp8_scaled.safetensorswith the Python check above. - For cloud: ensure
api_key_comfy_orgis set or the user is logged into comfy.org via the web UI. - For local: verify the blueprint was exported as API format, not editor format (which has top-level
"nodes"/"links"arrays).