-
Notifications
You must be signed in to change notification settings - Fork 13
Add Qwen3.5 support #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |
| from .quant_ops import make_quantized | ||
|
|
||
| IMG_ARCH_LIST = {"flux", "sd1", "sdxl", "sd3", "aura", "hidream", "cosmos", "ltxv", "ltxv_upscaler", "hyvid", "wan", "lumina2", "qwen_image", "ideogram", "krea2", "minimax_h3", "minimax_h3_vae", "minimax_music3"} | ||
| TXT_ARCH_LIST = {"t5", "t5encoder", "llama", "qwen2vl", "qwen3", "qwen3vl", "gemma3", "gemma4", "minimax_music3"} | ||
| TXT_ARCH_LIST = {"t5", "t5encoder", "llama", "qwen2vl", "qwen3", "qwen3vl", "qwen35", "gemma3", "gemma4", "minimax_music3"} | ||
| VIS_TYPE_LIST = {"clip-vision", "mmproj"} | ||
|
|
||
| def device_supports_bf16(): | ||
|
|
@@ -402,6 +402,40 @@ def gguf_sd_loader(path, handle_prefix="model.diffusion_model.", is_text_model=F | |
| **GEMMA3_SD_MAP, | ||
| } | ||
|
|
||
| # Qwen3.5 (llama.cpp ``qwen35`` arch). ComfyUI's detect_te_model() identifies | ||
| # Qwen3.5 by the unprefixed ``model.language_model.*`` layout before applying | ||
| # its own prefix rename, so the LM must map with that prefix. Hybrid layers | ||
| # carry linear attention (``attn_qkv``/``attn_gate``/``ssm_*`` tensors); the | ||
| # second layernorm is exported as ``post_attention_norm`` (no ``ffn_norm``). | ||
| # Entry order matters because sd_map_replace() does substring replacement: | ||
| # the fused/SSM variants must precede the generic ``attn_q``/``ssm_a`` keys. | ||
| QWEN35_SD_MAP = { | ||
| "blk.": "model.language_model.layers.", | ||
| "attn_norm": "input_layernorm", | ||
| "attn_q_norm.": "self_attn.q_norm.", | ||
| "attn_k_norm.": "self_attn.k_norm.", | ||
| "attn_qkv": "linear_attn.in_proj_qkv", | ||
| "attn_gate": "linear_attn.in_proj_z", | ||
| "attn_q": "self_attn.q_proj", | ||
| "attn_k": "self_attn.k_proj", | ||
| "attn_v": "self_attn.v_proj", | ||
| "attn_output": "self_attn.o_proj", | ||
| "ssm_alpha": "linear_attn.in_proj_a", | ||
| "ssm_beta": "linear_attn.in_proj_b", | ||
| "ssm_conv1d": "linear_attn.conv1d", | ||
| "ssm_dt.bias": "linear_attn.dt_bias", | ||
| "ssm_norm": "linear_attn.norm", | ||
| "ssm_out": "linear_attn.out_proj", | ||
| "ssm_a": "linear_attn.A_log", | ||
| "post_attention_norm": "post_attention_layernorm", | ||
| "ffn_up": "mlp.up_proj", | ||
| "ffn_down": "mlp.down_proj", | ||
| "ffn_gate": "mlp.gate_proj", | ||
| "token_embd": "model.language_model.embed_tokens", | ||
| "output_norm": "model.language_model.norm", | ||
| "output.weight": "lm_head.weight", | ||
| } | ||
|
|
||
| CLIP_VISION_SD_MAP = { | ||
| "mm.": "visual.merger.mlp.", | ||
| "v.post_ln.": "visual.merger.ln_q.", | ||
|
|
@@ -482,6 +516,137 @@ def gemma3_norm_corrections(sd): | |
| #logging.info(f"Gemma3: Applied -1 norm correction to {corrected} tensors") | ||
| return sd | ||
|
|
||
| def _qwen35_v_reorder(value, num_v_heads, num_k_heads, head_dim, qk_rows=0, axis=0): | ||
| """Reverse llama.cpp's tiled V-head order for linear attention. | ||
|
|
||
| llama.cpp (``_LinearAttentionVReorderBase``) stores V heads in tiled | ||
| order ``[K0_v0, K1_v0, ..., K0_v1, K1_v1, ...]`` so its kernels can | ||
| broadcast K with ``ggml_repeat``. ComfyUI expects the grouped-by-K-head | ||
| order ``[K0_v0..v{r-1}, K1_v0..v{r-1}, ...]`` that the HF checkpoints | ||
| use. Quantized tensors are dequantized first because the head blocks | ||
| do not align with every GGML quant block size. ``qk_rows`` skips the | ||
| untouched Q/K rows of fused tensors; ``axis=1`` reorders columns. | ||
| """ | ||
| if num_k_heads <= 0 or num_v_heads <= num_k_heads: | ||
| return value | ||
| if is_quantized(value): | ||
| dtype = torch.bfloat16 if device_supports_bf16() else torch.float16 | ||
| value = dequantize_tensor(value, dtype=dtype) | ||
| r = num_v_heads // num_k_heads | ||
| # grouped head g = (k_idx, v_idx); its tiled position is v_idx * k + k_idx | ||
| src = [(g % r) * num_k_heads + (g // r) for g in range(num_v_heads)] | ||
| idx = torch.tensor(src, dtype=torch.long) | ||
| if value.ndim == 1: | ||
| return value.index_select(0, idx) | ||
| if qk_rows > 0: | ||
| # fused qkv / conv1d: only the V block is reordered | ||
| head_rows = value.shape[0] - qk_rows | ||
| v = value[qk_rows:].view(num_v_heads, -1).index_select(0, idx) | ||
| v = v.reshape(head_rows, value.shape[1]) | ||
| return torch.cat([value[:qk_rows], v], dim=0) | ||
| if axis == 1: | ||
| return ( | ||
| value.view(-1, num_v_heads, head_dim) | ||
| .index_select(1, idx) | ||
| .reshape(value.shape) | ||
| ) | ||
| return value.view(num_v_heads, -1).index_select(0, idx).reshape(value.shape) | ||
|
|
||
| def qwen35_corrections(sd): | ||
| # Reverse llama.cpp's Qwen3.5 conversion tweaks (see Qwen3NextModel and | ||
| # _LinearAttentionVReorderBase modify_tensors in llama.cpp): it stores | ||
| # RMS norm weights as (w + 1), stores ``ssm_a`` as ``-exp(A_log)``, | ||
| # squeezes the depthwise conv1d kernels to 2D, and stores linear-attention | ||
| # V heads in tiled order. ComfyUI's Qwen35 TE expects the raw weights and | ||
| # applies ``w + 1`` and ``-exp(A_log)`` itself. | ||
| norm_patterns = [ | ||
| "input_layernorm.weight", | ||
| "post_attention_layernorm.weight", | ||
| "self_attn.q_norm.weight", | ||
| "self_attn.k_norm.weight", | ||
| "model.language_model.norm.weight", | ||
| ] | ||
| corrected = 0 | ||
| for key in list(sd.keys()): | ||
| if any(p in key for p in norm_patterns): | ||
| if is_quantized(sd[key]): | ||
| sd[key] = dequantize_tensor(sd[key], dtype=torch.float32) - 1.0 | ||
| else: | ||
| sd[key] = sd[key].float() - 1.0 | ||
| corrected += 1 | ||
|
|
||
| # derive the linear-attention head layout from the checkpoint shapes | ||
| qkv_key = next((k for k in sd if k.endswith(".linear_attn.in_proj_qkv.weight")), None) | ||
| z_key = next((k for k in sd if k.endswith(".linear_attn.in_proj_z.weight")), None) | ||
| alog_key = next((k for k in sd if k.endswith(".linear_attn.A_log")), None) | ||
| num_k_heads = num_v_heads = head_dim = 0 | ||
| if qkv_key and z_key and alog_key: | ||
| value_dim = sd[z_key].shape[0] | ||
| conv_dim = sd[qkv_key].shape[0] | ||
| key_dim = (conv_dim - value_dim) // 2 | ||
| num_v_heads = sd[alog_key].shape[0] | ||
| head_dim = value_dim // num_v_heads | ||
| num_k_heads = key_dim // head_dim | ||
|
|
||
| for key in list(sd.keys()): | ||
| if key.endswith(".linear_attn.A_log"): | ||
| # llama.cpp stores ``ssm_a = -exp(A_log)`` (always negative); | ||
| # ComfyUI computes ``-A_log.exp()``, so invert back to A_log. | ||
| value = sd[key] | ||
| if is_quantized(value): | ||
| value = dequantize_tensor(value, dtype=torch.float32) | ||
| else: | ||
| value = value.float() | ||
| sd[key] = torch.log(-value) | ||
| corrected += 1 | ||
| elif key.endswith(".linear_attn.dt_bias"): | ||
| corrected += 1 | ||
| elif key.endswith(".linear_attn.conv1d.weight"): | ||
| corrected += 1 | ||
| elif key.endswith(".linear_attn.in_proj_qkv.weight"): | ||
| corrected += 1 | ||
| elif key.endswith(( | ||
| ".linear_attn.in_proj_z.weight", | ||
| ".linear_attn.in_proj_a.weight", | ||
| ".linear_attn.in_proj_b.weight", | ||
| )): | ||
| corrected += 1 | ||
| elif key.endswith(".linear_attn.out_proj.weight"): | ||
| corrected += 1 | ||
| else: | ||
| continue | ||
|
|
||
| # reverse the tiled V-head order (identity when heads are balanced) | ||
| if key.endswith((".linear_attn.A_log", ".linear_attn.dt_bias")): | ||
| sd[key] = _qwen35_v_reorder(sd[key], num_v_heads, num_k_heads, 1) | ||
| elif key.endswith(".linear_attn.conv1d.weight"): | ||
| value = sd[key] | ||
| if value.ndim == 2: | ||
| conv_dim = value.shape[0] | ||
| qk_channels = conv_dim - num_v_heads * head_dim | ||
| # llama.cpp squeezes the depthwise conv1d kernel to 2D | ||
| # (out_channels, kernel_size); ComfyUI's Conv1d expects | ||
| # (out_channels, 1, kernel_size). | ||
| value = _qwen35_v_reorder( | ||
| value, num_v_heads, num_k_heads, head_dim, qk_rows=qk_channels | ||
| ) | ||
| sd[key] = value.unsqueeze(-2) | ||
|
Comment on lines
+622
to
+633
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Find keys_noquant / keys_hiprec handling for ssm/conv tensors.
rg -n -C4 'keys_noquant|keys_hiprec' --glob '*.py'
rg -n -C2 'ssm_conv1d|conv1d' --glob '*.py'Repository: molbal/ComfyUI-GGUF Length of output: 157 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(loader|.*gguf.*|.*quant.*)\.py$' | head -80
printf '%s\n' '--- loader.py structure and target references ---'
fd -t f -a 'loader.py' .
for f in $(fd -t f -a 'loader.py' .); do
echo "FILE: $f"
wc -l "$f"
rg -n -C8 '_qwen35_v_reorder|linear_attn\.conv1d\.weight|is_quantized|dequantize_tensor|GGMLTensor|keys_noquant|keys_hiprec|ssm_conv1d|conv1d' "$f" || true
done
printf '%s\n' '--- repository-wide relevant definitions and call sites ---'
rg -n -C5 'def _qwen35_v_reorder|def is_quantized|def dequantize_tensor|class GGMLTensor|keys_noquant|keys_hiprec|ssm_conv1d|linear_attn\.conv1d' --glob '*.py' . || trueRepository: molbal/ComfyUI-GGUF Length of output: 40104 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- Qwen3.5 converter definitions and selection ---'
rg -n -C12 'Qwen3\.5|Qwen35|qwen35|class Model|model_arch|keys_noquant|keys_hiprec' tools tests --glob '*.py' | head -500
printf '%s\n' '--- converter quantization decision paths ---'
sed -n '1,220p' tools/convert.py
sed -n '360,530p' tools/convert.py
sed -n '880,975p' tools/convert.py
printf '%s\n' '--- GGMLTensor and dequantization behavior ---'
sed -n '1,180p' ops.py
sed -n '1,140p' dequant.py
printf '%s\n' '--- relevant tests ---'
sed -n '1120,1210p' tests/test_targeted_quantization.py
rg -n -C8 'qwen35|conv1d|ssm' tests --glob '*.py'Repository: molbal/ComfyUI-GGUF Length of output: 50375 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- GGUF loader tensor construction ---'
sed -n '150,240p' loader.py
printf '%s\n' '--- dequantize_tensor implementation ---'
sed -n '1,115p' dequant.py
printf '%s\n' '--- complete GGMLTensor implementation ---'
sed -n '57,155p' ops.py
printf '%s\n' '--- Qwen correction implementation ---'
sed -n '519,648p' loader.py
printf '%s\n' '--- targeted Qwen tests only ---'
rg -n -C5 'qwen35|Qwen35|conv1d|reorder' tests/test_targeted_quantization.py
printf '%s\n' '--- direct synthetic behavior probe ---'
python3 - <<'PY'
from pathlib import Path
for name in ("loader.py", "dequant.py", "ops.py"):
text = Path(name).read_text()
print(f"{name}: {len(text.splitlines())} lines")
print("ModelQwen35 in tools/convert.py:", "ModelQwen35" in Path("tools/convert.py").read_text())
print("qwen35 in tools/convert.py:", "qwen35" in Path("tools/convert.py").read_text().lower())
PYRepository: molbal/ComfyUI-GGUF Length of output: 30069 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- installed GGUF reader availability and tensor data-shape convention ---'
python3 - <<'PY'
try:
import gguf
import inspect
from gguf import gguf_reader
print("gguf:", getattr(gguf, "__file__", None))
print("gguf_reader:", getattr(gguf_reader, "__file__", None))
source = inspect.getsource(gguf_reader.ReaderTensor)
for i, line in enumerate(source.splitlines(), 1):
if "data" in line or "shape" in line:
print(f"{i}: {line}")
except Exception as exc:
print(type(exc).__name__ + ":", exc)
PY
printf '%s\n' '--- standalone GGMLTensor metadata probe ---'
python3 - <<'PY'
try:
import torch
except Exception as exc:
print("torch unavailable:", type(exc).__name__, exc)
raise SystemExit(0)
class SyntheticGGMLTensor(torch.Tensor):
def __new__(cls, raw, tensor_shape):
obj = torch.Tensor._make_subclass(cls, raw, require_grad=False)
obj.tensor_shape = torch.Size(tensor_shape)
return obj
`@property`
def shape(self):
return self.tensor_shape
raw = SyntheticGGMLTensor(torch.zeros((2, 64), dtype=torch.uint8), (8192, 4))
print("raw ndim:", raw.ndim)
print("raw size:", tuple(raw.size()))
print("logical shape:", tuple(raw.shape))
expanded = raw.unsqueeze(-2)
print("expanded ndim:", expanded.ndim)
print("expanded size:", tuple(expanded.size()))
print("expanded logical shape:", tuple(expanded.shape))
PY
printf '%s\n' '--- exact converter registration summary ---'
sed -n '303,355p' tools/convert.pyRepository: molbal/ComfyUI-GGUF Length of output: 2299 Dequantize quantized Qwen3.5 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| elif key.endswith(".linear_attn.in_proj_qkv.weight"): | ||
| value = sd[key] | ||
| qk_rows = value.shape[0] - num_v_heads * head_dim | ||
| sd[key] = _qwen35_v_reorder( | ||
| value, num_v_heads, num_k_heads, head_dim, qk_rows=qk_rows | ||
| ) | ||
| elif key.endswith(".linear_attn.out_proj.weight"): | ||
| sd[key] = _qwen35_v_reorder( | ||
| sd[key], num_v_heads, num_k_heads, head_dim, axis=1 | ||
| ) | ||
| else: | ||
| sd[key] = _qwen35_v_reorder(sd[key], num_v_heads, num_k_heads, 1) | ||
|
|
||
| logging.info(f"qwen35 GGUF: corrected {corrected} norm/A_log/conv1d/V-head tensors") | ||
| return sd | ||
|
|
||
| def strip_quant_suffix(name): | ||
| pattern = r"[-_]?(?:ud-)?i?q[0-9]_[a-z0-9_\-]{1,8}$" | ||
| match = re.search(pattern, name, re.IGNORECASE) | ||
|
|
@@ -526,7 +691,10 @@ def gguf_mmproj_loader(path): | |
| w2 = dequantize_tensor(vsd.pop("v.patch_embd.weight.1"), dtype=torch.float32) | ||
| vsd["v.patch_embd.weight"] = torch.stack([w1, w2], dim=2) | ||
|
|
||
| if any("deepstack" in key or "deepstast" in key for key in vsd): | ||
| if any("deepstack" in key or "deepstast" in key or "attn_qkv" in key for key in vsd): | ||
| # Qwen3-VL / Qwen3.5 style vision towers use fused ``v.blk.N.attn_qkv`` | ||
| # projections and a ``linear_fc`` merger. Qwen3.5 mmprojs omit the | ||
| # deepstack tensors, so detect them by the fused projection instead. | ||
| return sd_map_replace(vsd, CLIP_VISION_QWEN3_MAP) | ||
|
|
||
| # run main replacement | ||
|
|
@@ -799,7 +967,7 @@ def gguf_clip_loader(path, dynamic=False, progress_callback=None): | |
| logging.warning(f"Dequantizing {temb_key} to prevent runtime OOM.") | ||
| sd[temb_key] = dequantize_tensor(sd[temb_key], dtype=torch.float16) | ||
| sd = sd_map_replace(sd, T5_SD_MAP) | ||
| elif arch in {"llama", "qwen2vl", "qwen3", "qwen3vl", "gemma3", "gemma4"}: | ||
| elif arch in {"llama", "qwen2vl", "qwen3", "qwen3vl", "qwen35", "gemma3", "gemma4"}: | ||
| # TODO: pass model_options["vocab_size"] to loader somehow | ||
| temb_key = "token_embd.weight" | ||
| if temb_key in sd and sd[temb_key].shape[0] >= (64 * 1024): | ||
|
|
@@ -820,6 +988,11 @@ def gguf_clip_loader(path, dynamic=False, progress_callback=None): | |
| # ComfyUI calculates Gemma 4 RoPE frequencies itself. | ||
| sd.pop("rope_freqs.weight", None) | ||
| sd = sd_map_replace(sd, GEMMA4_SD_MAP) | ||
| elif arch == "qwen35": | ||
| sd = sd_map_replace(sd, QWEN35_SD_MAP) | ||
| sd = qwen35_corrections(sd) | ||
| vsd = gguf_mmproj_loader(path) | ||
| sd.update(vsd) | ||
| else: | ||
| sd = sd_map_replace(sd, LLAMA_SD_MAP) | ||
| if arch == "llama": | ||
|
|
@@ -851,6 +1024,17 @@ def gguf_clip_loader(path, dynamic=False, progress_callback=None): | |
| # parameters so that load_state_dict(strict=False) doesn't raise a size | ||
| # mismatch error while still satisfying detect_te_model()'s key checks. | ||
| inject_qwen3vl_detection_markers(sd) | ||
| if "lm_head.weight" in sd and is_quantized(sd["lm_head.weight"]): | ||
| lm_head = sd["lm_head.weight"] | ||
| if getattr(lm_head, "tensor_type", None) != gguf.GGMLQuantizationType.BF16: | ||
| # BaseGenerate.logits() feeds lm_head straight to F.linear, | ||
| # bypassing the GGML ops path that dequantizes on the fly. | ||
| # Block-quantized GGUF data has a byte-expanded shape (e.g. | ||
| # Q8_0 stores scales interleaved), so raw data must never | ||
| # reach the matmul. BF16 storage is already full precision | ||
| # and keeps its logical shape, so it can stay quantized. | ||
| logging.warning(f"Dequantizing lm_head.weight to prevent raw-block matmul.") | ||
| sd["lm_head.weight"] = dequantize_tensor(lm_head, dtype=torch.float16) | ||
| elif arch == "ideogram": | ||
| # Dequantize Ideogram model for inference | ||
| logging.info("Dequantizing Ideogram model for inference...") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: molbal/ComfyUI-GGUF
Length of output: 161
🏁 Script executed:
Repository: molbal/ComfyUI-GGUF
Length of output: 29646
🌐 Web query:
Hugging Face unsloth/Qwen3.5-4B-GGUF repository model variants Qwen3.5 GGUF💡 Result:
The unsloth/Qwen3.5-4B-GGUF repository on Hugging Face provides GGUF-formatted quantization variants of the Qwen3.5-4B model [1][2]. This repository is part of a series of quantized models provided by Unsloth, utilizing efficient quantization techniques to make the Qwen3.5 model architecture—a causal language model with a vision encoder [1]—more accessible for various hardware configurations. Model Variants The repository includes a variety of quantization levels to suit different memory and performance requirements. Examples of these GGUF files include, but are not limited to: - Standard quantizations such as Q4_K_M, Q5_K_M, and Q8_0 [2]. - Specialized variants like IQ4_NL, IQ2_M, and UD-Q4_K_XL [2][3]. - Associated files such as mmproj (multimodal projector) weights (e.g., mmproj-BF16.gguf) [2]. Background and Context - Model Architecture: Qwen3.5-4B is a post-trained causal language model featuring a vision encoder, 32 layers, and support for a native context length of 262,144 tokens [1]. - Release Information: The Qwen3.5 series was announced by Alibaba in February 2026, with the 4B, 2B, 0.8B, and 9B variants becoming available on the Hugging Face Hub by early March 2026 [4][5]. - Compatibility: The GGUF artifacts are designed for use with popular inference frameworks that support the GGUF format, such as llama.cpp, which are frequently used for running quantized models locally [6][7][1]. Users should check the Files and versions tab of the repository directly to see the full list of currently available quantization files, as the specific list of variants may be updated over time [2].
Citations:
🌐 Web query:
ComfyUI TEModel QWEN35 introduced commit Qwen3.5 text encoder support💡 Result:
Support for Qwen3.5 text encoder models was introduced in ComfyUI through pull request
#12771, which was merged via commit 404d7b9 on March 26, 2026 [1][2]. This implementation added a new dedicated module,comfy/text_encoders/qwen35.py, along with associated tokenizer configuration files and support for multiple Qwen3.5 model variants [2][3][4]. The update included several technical additions: - NewTEModelenum variants for Qwen3.5 (e.g.,TEModel.QWEN35_2B,TEModel.QWEN35_4B,TEModel.QWEN35_9B, andTEModel.QWEN35_27B) to identify and route these weights during model loading [1][4]. - Updates tocomfy/sd.pyto enable detection and loading of Qwen3.5 state dicts [1][2]. - Native support for multimodal tasks (such as image captioning and reverse prompt engineering) by integrating with ComfyUI's existingTextGeneratenode logic [5][6]. - A mechanism for handling vision components within the Qwen3.5 architecture, allowing the model to process both text and image inputs [3][5]. Following the initial release, subsequent refinements have been made, including fixes for image handling in multi-image scenarios [7] and extensions to support additional model sizes like 31B [8]. Qwen3.5 models are typically used in ComfyUI by placing the.safetensorsfiles in themodels/text_encoders/directory and loading them via standard nodes likeCLIPLoaderor specialized text generation nodes [5][6].Citations:
🏁 Script executed:
Repository: molbal/ComfyUI-GGUF
Length of output: 1048
🏁 Script executed:
Repository: molbal/ComfyUI-GGUF
Length of output: 47844
Document the Qwen3.5 repositories and minimum ComfyUI revision.
Link each listed model size to its matching repository. Require ComfyUI commit
404d7b9978f9bd6a920e7a586cae40ffaee77a7dor newer, which adds allTEModel.QWEN35_*variants.🤖 Prompt for AI Agents