Nodes/Multimodal — LLM (Local-first: GGUF + Transformers)/Multimodal — LLM (local first: GGUF + optional transformers)
ComfyUI Node

Multimodal — LLM (local first: GGUF + optional transformers)

An LLM that lives inside your ComfyUI graph, reads up to 3 images, and never needs an API key

By theonetwoone·Created 6 months ago·Updated 2 months ago· 2
Multimodal — LLM (local first: GGUF + optional transformers)
  • image
  • image2
  • image3
  • text
◄use_visiontrue►
◄load_sourceauto►
◄gguf_main_models_llm— use path fields below —►
◄gguf_mmproj_models_llm— use path fields below —►
◄gguf_model_path►
◄gguf_mmproj_path►
◄gguf_vlm_handlerqwen3-vl►
◄gguf_allow_qwen25_if_qwen3_handler_missingtrue►
◄gguf_n_ctx12288►
◄gguf_streamingfalse►
◄gguf_chat_template_enable_thinkingfalse►
◄include_thinking_in_outputfalse►
◄thinking_open_tag<think>►
◄thinking_close_tag</think>►
◄combined_model►
◄system_prompt►
◄extra_context►
◄prompt►
◄max_new_tokens5000►
◄do_samplefalse►
◄generation_seed_modefixed►
◄generation_seed0►
◄temperature0.70►
◄top_p0.95►
◄top_k20►
◄repetition_penalty1.00►
◄use_repetition_penaltytrue►
◄deviceauto►
◄dtypeauto►
◄trust_remote_codefalse►
◄keep_models_loadedfalse►
◄offload_image_modefalse►
◄log_load_detailstrue►

This is the node that makes the whole pack make sense. It runs a real LLM or vision-language model as a node inside your ComfyUI graph - same Python process, no separate server, no OpenAI-style API, no key. You hand it text and up to three images, it hands you a text string, and whatever you do with that string is your business. The classic use: image → VLM describes what it sees → LLM writes a prompt → that prompt feeds CLIP Encode → KSampler. A closed loop where the machine reads its own output and decides what to generate next, with no copy/paste between windows.

The name "Multimodal - LLM" undersells it. The thing to internalize first is that there are two entirely different engines under the same hood.

GGUF first, transformers as the fallback

The default path is GGUF + mmproj through llama-cpp-python - the same format and library that powers llama.cpp. You point it at two files sitting in ComfyUI/models/llm/<your_model>/: the main weights and the vision projector (mmproj-*.gguf). Both have to come from the same release family; mixing a Qwen main with a Gemma mmproj fails loudly or silently degrades. Supported families are Qwen3-VL, Qwen2.5-VL, Gemma 3, and LLaVA 1.5/1.6, selected via gguf_vlm_handler. This is the part where people get burned: pick the wrong handler and you get "can't see the image" or empty/odd output. If your weights are Qwen3-VL, gguf_vlm_handler must be qwen3-vl, and your llama-cpp-python build has to actually export Qwen3VLChatHandler (see the wheel picker article in this pack for that).

The optional second engine is transformers (Hugging Face). Fill combined_model with an HF id like Qwen/Qwen3-4B or a local snapshot folder and the node loads it through the transformers pipeline instead. load_source (auto/local/huggingface) only applies to that field - it tells the node whether combined_model is a path on disk or something to fetch. Leave combined_model empty for pure GGUF workflows, which covers text-only too.

The inputs that matter

use_vision is the big switch. On, the model sees the linked image/image2/image3 sockets; off, linked images are ignored - ComfyUI can't hide a socket, but the model never sees the pixels. For an image-only run, leave the text fields empty. Then the sampling stack, which you'll recognize from any LLM UI: temperature (0.7), top_p (0.95), top_k (20), repetition_penalty, and max_new_tokens (default 5000 - drop it if the node feels sluggish). generation_seed_mode is either fixed or increment_each_run; the seed controls sampling randomness, not a chat session. There's no built-in chat memory - that's what system_prompt + extra_context are for, and this pack's helper nodes (System prompter, Context handler) exist to feed those two fields.

keep_models_loaded is worth a warning: it only caches this extension's HF pipelines and llama-cpp instances. It does not stop ComfyUI from unloading SD checkpoints. If you're fighting VRAM, offload_image_mode asks ComfyUI to free image-model memory before loading the LLM, and log_load_details prints which branch actually ran (GGUF+VLM, GGUF+text, HF pipeline, causal LM) to the console - invaluable when the output makes no sense.

Install and the one dependency trap

Install it like any custom node: ComfyUI Manager, search "Multimodal" and install theonetwoone/Multimodal-LLM-ComfyUI-node, or:

cd ComfyUI/custom_nodes
git clone https://github.com/theonetwoone/Multimodal-LLM-ComfyUI-node
pip install -r Multimodal-LLM-ComfyUI-node/requirements.txt

The requirements pull in transformers, accelerate, sentencepiece, and llama-cpp-python>=0.3.0 into the same Python environment ComfyUI uses. The trap: GGUF vision needs a CUDA-capable llama-cpp-python wheel that matches your Python version, platform, and CUDA tag, and the default pip one often isn't it. The pack ships a wheel-picker node and a CLI (python -m llm_comfy_multimodal.install_llama_wheel) to find a matching build from GitHub Releases - use them. Then restart ComfyUI.

Where people get stuck: "cannot import name Qwen3VLChatHandler" means your llama-cpp-python build predates Qwen3-VL support; "slow or OOM" usually means gguf_n_ctx too high, streaming on, or a second ComfyUI instance squatting on the GPU. If the dropdown fields look scrambled on an old workflow, delete and re-add the node.

Categoryllm/multimodal

Inputs (36)

NameTypeDefaultDescription
use_visionBOOLEANtrue—
load_sourceCOMBOauto3 options: auto, local, huggingface
gguf_main_models_llmCOMBO— use path fields below —1 options: — use path fields below —
gguf_mmproj_models_llmCOMBO— use path fields below —1 options: — use path fields below —
gguf_model_pathSTRING—
gguf_mmproj_pathSTRING—
gguf_vlm_handlerCOMBOqwen3-vl5 options: qwen3-vl, qwen2.5-vl, gemma3, llava-1.5, llava-1.6
gguf_allow_qwen25_if_qwen3_handler_missingBOOLEANtrue—
gguf_n_ctxINT12288512–131072—
gguf_streamingBOOLEANfalse—
gguf_chat_template_enable_thinkingBOOLEANfalse—
include_thinking_in_outputBOOLEANfalse—
thinking_open_tagSTRING<think>—
thinking_close_tagSTRING</think>—
combined_modelSTRING—
system_promptSTRING—
extra_contextSTRING—
promptSTRING—
max_new_tokensINT50000–65536—
do_sampleBOOLEANfalse—
generation_seed_modeCOMBOfixed2 options: fixed, increment_each_run
generation_seedINT00–4294967295—
temperatureFLOAT0.700–2—
top_pFLOAT0.950–128—
top_kINT20-1–100—
repetition_penaltyFLOAT1.000.5–2—
use_repetition_penaltyBOOLEANtrue—
deviceCOMBOauto3 options: auto, cuda, cpu
dtypeCOMBOauto6 options: auto, bfloat16, float16, float32, False, True
trust_remote_codeBOOLEANfalse—
keep_models_loadedBOOLEANfalse—
offload_image_modeBOOLEANfalse—
log_load_detailsBOOLEANtrue—
imageoptIMAGE—
image2optIMAGE—
image3optIMAGE—

Outputs (1)

NameTypeDescription
textSTRING—