diff --git a/AILab_QwenVL.py b/AILab_QwenVL.py index f5fb897..e57a686 100644 --- a/AILab_QwenVL.py +++ b/AILab_QwenVL.py @@ -64,7 +64,10 @@ try: sageattn_qk_int8_pv_fp8_cuda_sm90, ) SAGE_ATTENTION_AVAILABLE = True -except ImportError: +except Exception: + sageattn_qk_int8_pv_fp16_cuda = None + sageattn_qk_int8_pv_fp8_cuda = None + sageattn_qk_int8_pv_fp8_cuda_sm90 = None SAGE_ATTENTION_AVAILABLE = False # Global cache for generated prompts diff --git a/__init__.py b/__init__.py index fedef2b..f4404e3 100644 --- a/__init__.py +++ b/__init__.py @@ -3,7 +3,7 @@ "name": "ComfyUI-ThinkingLLM", "description": "A multimodal ComfyUI AI node with Qwen3.5, Qwen3-VL, Qwen2.5-VL, Qwen3, and Gemma 4 integrations. Features live thinking in the terminal to see what the LLM is doing in real time.", "author": "goodguy1963", - "version": "2.3.3", + "version": "2.3.4", "url": "https://github.com/goodguy1963/ComfyUI-ThinkingLLM", "category": "image" } diff --git a/custom_nodes.json b/custom_nodes.json index 117088b..ef5d749 100644 --- a/custom_nodes.json +++ b/custom_nodes.json @@ -9,5 +9,5 @@ "description": "A multimodal ComfyUI AI node with Qwen3.5, Qwen3-VL, Qwen2.5-VL, Qwen3, and Gemma 4 integrations. Features live thinking in the terminal to see what the LLM is doing in real time.", "category": "image", "tags": ["vision", "language", "multimodal", "thinkingllm", "qwen", "gguf", "flash_attention", "performance", "wan2.2", "video", "i2v", "t2v", "storyboard", "caching", "fork", "gpl-3.0"], - "version": "2.3.3" + "version": "2.3.4" } diff --git a/gguf_models.json b/gguf_models.json index 28c4190..e29c68a 100644 --- a/gguf_models.json +++ b/gguf_models.json @@ -189,6 +189,27 @@ "context_length": 131072 } }, + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-GGUF [Q2:11GB|Q3:15GB|Q4:20GB|Q5:28GB|Q6:31GB|Q8:44GB|VRAM:~24GB+]": { + "author": "HauhauCS", + "repo_name": "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive", + "repo_id": "HauhauCS/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive", + "model_files": [ + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q8_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q6_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q5_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ4_NL.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ4_XS.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q3_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ3_M.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q2_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ2_M.gguf" + ], + "defaults": { + "context_length": 131072 + } + }, "Gemma-4-E2B-it-GGUF [Q4:2GB|Q8:3.5GB|VRAM:~3GB]": { "author": "ggml-org", "repo_name": "gemma-4-E2B-it-GGUF", @@ -333,6 +354,33 @@ "pool_size": 4194304 } }, + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive [Q2:11GB|Q3:15GB|Q4:20GB|Q5:28GB|Q6:31GB|Q8:44GB|VRAM:~24GB+]": { + "author": "HauhauCS", + "repo_name": "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive", + "repo_id": "HauhauCS/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive", + "mmproj_file": "mmproj-Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-f16.gguf", + "model_files": [ + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q8_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q6_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q5_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ4_NL.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ4_XS.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q3_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ3_M.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q2_K_P.gguf", + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-IQ2_M.gguf" + ], + "defaults": { + "context_length": 131072, + "image_max_tokens": 4096, + "n_batch": 512, + "gpu_layers": -1, + "top_k": 20, + "pool_size": 4194304 + } + }, "Qwen3.5-4B [Q4:2.4GB|Q6:3.2GB|Q8:4.2GB|BF16:8GB|VRAM:~4GB]": { "author": "unsloth", "repo_name": "Qwen3.5-4B-GGUF", diff --git a/pyproject.toml b/pyproject.toml index 14dc0e5..4afe7bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ComfyUI-ThinkingLLM" description = "A multimodal ComfyUI AI node with Qwen3.5, Qwen3-VL, Qwen2.5-VL, Qwen3, and Gemma 4 integrations. Features live thinking in the terminal to see what the LLM is doing in real time." -version = "2.3.3" +version = "2.3.4" license = {file = "LICENSE"} dependencies = ["transformers>=5.2.0", "torch", "huggingface-hub", "hf_xet", "psutil", "numpy", "Pillow", "opencv-python", "bitsandbytes", "accelerate", "sentencepiece", "tiktoken", "llama-cpp-python"] diff --git a/tests/test_new_features.py b/tests/test_new_features.py index 2cffc44..c7fefd2 100644 --- a/tests/test_new_features.py +++ b/tests/test_new_features.py @@ -272,6 +272,38 @@ def iter_workflow_types(path: Path) -> list[str]: ] +def load_model_recommendations() -> dict: + return json.loads((PKG / "web" / "model_recommendations.json").read_text(encoding="utf-8")) + + +def matches_recommendation_rule(model_name: str, rules: list[dict]) -> bool: + lowered = model_name.lower() + ordered = sorted(rules, key=lambda rule: rule.get("priority", 0), reverse=True) + for rule in ordered: + include = [token.lower() for token in rule.get("match_any", [])] + exclude = [token.lower() for token in rule.get("exclude_any", [])] + if any(token in lowered for token in include) and not any(token in lowered for token in exclude): + return True + return False + + +def iter_catalog_display_names() -> list[str]: + names: list[str] = [] + hf_payload = json.loads((PKG / "hf_models.json").read_text(encoding="utf-8")) + for section in ("hf_vl_models", "hf_text_models"): + names.extend(list((hf_payload.get(section) or {}).keys())) + + gguf_payload = json.loads((PKG / "gguf_models.json").read_text(encoding="utf-8")) + for section in ("Qwen_model", "qwenVL_model"): + entries = gguf_payload.get(section) or {} + for repo_key, repo in entries.items(): + if not isinstance(repo, dict): + continue + for model_file in repo.get("model_files") or []: + names.append(str(Path(model_file).name)) + return names + + class TestTerminalDisplayHelper(unittest.TestCase): def test_helper_file_exists(self): self.assertTrue((PKG / "AILab_StreamDisplay.py").exists()) @@ -318,8 +350,44 @@ class TestBufferedStreamingIntegration(unittest.TestCase): def test_gguf_prompt_enhancer_has_no_off_toggle_text_stream_fallback(self): source = read_source("AILab_QwenVL_GGUF_PromptEnhancer.py") self.assertNotIn("_maybe_emit_prompt_compact_progress", source) + + +class TestModelRecommendations(unittest.TestCase): + def test_recommendation_rules_have_sources(self): + payload = load_model_recommendations() + rules = payload.get("rules") or [] + self.assertTrue(rules, "model_recommendations.json should define at least one rule") + for rule in rules: + with self.subTest(rule=rule.get("id")): + self.assertTrue(rule.get("sources"), "each recommendation rule should record its research source") + + def test_recommendations_cover_all_catalog_models(self): + payload = load_model_recommendations() + rules = payload.get("rules") or [] + uncovered = [name for name in iter_catalog_display_names() if not matches_recommendation_rule(name, rules)] + self.assertEqual([], uncovered) + + def test_qwen36_hauhau_exists_in_text_and_vision_catalogs(self): + payload = json.loads((PKG / "gguf_models.json").read_text(encoding="utf-8")) + text_catalog = payload.get("Qwen_model") or {} + vision_catalog = payload.get("qwenVL_model") or {} + + text_key = next((key for key in text_catalog if "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive" in key), None) + vision_key = next((key for key in vision_catalog if "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive" in key), None) + + self.assertIsNotNone(text_key) + self.assertIsNotNone(vision_key) + self.assertIn("mmproj-Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-f16.gguf", vision_catalog[vision_key]["mmproj_file"]) + self.assertGreaterEqual(len(text_catalog[text_key].get("model_files") or []), 11) + self.assertGreaterEqual(len(vision_catalog[vision_key].get("model_files") or []), 11) + + def test_appearance_js_loads_recommendation_widget(self): + source = read_source("web/js/appearance.js") + self.assertIn("model_recommendations.json", source) + self.assertIn("ComfyWidgets", source) + self.assertIn("recommended_settings", source) self.assertNotIn("Prompt enhancer compact terminal progress is active", source) - self.assertIn("_maybe_emit_prompt_background_heartbeat", source) + self.assertIn("queueRecommendationRefresh", source) def test_gguf_prompt_enhancer_caps_context_and_safe_constructs_llama(self): source = read_source("AILab_QwenVL_GGUF_PromptEnhancer.py") diff --git a/web/js/appearance.js b/web/js/appearance.js index 29d0f21..c1d2246 100644 --- a/web/js/appearance.js +++ b/web/js/appearance.js @@ -1,4 +1,5 @@ import { app } from "/scripts/app.js"; +import { ComfyWidgets } from "/scripts/widgets.js"; const COLOR_THEMES = { QwenVL: { nodeColor: "#28403f", nodeBgColor: "#374539", width: 340 }, @@ -8,15 +9,12 @@ const COLOR_THEMES = { }; const NODE_COLORS = { - // QwenVL nodes "AILab_QwenVL": "QwenVL", "AILab_QwenVL_Advanced": "QwenVL", "AILab_QwenVL_PromptEnhancer": "Enhancer", "AILab_QwenVL_GGUF": "QwenVLGGUF", "AILab_QwenVL_GGUF_Advanced": "QwenVLGGUF", "AILab_QwenVL_GGUF_PromptEnhancer": "Enhancer", - - // Tools "AILab_QwenVL_PromptLibrary": "Tools", }; @@ -46,6 +44,31 @@ const GGUF_ADVANCED_INTERNAL_WIDGETS = new Set([ "ctx_checkpoints", ]); +const RECOMMENDATIONS_URL = new URL("../model_recommendations.json", import.meta.url); +const RECOMMENDATION_WIDGET_NAME = "recommended_settings"; +const RECOMMENDATION_PLACEHOLDER = "Select a model to see provider/community recommended settings. This note never changes your saved widget values."; +const RECOMMENDATION_FIELDS = ["temperature", "top_p", "top_k", "max_tokens", "ctx_min", "n_batch", "image_max_tokens"]; + +let recommendationsPromise = null; + +function loadRecommendations() { + if (!recommendationsPromise) { + recommendationsPromise = fetch(RECOMMENDATIONS_URL) + .then((response) => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + return response.json(); + }) + .then((payload) => Array.isArray(payload?.rules) ? payload.rules : []) + .catch((error) => { + console.warn("[ThinkingLLM] Failed to load model recommendations", error); + return []; + }); + } + return recommendationsPromise; +} + function setNodeColors(node, theme) { if (!theme) { return; } if (theme.nodeColor) { @@ -103,17 +126,224 @@ function clearHfTokenAfterExecution(node) { }; } +function recommendationScope(node) { + if ([ + "AILab_QwenVL_GGUF", + "AILab_QwenVL_GGUF_Advanced", + "ThinkingLLM_QwenVL_GGUF", + "ThinkingLLM_QwenVL_GGUF_Advanced", + "AILab_QwenVL_GGUF_PromptEnhancer", + "ThinkingLLM_QwenVL_GGUF_PromptEnhancer", + ].includes(node.comfyClass)) { + return "gguf"; + } + return "hf"; +} + +function matchRecommendationRule(modelName, rules) { + const lowered = String(modelName || "").toLowerCase(); + if (!lowered) { + return null; + } + const ordered = [...(rules || [])].sort((left, right) => (right.priority || 0) - (left.priority || 0)); + for (const rule of ordered) { + const include = Array.isArray(rule?.match_any) ? rule.match_any : []; + const exclude = Array.isArray(rule?.exclude_any) ? rule.exclude_any : []; + const matches = include.some((token) => lowered.includes(String(token).toLowerCase())); + const blocked = exclude.some((token) => lowered.includes(String(token).toLowerCase())); + if (matches && !blocked) { + return rule; + } + } + return null; +} + +function hasWidget(node, widgetName) { + return Boolean(findWidget(node, widgetName)); +} + +function formatSetting(fieldName, value) { + if (typeof value !== "number") { + return null; + } + if (fieldName === "ctx_min") { + return `ctx>=${value}`; + } + return `${fieldName}=${value}`; +} + +function buildModeSummary(node, settings) { + const parts = []; + for (const fieldName of RECOMMENDATION_FIELDS) { + if (!(fieldName in (settings || {}))) { + continue; + } + if (fieldName === "ctx_min") { + if (!hasWidget(node, "ctx")) { + continue; + } + } else if (fieldName === "top_k") { + if (!hasWidget(node, "top_k")) { + continue; + } + } else if (!hasWidget(node, fieldName)) { + continue; + } + const formatted = formatSetting(fieldName, settings[fieldName]); + if (formatted) { + parts.push(formatted); + } + } + return parts; +} + +function buildRecommendationText(node, rules) { + const modelName = String(findWidget(node, "model_name")?.value || "").trim(); + if (!modelName || modelName.startsWith("(no ")) { + return RECOMMENDATION_PLACEHOLDER; + } + + const rule = matchRecommendationRule(modelName, rules); + if (!rule) { + return [ + "Manual settings guide", + `No curated recommendation is bundled yet for ${modelName}.`, + "Your current widget values stay exactly as you set them.", + ].join("\n"); + } + + const thinkingEnabled = Boolean(findWidget(node, "enable_thinking")?.value); + const activeSettings = thinkingEnabled ? rule.thinking_on : rule.thinking_off; + const alternateSettings = thinkingEnabled ? rule.thinking_off : rule.thinking_on; + const activeSummary = buildModeSummary(node, activeSettings); + const alternateSummary = buildModeSummary(node, alternateSettings); + const scope = recommendationScope(node); + const lines = [ + rule.family_label || "Manual settings guide", + `Current toggle: ${thinkingEnabled ? "thinking ON" : "thinking OFF"}`, + ]; + + const supportNote = rule.support_notes?.[scope]; + if (supportNote) { + lines.push(`Toggle: ${supportNote}`); + } + lines.push(`Thinking ON: ${(thinkingEnabled ? activeSummary : alternateSummary).join(" | ") || "use the provider defaults"}`); + lines.push(`Thinking OFF: ${(thinkingEnabled ? alternateSummary : activeSummary).join(" | ") || "use the provider defaults"}`); + + const softSwitch = rule.support_notes?.soft_switch; + if (softSwitch) { + lines.push(`Note: ${softSwitch}`); + } + if (Array.isArray(rule.notes) && rule.notes.length) { + lines.push(`Extra: ${rule.notes[0]}`); + } + if (Array.isArray(rule.sources) && rule.sources.length) { + lines.push(`Sources: ${rule.sources.join(" + ")}`); + } + return lines.join("\n"); +} + +function ensureRecommendationWidget(node) { + if (!THINKINGLLM_NODE_NAMES.has(node.comfyClass)) { + return null; + } + let widget = findWidget(node, RECOMMENDATION_WIDGET_NAME); + if (widget) { + return widget; + } + widget = ComfyWidgets.STRING(node, RECOMMENDATION_WIDGET_NAME, ["STRING", { multiline: true }], app).widget; + if (widget?.inputEl) { + widget.inputEl.readOnly = true; + widget.inputEl.style.opacity = 0.78; + widget.inputEl.style.fontSize = "12px"; + widget.inputEl.style.lineHeight = "1.35"; + widget.inputEl.rows = 6; + } + widget.options = { ...(widget.options || {}), serialize: false }; + widget.serializeValue = async () => undefined; + widget.value = RECOMMENDATION_PLACEHOLDER; + return widget; +} + +function resizeNode(node) { + requestAnimationFrame(() => { + const size = node.computeSize(); + if (size[0] < node.size[0]) { + size[0] = node.size[0]; + } + if (size[1] < node.size[1]) { + size[1] = node.size[1]; + } + node.setSize(size); + app.graph?.setDirtyCanvas?.(true, false); + }); +} + +function updateRecommendationWidget(node) { + const widget = ensureRecommendationWidget(node); + if (!widget) { + return; + } + loadRecommendations().then((rules) => { + widget.value = buildRecommendationText(node, rules); + resizeNode(node); + }); +} + +function queueRecommendationRefresh(node) { + if (!node || node._thinkingllmRecommendationQueued) { + return; + } + node._thinkingllmRecommendationQueued = true; + requestAnimationFrame(() => { + node._thinkingllmRecommendationQueued = false; + updateRecommendationWidget(node); + }); +} + +function hookRecommendationUpdates(node) { + if (!THINKINGLLM_NODE_NAMES.has(node.comfyClass) || node._thinkingllmRecommendationHooked) { + return; + } + node._thinkingllmRecommendationHooked = true; + ensureRecommendationWidget(node); + + for (const widgetName of ["model_name", "enable_thinking"]) { + const target = findWidget(node, widgetName); + if (!target || target._thinkingllmRecommendationCallbackHooked) { + continue; + } + target._thinkingllmRecommendationCallbackHooked = true; + const originalCallback = target.callback; + target.callback = function (...args) { + const result = originalCallback?.apply(this, args); + queueRecommendationRefresh(node); + return result; + }; + } + + const originalConfigure = node.onConfigure; + node.onConfigure = function (...args) { + const result = originalConfigure?.apply(this, args); + queueRecommendationRefresh(this); + return result; + }; + + queueRecommendationRefresh(node); +} + const ext = { name: "QwenVL.appearance", nodeCreated(node) { const nclass = node.comfyClass; if (NODE_COLORS.hasOwnProperty(nclass)) { - let colorKey = NODE_COLORS[nclass]; + const colorKey = NODE_COLORS[nclass]; const theme = COLOR_THEMES[colorKey]; setNodeColors(node, theme); } hideGgufAdvancedInternals(node); + hookRecommendationUpdates(node); clearHfTokenAfterExecution(node); } }; diff --git a/web/model_recommendations.json b/web/model_recommendations.json new file mode 100644 index 0000000..ac5bf55 --- /dev/null +++ b/web/model_recommendations.json @@ -0,0 +1,199 @@ +{ + "version": 1, + "rules": [ + { + "id": "qwen36_hauhau_multimodal", + "priority": 100, + "match_any": [ + "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive" + ], + "family_label": "Qwen3.6 35B A3B HauhauCS aggressive", + "sources": [ + "HauhauCS model card", + "Qwen3.5 official model card" + ], + "support_notes": { + "hf": "Not in the HF lineup here. Use the GGUF nodes for this model.", + "gguf": "Hard toggle via enable_thinking. Pair the vision node with the matching mmproj file.", + "soft_switch": "No official /think or /no_think soft switch for this family." + }, + "thinking_on": { + "temperature": 1.0, + "top_p": 0.95, + "top_k": 20, + "max_tokens": 32768, + "ctx_min": 131072 + }, + "thinking_off": { + "temperature": 0.7, + "top_p": 0.8, + "top_k": 20, + "max_tokens": 32768, + "ctx_min": 131072 + }, + "notes": [ + "Provider says keep at least 128K context to preserve thinking quality.", + "Q4_K_P is the best quality-size starting point; move to Q6_K_P or Q8_K_P when VRAM allows.", + "IQ quants are fallback options when you cannot fit Q4_K_M or Q4_K_P." + ] + }, + { + "id": "qwen35_family", + "priority": 80, + "match_any": [ + "Qwen3.5", + "qwen3.5" + ], + "family_label": "Qwen3.5 family", + "sources": [ + "Qwen3.5 official model card" + ], + "support_notes": { + "hf": "Hard toggle via enable_thinking when the backend honors the chat template.", + "gguf": "Hard toggle via enable_thinking in the Qwen chat template.", + "soft_switch": "No official /think or /no_think soft switch for Qwen3.5." + }, + "thinking_on": { + "temperature": 1.0, + "top_p": 0.95, + "top_k": 20, + "max_tokens": 32768, + "ctx_min": 131072 + }, + "thinking_off": { + "temperature": 0.7, + "top_p": 0.8, + "top_k": 20, + "max_tokens": 32768, + "ctx_min": 131072 + }, + "notes": [ + "For precise coding, Qwen recommends temperature 0.6 with thinking still enabled.", + "For hard non-thinking reasoning tasks, Qwen recommends top_p 1.0 and top_k 40 when the backend exposes them.", + "Provider says keep at least 128K context to preserve thinking capability and use 32K+ output budget when possible." + ] + }, + { + "id": "qwen3_family", + "priority": 70, + "match_any": [ + "Qwen3VL-", + "qwen3vl-", + "Qwen3-VL", + "Qwen3-", + "qwen3-" + ], + "exclude_any": [ + "Qwen3.5", + "qwen3.5", + "Qwen3.6", + "qwen3.6" + ], + "family_label": "Qwen3 family", + "sources": [ + "Qwen3 official model card", + "Qwen Transformers docs" + ], + "support_notes": { + "hf": "Hard toggle via enable_thinking, with /think and /no_think available as soft switches.", + "gguf": "Hard toggle via enable_thinking. /think and /no_think are available only on Qwen3 chat-template builds.", + "soft_switch": "Qwen3 officially supports /think and /no_think when thinking is enabled." + }, + "thinking_on": { + "temperature": 0.6, + "top_p": 0.95, + "top_k": 20, + "max_tokens": 32768, + "ctx_min": 32768 + }, + "thinking_off": { + "temperature": 0.7, + "top_p": 0.8, + "top_k": 20, + "max_tokens": 32768, + "ctx_min": 32768 + }, + "notes": [ + "Qwen warns against greedy decoding for thinking mode because it can repeat or degrade quality.", + "Provider recommends 32K output budget for most tasks and only enabling YaRN when you really need longer than the native 32K context.", + "For harder reasoning benchmarks, Qwen suggests stretching output budget beyond 32K if your backend allows it." + ] + }, + { + "id": "gemma4_large_family", + "priority": 60, + "match_any": [ + "Gemma-4-26B", + "gemma-4-26B", + "Gemma-4-31B", + "gemma-4-31B" + ], + "family_label": "Gemma 4 large family", + "sources": [ + "Gemma 4 official model card" + ], + "support_notes": { + "hf": "Gemma 4 supports configurable thinking, but current repo behavior can vary by backend.", + "gguf": "Use the settings manually and verify behavior in RAW_TRACE because GGUF support varies by backend.", + "soft_switch": "Gemma uses chat-template thinking controls instead of /think or /no_think." + }, + "thinking_on": { + "temperature": 1.0, + "top_p": 0.95, + "top_k": 64, + "max_tokens": 1024, + "ctx_min": 262144 + }, + "thinking_off": { + "temperature": 1.0, + "top_p": 0.95, + "top_k": 64, + "max_tokens": 1024, + "ctx_min": 262144 + }, + "notes": [ + "Gemma 4 uses one standardized sampler for both thinking and non-thinking modes.", + "Official context target is 256K on 26B A4B and 31B variants.", + "If the backend does not expose top_k, keep temperature and top_p at the official defaults." + ] + }, + { + "id": "gemma4_small_family", + "priority": 50, + "match_any": [ + "Gemma-4-E2B", + "gemma-4-E2B", + "Gemma-4-E4B", + "gemma-4-E4B" + ], + "family_label": "Gemma 4 small family", + "sources": [ + "Gemma 4 official model card" + ], + "support_notes": { + "hf": "Gemma 4 supports configurable thinking, but current repo behavior can vary by backend.", + "gguf": "Use the settings manually and verify behavior in RAW_TRACE because GGUF support varies by backend.", + "soft_switch": "Gemma uses chat-template thinking controls instead of /think or /no_think." + }, + "thinking_on": { + "temperature": 1.0, + "top_p": 0.95, + "top_k": 64, + "max_tokens": 1024, + "ctx_min": 131072 + }, + "thinking_off": { + "temperature": 1.0, + "top_p": 0.95, + "top_k": 64, + "max_tokens": 1024, + "ctx_min": 131072 + }, + "notes": [ + "Gemma 4 uses one standardized sampler for both thinking and non-thinking modes.", + "Official context target is 128K on E2B and E4B variants.", + "Gemma recommends higher visual token budgets only for OCR or fine detail tasks; lower budgets are faster for captioning or video summaries." + ] + } + ] +} \ No newline at end of file