Skip to content

feat: Support text generation with Qwen3-VL (CORE-276) - #14298

Merged
alexisrolland merged 8 commits into
Comfy-Org:masterfrom
kijai:qwen3vl
Jun 17, 2026
Merged

alexisrolland merged 8 commits into
Comfy-Org:masterfrom
kijai:qwen3vl

Conversation

@kijai

@kijai kijai commented Jun 5, 2026 •

Copy link
Copy Markdown
Collaborator

Adds support to do text generation with image understanding using existing Qwen3-VL 4b and 8b models.

https://huggingface.co/Comfy-Org/Qwen3-VL

Video support is still missing from both 3 and 3.5, that would be separate PR to add for both at same time.

image

New file

  • New comfy/text_encoders/qwen3vl.py — model, tokenizer, and TE wrappers (4B uses tied word embeddings, 8B has a separate lm_head).
  • Reuses the Qwen3.5 vision tower plus DeepStack: per-layer visual features injected into the first decoder layers at image positions.
  • Interleaved MRoPE enabled via the existing interleaved_mrope config flag (rope_dims=[24, 20, 20]).
  • Auto-detection keyed on the Qwen3-VL-unique model.visual.deepstack_merger_list weights; 4B vs 8B split by merger output dim.

Shared-code changes (additive, None-safe defaults)

  • BaseGenerate.generate — threads position_ids / deepstack_embeds through prefill and advances the MRoPE position during decode.
  • Llama2_.forward — optional DeepStack injection at visual positions.
  • Extracted the Qwen2-VL MRoPE position-id logic into a shared qwen2vl_mrope_position_ids helper (qwen35 now reuses it).

Compatibility

No behavioral changes to other models — the rope code is untouched, and the generate change is a no-op when position_ids is None.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds Qwen3-VL, a multimodal text encoder combining a LLaMA-based backbone with vision processing and DeepStack visual feature injection. The implementation extends the model registry, adds position_ids and deepstack input support throughout the LLaMA forward and generation paths, introduces a vision feature merger for per-layer injection, unifies text and vision processing in a Qwen3VL model class, and provides CLIP-compatible wrappers and tokenizers that replace image placeholders with actual embedding data during inference.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Qwen3-VL text generation support with a specific ticket reference.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the new Qwen3-VL integration, DeepStack features, shared code changes, and compatibility guarantees.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
comfy/text_encoders/llama.py (1)

773-786: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Inject DeepStack before the decoder layer.

These features are added only after layer(...) has already consumed x and after current_kv has been produced. That shifts the conditioning by one layer and leaves the cached K/V for the affected layers missing the injected visual state during decode.

Suggested fix
         for i, layer in enumerate(self.layers):
+            if deepstack_embeds is not None and i < len(deepstack_embeds):
+                x[visual_pos_masks] = x[visual_pos_masks] + deepstack_embeds[i].to(x)
+
             if all_intermediate is not None:
                 if only_layers is None or (i in only_layers):
                     all_intermediate.append(x.unsqueeze(1).clone())
@@
             if current_kv is not None:
                 next_key_values.append(current_kv)
-
-            # DeepStack: add per-layer visual features into the first len() decoder layers at image positions (Qwen3-VL)
-            if deepstack_embeds is not None and i < len(deepstack_embeds):
-                x[visual_pos_masks] = x[visual_pos_masks] + deepstack_embeds[i].to(x)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@comfy/text_encoders/llama.py` around lines 773 - 786, The DeepStack visual
embeddings are being added after calling layer(...) which causes the layer to
compute attention keys/values without the visual state and shifts conditioning
by one layer; move the block that applies deepstack_embeds (using
deepstack_embeds, visual_pos_masks, and x) to just before calling layer(...) so
the layer receives x already augmented, and ensure
past_kv/current_kv/next_key_values usage remains unchanged so cached K/Vs
include the visual features during decode (adjust the order around the
layer(...) invocation and past_key_value handling accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@comfy/text_encoders/qwen_vl.py`:
- Around line 91-114: The function qwen2vl_mrope_position_ids incorrectly
assigns compact post-image MRoPE indices across every remaining slot (including
padding); update it to accept an attention_mask (or compute one from
embeds_info) and only advance/assign the post-image position_ids for real tokens
(where attention_mask == 1). Specifically, change the post-image assignment that
writes to position_ids[:, end:] to iterate or mask by attention_mask[:, end:],
compute the compacted indices only for non-padded token positions (preserving
their relative order), and leave padded positions unchanged so later real tokens
keep the correct smaller MRoPE indices; keep all other logic (start, end,
offset, grid-derived ranges) in qwen2vl_mrope_position_ids. Ensure parameter
list (seq_len, device) is updated to include attention_mask and update callers
accordingly.

In `@comfy/text_encoders/qwen3vl.py`:
- Line 6: The module currently imports Qwen2Tokenizer at top-level which forces
transformers to be imported on module import; move the import into
Qwen3VLSDTokenizer.__init__ so it’s loaded lazily (follow the same pattern used
in qwen35.py): remove the top-level "from transformers import Qwen2Tokenizer"
and instead perform the import inside Qwen3VLSDTokenizer.__init__ (e.g., "from
transformers import Qwen2Tokenizer" or equivalent try/except within the
constructor) and assign or use Qwen2Tokenizer there so the transformers
dependency is only imported when an instance is constructed.

---

Outside diff comments:
In `@comfy/text_encoders/llama.py`:
- Around line 773-786: The DeepStack visual embeddings are being added after
calling layer(...) which causes the layer to compute attention keys/values
without the visual state and shifts conditioning by one layer; move the block
that applies deepstack_embeds (using deepstack_embeds, visual_pos_masks, and x)
to just before calling layer(...) so the layer receives x already augmented, and
ensure past_kv/current_kv/next_key_values usage remains unchanged so cached K/Vs
include the visual features during decode (adjust the order around the
layer(...) invocation and past_key_value handling accordingly).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46c77a71-c1ef-43dc-93fe-272ed6075859

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa71b9 and 745634a.

📒 Files selected for processing (5)
  • comfy/sd.py
  • comfy/text_encoders/llama.py
  • comfy/text_encoders/qwen35.py
  • comfy/text_encoders/qwen3vl.py
  • comfy/text_encoders/qwen_vl.py

Comment thread comfy/text_encoders/qwen_vl.py
Comment thread comfy/text_encoders/qwen3vl.py
@kijai
kijai marked this pull request as draft June 5, 2026 14:57
@kijai
kijai marked this pull request as ready for review June 5, 2026 19:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@comfy_extras/nodes_textgen.py`:
- Line 38: The max_length UI bound in io.Int.Input("max_length", default=512,
min=1, max=32768) must be validated against the model's actual context capacity
and interpreted as decode tokens vs total context to avoid runtime OOB errors;
update the node (comfy_extras/nodes_textgen.py) to query or accept the
encoder/model context limit (from comfy/text_encoders/qwen3vl.py or the model
metadata) and clamp the user-provided max_length so that prompt_length +
generated_tokens <= model_max_context, and add a clear validation/error message
when the requested decode tokens would exceed the model’s effective context
window (document that max_length is decode tokens, not total context, or compute
max_decode = model_max_context - prompt_length and enforce max_length <=
max_decode).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ca0bc6f-dc70-408b-8cc8-0965153b7225

📥 Commits

Reviewing files that changed from the base of the PR and between 43ffcd6 and 5e4f7a9.

📒 Files selected for processing (1)
  • comfy_extras/nodes_textgen.py

Comment thread comfy_extras/nodes_textgen.py
@alexisrolland

Copy link
Copy Markdown
Member

I confirm this is working for still images, video will need support in a separate PR in the future.

@alexisrolland
alexisrolland merged commit fc96404 into Comfy-Org:master Jun 17, 2026
14 checks passed
feice-huang added a commit to feice-huang/ComfyUI that referenced this pull request Jun 17, 2026
Upstream merged native Qwen3-VL support (Comfy-Org#14298), adding
comfy/text_encoders/qwen3vl.py plus helpers in qwen_vl.py / llama.py /
qwen35.py. The JoyImage port previously shipped its own duplicate
Qwen3-VL implementation (comfy/text_encoders/qwen3_vl.py); that
duplication is now removed and the JoyImage text encoder rides on the
upstream stack.

- Delete comfy/text_encoders/qwen3_vl.py.
- Rewrite comfy/text_encoders/joyimage.py to subclass upstream
  comfy.text_encoders.qwen3vl. The JoyImage checkpoint is a stock
  qwen3vl_8b, so only JoyImage-specific behavior is overridden:
  * Qwen3VL8B_JoyImage.forward builds the 3D MRoPE position ids and
    injects deepstack visual features on the conditioning path. Upstream
    Qwen3VL only does this inside generate() via build_image_inputs;
    SDClipModel.forward never passes those kwargs. The JoyImage node
    feeds an image through the encoder (clip.tokenize(prompt, images=[..])),
    so the override reuses build_image_inputs to reproduce the multimodal
    conditioning that Llama2_.forward already accepts kwargs for.
  * preprocess_embed keeps JoyImage's bicubic+clamp image preprocessing
    (process_qwen3vl_image) instead of upstream's bilinear path, to
    preserve validated DiT numerics.
  * JoyImageTokenizer keeps the JoyImage system-prompt templates,
    suppresses the Qwen3 <think> block, and raises on image-placeholder
    count mismatch.
  * JoyImageTEModel keeps the drop_idx=34 system-prompt strip and the
    pre-final-norm layer tap (layer="hidden", layer_idx=-1).
- sd.py QWEN3VL_8B_JOYIMAGE branch: apply the same state-dict prefix
  remap the sibling QWEN3VL branch uses (model.language_model.->model.,
  model.visual.->visual., lm_head.->model.lm_head.) so the checkpoint
  loads into the upstream Qwen3VL namespace, then use the module-level
  llama_detect. Detection ordering is preserved: the JoyImage
  discriminator is checked before the generic Qwen3-VL deepstack key.

No changes to llama.py / qwen3vl.py / qwen_vl.py / qwen35.py.
@GlamoramaAttack

Copy link
Copy Markdown

The default template workflow for Flux2klein-KV doesn't work any longer - since I've updated today - if I use
qwen3-vl-8b_fp8_scaled.safetensors - CLIP
or
qwen3vl_8b_bf16.safetensors - CLIP from Black Forest Labs
with
flux-2-klein-9b-kv-fp8.safetensors or flux-2-klein-9b-kv.safetensors - models from Black Forest Labs.

But it should work, despite the fact that the default Flux2klein-KV template asks for qwen_3_8b_fp8mixed.safetensors, which I've never used. Instead I've always used the qwen3-vl-8b_fp8_scaled.safetensors and a qwen3-8b-abliterated-v2-fp8.safetensors version worked too!

Now, I assume because the changes were made for qwen3-vl text generation, only the vl-less CLIP qwen3-8b-abliterated-v2-fp8 still works with flux-2-klein-9b-kv-fp8 - but the image quality is slightly worse and Black Forest Labs uses qwen3vl. Using qwen3vl_8b_bf16 or qwen3vl_8b_fp8 is impossible.

KV - OK

Image

KV - NOT OK

Image

[INFO] Model Flux2 prepared for dynamic VRAM loading. 9364MB Staged. 0 patches attached. Force pre-loaded 185 weights: 59 KB.
0%| | 0/4 [00:00<?, ?it/s, Model Initializing ... ]
[ERROR] !!! Exception during processing !!! mat1 and mat2 shapes cannot be multiplied (512x4096 and 12288x4096)
[ERROR] Traceback (most recent call last):
File "D:\ComfyUI\execution.py", line 542, 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 "D:\ComfyUI\execution.py", line 341, 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 "D:\ComfyUI\execution.py", line 315, in async_map_node_over_list
await process_inputs(input_dict, i)
File "D:\ComfyUI\execution.py", line 303, in process_inputs
result = f(**inputs)
File "D:\ComfyUI\comfy_api\internal_init
.py", line 149, in wrapped_func
return method(locked_class, **inputs)
File "D:\ComfyUI\comfy_api\latest_io.py", line 1900, in EXECUTE_NORMALIZED
to_return = cls.execute(*args, **kwargs)
File "D:\ComfyUI\comfy_extras\nodes_custom_sampler.py", line 1048, in execute
samples = guider.sample(noise.generate_noise(latent), latent_image, sampler, sigmas, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise.seed)
File "D:\ComfyUI\comfy\samplers.py", line 1316, in sample
output = executor.execute(noise, latent_image, sampler, sigmas, denoise_mask, callback, disable_pbar, seed, latent_shapes=latent_shapes)
File "D:\ComfyUI\comfy\patcher_extension.py", line 113, in execute
return self.original(*args, **kwargs)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\samplers.py", line 1254, in outer_sample
output = self.inner_sample(noise, latent_image, device, sampler, sigmas, denoise_mask, callback, disable_pbar, seed, latent_shapes=latent_shapes)
File "D:\ComfyUI\comfy\samplers.py", line 1229, in inner_sample
samples = executor.execute(self, sigmas, extra_args, callback, noise, latent_image, denoise_mask, disable_pbar)
File "D:\ComfyUI\comfy\patcher_extension.py", line 113, in execute
return self.original(*args, **kwargs)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\samplers.py", line 999, in sample
samples = self.sampler_function(model_k, noise, sigmas, extra_args=extra_args, callback=k_callback, disable=disable_pbar, **self.extra_options)
File "D:\ComfyUI\venv\Lib\site-packages\torch\utils_contextlib.py", line 124, in decorate_context
return func(*args, **kwargs)
File "D:\ComfyUI\comfy\k_diffusion\sampling.py", line 205, in sample_euler
denoised = model(x, sigma_hat * s_in, **extra_args)
File "D:\ComfyUI\comfy\samplers.py", line 639, in call
out = self.inner_model(x, sigma, model_options=model_options, seed=seed)
File "D:\ComfyUI\comfy\samplers.py", line 1202, in call
return self.outer_predict_noise(*args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\samplers.py", line 1209, in outer_predict_noise
).execute(x, timestep, model_options, seed)
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\patcher_extension.py", line 113, in execute
return self.original(*args, **kwargs)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\samplers.py", line 1212, in predict_noise
return sampling_function(self.inner_model, x, timestep, self.conds.get("negative", None), self.conds.get("positive", None), self.cfg, model_options=model_options, seed=seed)
File "D:\ComfyUI\comfy\samplers.py", line 619, in sampling_function
out = calc_cond_batch(model, conds, x, timestep, model_options)
File "D:\ComfyUI\comfy\samplers.py", line 210, in calc_cond_batch
return _calc_cond_batch_outer(model, conds, x_in, timestep, model_options)
File "D:\ComfyUI\comfy\samplers.py", line 218, in _calc_cond_batch_outer
return executor.execute(model, conds, x_in, timestep, model_options)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\patcher_extension.py", line 113, in execute
return self.original(*args, **kwargs)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\samplers.py", line 334, in calc_cond_batch
output = model.apply_model(input_x, timestep
, **c).chunk(batch_chunks)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\model_base.py", line 188, in apply_model
return comfy.patcher_extension.WrapperExecutor.new_class_executor(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...<2 lines>...
comfy.patcher_extension.get_all_wrappers(comfy.patcher_extension.WrappersMP.APPLY_MODEL, transformer_options)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
).execute(x, t, c_concat, c_crossattn, control, transformer_options, **kwargs)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\patcher_extension.py", line 113, in execute
return self.original(*args, **kwargs)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\model_base.py", line 232, in _apply_model
model_output = self.diffusion_model(xc, t, context=context, control=control, transformer_options=transformer_options, **extra_conds)
File "D:\ComfyUI\venv\Lib\site-packages\torch\nn\modules\module.py", line 1776, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\venv\Lib\site-packages\torch\nn\modules\module.py", line 1787, in _call_impl
return forward_call(*args, **kwargs)
File "D:\ComfyUI\comfy\ldm\flux\model.py", line 345, in forward
return comfy.patcher_extension.WrapperExecutor.new_class_executor(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...<2 lines>...
comfy.patcher_extension.get_all_wrappers(comfy.patcher_extension.WrappersMP.DIFFUSION_MODEL, transformer_options)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
).execute(x, timestep, context, y, guidance, ref_latents, control, transformer_options, **kwargs)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\patcher_extension.py", line 113, in execute
return self.original(*args, **kwargs)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\comfy\ldm\flux\model.py", line 406, in _forward
out = self.forward_orig(img, img_ids, context, txt_ids, timestep, y, guidance, control, timestep_zero_index=timestep_zero_index, transformer_options=transformer_options, attn_mask=kwargs.get("attention_mask", None))
File "D:\ComfyUI\comfy\ldm\flux\model.py", line 182, in forward_orig
txt = self.txt_in(txt)
File "D:\ComfyUI\venv\Lib\site-packages\torch\nn\modules\module.py", line 1776, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\venv\Lib\site-packages\torch\nn\modules\module.py", line 1787, in _call_impl
return forward_call(*args, **kwargs)
File "D:\ComfyUI\comfy\ops.py", line 1244, in forward
output = self.forward_comfy_cast_weights(input, compute_dtype, want_requant=isinstance(input, QuantizedTensor))
File "D:\ComfyUI\comfy\ops.py", line 1188, in forward_comfy_cast_weights
x = self._forward(input, weight, bias)
File "D:\ComfyUI\comfy\ops.py", line 1184, in _forward
return torch.nn.functional.linear(input, weight, bias)
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\venv\Lib\site-packages\comfy_kitchen\tensor\base.py", line 348, in torch_dispatch
return op_handlers[parent_cls](qt, args, kwargs)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "D:\ComfyUI\venv\Lib\site-packages\comfy_kitchen\tensor\fp8.py", line 147, in _handle_fp8_linear
return torch.nn.functional.linear(*dequantize_args((input_tensor, weight, bias)))
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: mat1 and mat2 shapes cannot be multiplied (512x4096 and 12288x4096)

For KV I can use 4 images without OOM on VRAM 8 GB (64GB system) - still possible with that vl-less CLIP model - that's good! Also good: I can now use qwen3-vl-8b_fp8_scaled for generating text, captioning an image.

The crash probably happens here:

txt = self.txt_in(txt)

in: comfy/ldm/flux/model.py

and

linear(input, weight)

in: comfy/ops.py

@kijai

kijai commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

The default template workflow for Flux2klein-KV doesn't work any longer - since I've updated today

Which template are you referring to exactly?

@GlamoramaAttack

Copy link
Copy Markdown

This one:

Screenshot 2026-06-17e

@kijai

kijai commented Jun 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

Okay I had old template pack and didn't see it, thanks.

Black Forest Labs uses qwen3vl

Where are you seeing this though? I only see them using it for the prompt enhancer, not as text encoder.

I can restore the ability to use the VL version as text encoder, I just don't think that's the intended way to use either klein model?

#14526

@GlamoramaAttack

Copy link
Copy Markdown

Black Forest Labs uses qwen3vl

Where are you seeing this though?

I can't find it or I'm wrong about Black Forest Lab's use of the vl-Qwen3, sorry! But it actually worked for me and I've tried the vl-less clip months ago, when I first tested Flux2-klein-KV. But the results were bad (burned, spotty), so I replaced it with the vl-one and probably thought it was meant to be like this with the vl-Qwen 3 clip. I didn't use it for the older Flux2-klein 9b model.

@kijai

kijai commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Black Forest Labs uses qwen3vl

Where are you seeing this though?

I can't find it or I'm wrong about Black Forest Lab's use of the vl-Qwen3, sorry! But it actually worked for me and I've tried the vl-less clip months ago, when I first tested Flux2-klein-KV. But the results were bad (burned, spotty), so I replaced it with the vl-one and probably thought it was meant to be like this with the vl-Qwen 3 clip. I didn't use it for the older Flux2-klein 9b model.

I can confirm it does work in the sense that it does generate results, didn't test further than that though, so I made a PR to allow it work like it used to, while still also allowing you to use it for text generation, so there's an use case where you can have workflow that does prompt enhancement or such with same model load.

@GlamoramaAttack

Copy link
Copy Markdown

Great, thanks!!!

@gonzalu

gonzalu commented Jun 24, 2026

Copy link
Copy Markdown

@kijai do you think I can replace my current use of Ollama node to leverage Image to Text generation with your new node pack?

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants