karlexmarin Claude Opus 4.7 (1M context) commited on
Commit
819758d
·
1 Parent(s): b0f51d4

v0.8.3 PEFT Anti-Pattern Checker — anti-bullshit pack #9

Browse files

PEFT's `get_peft_model(base, config)` creates a FRESH adapter — it
does not load saved weights from a path. Users who paste tutorial code
and try to resume from a checkpoint silently throw away their training.
peft #2115 has the canonical bug report (12+ thumbs).

🔧 PEFT Lint (17th mode):
- Paste your PEFT/LoRA training script
- Static linter scans for 4 classes of bug:
1. silent_base_load — `get_peft_model` + checkpoint path string in
same script, no `PeftModel.from_pretrained`. Suggests the fix
inline with the detected path.
2. qlora_order — `prepare_model_for_kbit_training` AFTER
`get_peft_model`. Reversed order silently breaks gradient flow
through LoRA layers (loss → NaN or training nothing).
3. target_modules_mismatch — user's `target_modules=[…]` doesn't
overlap with the conventional list for the architecture
detected from a model id string. Covers Llama / Mistral / Qwen2
/ Phi / Phi-3 / Gemma / Falcon / Bloom / GPT-2 / GPT-NeoX / MPT.
4. alpha_not_2r — `lora_alpha` ratio off the α=2r / α=r
conventions (info-only).
- Findings rendered with severity badge, line number, per-rule
explainer + fix hint.

Pure logic in `js/peft_anti_pattern.js` (codes + params, no human
strings); main.js renders with i18n. Comment + string-literal stripping
prevents false positives on commented-out code or unrelated literals.

47 i18n keys × 4 langs (EN/ES/FR/ZH) = 188 keys, parity clean.
Solutions Hub `peft_loading` pain upgraded from
`tafagent_planned_mode: "🔧 PEFT Anti-Pattern Checker (v0.8.2)"` →
`tafagent_mode: "🔧 PEFT Anti-Pattern Checker"` (planned: → covered).
Help modal v0.8.3 entry + Inventory anti-bullshit-pack list updated +
task tile "⚙️ Set up an eval correctly" gains the new mode button.

Source citations:
- https://github.com/huggingface/peft/issues/2115 (the silent bug)
- https://huggingface.co/docs/peft/main/en/developer_guides/troubleshooting
- PEFT `get_layer_status() / get_model_status()` runtime check

Verified: 7/7 logic cases (silent base-load, correct from_pretrained,
QLoRA order, target_modules mismatch, alpha info, no-peft, comment
stripping) + 188/188 i18n parity + headless e2e (tab/section/three
examples render verdict+line+fix). 18 mode tabs total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (5) hide show
  1. data/solutions_hub.json +2 -3
  2. index.html +29 -0
  3. js/i18n.js +196 -0
  4. js/main.js +158 -1
  5. js/peft_anti_pattern.js +331 -0
data/solutions_hub.json CHANGED
@@ -222,15 +222,14 @@
222
  "id": "peft_loading",
223
  "category": "training",
224
  "pain": "`get_peft_model()` before `PeftModel.from_pretrained()` silently loads base model — LoRA weights ignored.",
225
- "tafagent_mode": null,
226
  "external_tools": [
227
  {"name": "HF PEFT troubleshooting (canonical)", "url": "https://huggingface.co/docs/peft/main/en/developer_guides/troubleshooting", "type": "docs"},
228
  {"name": "peft #2115 — original bug report", "url": "https://github.com/huggingface/peft/issues/2115", "type": "issue"},
229
  {"name": "PEFT get_layer_status() / get_model_status()", "url": "https://huggingface.co/docs/peft/main/en/package_reference/peft_model", "type": "docs"}
230
  ],
231
  "best_for": "If you suspect your LoRA isn't being applied, call `model.get_layer_status()` and check `active_adapters` is non-empty.",
232
- "not_for": null,
233
- "tafagent_planned_mode": "🔧 PEFT Anti-Pattern Checker (v0.8.2)"
234
  },
235
  {
236
  "id": "intruder_dimensions",
 
222
  "id": "peft_loading",
223
  "category": "training",
224
  "pain": "`get_peft_model()` before `PeftModel.from_pretrained()` silently loads base model — LoRA weights ignored.",
225
+ "tafagent_mode": "🔧 PEFT Anti-Pattern Checker",
226
  "external_tools": [
227
  {"name": "HF PEFT troubleshooting (canonical)", "url": "https://huggingface.co/docs/peft/main/en/developer_guides/troubleshooting", "type": "docs"},
228
  {"name": "peft #2115 — original bug report", "url": "https://github.com/huggingface/peft/issues/2115", "type": "issue"},
229
  {"name": "PEFT get_layer_status() / get_model_status()", "url": "https://huggingface.co/docs/peft/main/en/package_reference/peft_model", "type": "docs"}
230
  ],
231
  "best_for": "If you suspect your LoRA isn't being applied, call `model.get_layer_status()` and check `active_adapters` is non-empty.",
232
+ "not_for": null
 
233
  },
234
  {
235
  "id": "intruder_dimensions",
index.html CHANGED
@@ -219,6 +219,9 @@
219
  <p><strong data-i18n="help.v082.cot.title">📋 JSON CoT-aware Linter</strong></p>
220
  <p data-i18n="help.v082.cot.body">Constrained-decoding engines (llguidance, Outlines, SGLang grammars) emit JSON properties in the order your schema declares them. If you write <code>{ answer, reasoning }</code> the model commits to <code>answer</code> first and CoT collapses into post-hoc justification. Paste any schema (or example response) — the linter classifies each field as <em>reasoning</em>, <em>answer</em>, or <em>other</em>, flags the ordering, and emits a reordered fix you can copy back. <em>Use case</em>: 'My CoT prompt works in plaintext but degrades under JSON mode' → run linter, find the inverted order, fix.</p>
221
 
 
 
 
222
  <p><strong data-i18n="help.v081.hub.title">🧭 Solutions Hub</strong></p>
223
  <p data-i18n="help.v081.hub.body">tafagent as integrator, not silo. 30+ pains across 7 categories (eval reliability · diagnostics · setup · training · retrieval · multimodal · observability), each mapped to (a) the tafagent mode that addresses it, if any, and (b) the best-of-breed external tools the community already trusts (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). Search box matches across pain, scenario, and tool name. <em>Use case</em>: 'I have problem X — does tafagent solve it, and if not, who does?'</p>
224
 
@@ -332,6 +335,7 @@
332
  <li data-i18n="inv.v07.niah"><strong>🔍 NIAH→Reason</strong> — does your "128k context" actually reason there, or just retrieve?</li>
333
  <li data-i18n="inv.v08.saturation"><strong>📈 Saturation</strong> — is your benchmark still useful, or are all frontier models tied at the top?</li>
334
  <li data-i18n="inv.v082.cot"><strong>📋 JSON CoT</strong> — lints structured-output schemas for the answer-before-reasoning anti-pattern that silently breaks Chain-of-Thought.</li>
 
335
  <li data-i18n="inv.v081.hub"><strong>🧭 Solutions Hub</strong> — every documented pain mapped to a tafagent mode or curated external tool. Don't reinvent — find.</li>
336
  </ul>
337
  </details>
@@ -404,6 +408,7 @@
404
  <button data-mode-link="template" data-i18n="modes.template">📜 Chat-template</button>
405
  <button data-mode-link="diagnose" data-i18n="modes.diagnose">🩺 Diagnose CLI</button>
406
  <button data-mode-link="cot" data-i18n="modes.cot">📋 JSON CoT</button>
 
407
  </div>
408
  </div>
409
  <div class="task-tile">
@@ -461,6 +466,7 @@
461
  <button class="mode-btn" data-mode="niah" role="tab" aria-selected="false" data-i18n="modes.niah">🔍 NIAH→Reason</button>
462
  <button class="mode-btn" data-mode="saturation" role="tab" aria-selected="false" data-i18n="modes.saturation">📈 Saturation</button>
463
  <button class="mode-btn" data-mode="cot" role="tab" aria-selected="false" data-i18n="modes.cot">📋 JSON CoT</button>
 
464
  <button class="mode-btn" data-mode="hub" role="tab" aria-selected="false" data-i18n="modes.hub">🧭 Solutions</button>
465
  </div>
466
  <p id="mode-desc" class="recipe-desc" data-i18n="modes.desc">
@@ -1032,6 +1038,29 @@
1032
  <div id="cot-output" style="margin-top: 1em;"></div>
1033
  </section>
1034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1035
  <section id="hub-section" style="display:none;">
1036
  <h2><span data-i18n="hub.title">🧭 Solutions Hub</span>
1037
  <span class="info"><span class="tooltip" data-i18n="hub.tip">
 
219
  <p><strong data-i18n="help.v082.cot.title">📋 JSON CoT-aware Linter</strong></p>
220
  <p data-i18n="help.v082.cot.body">Constrained-decoding engines (llguidance, Outlines, SGLang grammars) emit JSON properties in the order your schema declares them. If you write <code>{ answer, reasoning }</code> the model commits to <code>answer</code> first and CoT collapses into post-hoc justification. Paste any schema (or example response) — the linter classifies each field as <em>reasoning</em>, <em>answer</em>, or <em>other</em>, flags the ordering, and emits a reordered fix you can copy back. <em>Use case</em>: 'My CoT prompt works in plaintext but degrades under JSON mode' → run linter, find the inverted order, fix.</p>
221
 
222
+ <p><strong data-i18n="help.v083.peft.title">🔧 PEFT Anti-Pattern Checker</strong></p>
223
+ <p data-i18n="help.v083.peft.body">PEFT's <code>get_peft_model(base, config)</code> creates a FRESH adapter — it does not load saved weights from a path. Users who paste tutorial code and try to resume from a checkpoint silently throw away their training. peft #2115 has the canonical bug report. This linter scans your training script for the pattern + 3 related issues (QLoRA ordering, target_modules/arch mismatch, lora_alpha ratio) and reports findings with line numbers and suggested fixes. <em>Use case</em>: before you launch a 10-hour LoRA fine-tune, paste your script — catch the silent bugs in 200ms.</p>
224
+
225
  <p><strong data-i18n="help.v081.hub.title">🧭 Solutions Hub</strong></p>
226
  <p data-i18n="help.v081.hub.body">tafagent as integrator, not silo. 30+ pains across 7 categories (eval reliability · diagnostics · setup · training · retrieval · multimodal · observability), each mapped to (a) the tafagent mode that addresses it, if any, and (b) the best-of-breed external tools the community already trusts (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). Search box matches across pain, scenario, and tool name. <em>Use case</em>: 'I have problem X — does tafagent solve it, and if not, who does?'</p>
227
 
 
335
  <li data-i18n="inv.v07.niah"><strong>🔍 NIAH→Reason</strong> — does your "128k context" actually reason there, or just retrieve?</li>
336
  <li data-i18n="inv.v08.saturation"><strong>📈 Saturation</strong> — is your benchmark still useful, or are all frontier models tied at the top?</li>
337
  <li data-i18n="inv.v082.cot"><strong>📋 JSON CoT</strong> — lints structured-output schemas for the answer-before-reasoning anti-pattern that silently breaks Chain-of-Thought.</li>
338
+ <li data-i18n="inv.v083.peft"><strong>🔧 PEFT Lint</strong> — catches the silent <code>get_peft_model</code> base-load (peft #2115) + QLoRA order + target_modules / arch mismatch.</li>
339
  <li data-i18n="inv.v081.hub"><strong>🧭 Solutions Hub</strong> — every documented pain mapped to a tafagent mode or curated external tool. Don't reinvent — find.</li>
340
  </ul>
341
  </details>
 
408
  <button data-mode-link="template" data-i18n="modes.template">📜 Chat-template</button>
409
  <button data-mode-link="diagnose" data-i18n="modes.diagnose">🩺 Diagnose CLI</button>
410
  <button data-mode-link="cot" data-i18n="modes.cot">📋 JSON CoT</button>
411
+ <button data-mode-link="peft" data-i18n="modes.peft">🔧 PEFT Lint</button>
412
  </div>
413
  </div>
414
  <div class="task-tile">
 
466
  <button class="mode-btn" data-mode="niah" role="tab" aria-selected="false" data-i18n="modes.niah">🔍 NIAH→Reason</button>
467
  <button class="mode-btn" data-mode="saturation" role="tab" aria-selected="false" data-i18n="modes.saturation">📈 Saturation</button>
468
  <button class="mode-btn" data-mode="cot" role="tab" aria-selected="false" data-i18n="modes.cot">📋 JSON CoT</button>
469
+ <button class="mode-btn" data-mode="peft" role="tab" aria-selected="false" data-i18n="modes.peft">🔧 PEFT Lint</button>
470
  <button class="mode-btn" data-mode="hub" role="tab" aria-selected="false" data-i18n="modes.hub">🧭 Solutions</button>
471
  </div>
472
  <p id="mode-desc" class="recipe-desc" data-i18n="modes.desc">
 
1038
  <div id="cot-output" style="margin-top: 1em;"></div>
1039
  </section>
1040
 
1041
+ <!-- PEFT Anti-Pattern Checker (mode=peft, v0.8.3 anti-bullshit pack #9) -->
1042
+ <section id="peft-section" style="display:none;">
1043
+ <h2><span data-i18n="peft.title">🔧 PEFT Anti-Pattern Checker</span>
1044
+ <span class="info"><span class="tooltip" data-i18n="peft.tip">
1045
+ <strong>Why this matters</strong>: <code>get_peft_model(base, config)</code> creates a FRESH adapter — it does NOT load saved weights. Users who want to resume from a checkpoint must call <code>PeftModel.from_pretrained(base, path)</code>. peft #2115 documents the silent base-model bug. This linter scans your training script for that pattern (and 3 others: QLoRA ordering, target_modules/arch mismatch, lora_alpha ratio).
1046
+ </span></span>
1047
+ </h2>
1048
+ <p class="recipe-desc" data-i18n="peft.desc">
1049
+ <strong>Don't burn 10 hours of training on a base model.</strong> Paste your PEFT setup code — the linter flags silent base-model loads, QLoRA ordering bugs, target_modules/arch mismatches, and lora_alpha conventions.
1050
+ </p>
1051
+ <div class="form-row">
1052
+ <textarea id="peft-input" rows="14" style="width:100%;font-family:monospace;font-size:0.9em;" data-i18n-placeholder="peft.input.placeholder" placeholder="from peft import LoraConfig, get_peft_model&#10;base = AutoModelForCausalLM.from_pretrained('meta-llama/Llama-3-8B')&#10;config = LoraConfig(r=16, lora_alpha=32, target_modules=['q_proj','v_proj'])&#10;model = get_peft_model(base, config)&#10;# resume from saved adapter:&#10;model.load_state_dict('./outputs/checkpoint-1000/adapter_model.bin')"></textarea>
1053
+ </div>
1054
+ <div class="form-row">
1055
+ <button type="button" id="peft-lint-btn" data-i18n="peft.lint_btn">🔍 Lint</button>
1056
+ <button type="button" id="peft-example-bug-btn" class="secondary" data-i18n="peft.example_bug_btn">↳ Example: silent base-load</button>
1057
+ <button type="button" id="peft-example-qlora-btn" class="secondary" data-i18n="peft.example_qlora_btn">↳ Example: QLoRA order bug</button>
1058
+ <button type="button" id="peft-example-clean-btn" class="secondary" data-i18n="peft.example_clean_btn">↳ Example: clean</button>
1059
+ </div>
1060
+ <p id="peft-status" class="recipe-desc" style="font-size:0.92em;"></p>
1061
+ <div id="peft-output" style="margin-top: 1em;"></div>
1062
+ </section>
1063
+
1064
  <section id="hub-section" style="display:none;">
1065
  <h2><span data-i18n="hub.title">🧭 Solutions Hub</span>
1066
  <span class="info"><span class="tooltip" data-i18n="hub.tip">
js/i18n.js CHANGED
@@ -545,6 +545,55 @@ export const TRANSLATIONS = {
545
  "help.v082.cot.title": "📋 JSON CoT-aware Linter",
546
  "help.v082.cot.body": "Constrained-decoding engines (llguidance, Outlines, SGLang grammars) emit JSON properties in the order your schema declares them. If you write <code>{ answer, reasoning }</code> the model commits to <code>answer</code> first and CoT collapses into post-hoc justification. Paste any schema (or example response) — the linter classifies each field as <em>reasoning</em>, <em>answer</em>, or <em>other</em>, flags the ordering, and emits a reordered fix you can copy back. <em>Use case</em>: 'My CoT prompt works in plaintext but degrades under JSON mode' → run linter, find the inverted order, fix.",
547
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — every documented pain mapped to a tafagent mode or curated external tool. Don't reinvent — find.",
549
  "help.v081.hub.title": "🧭 Solutions Hub",
550
  "help.v081.hub.body": "tafagent as integrator, not silo. 30+ pains across 7 categories (eval reliability · diagnostics · setup · training · retrieval · multimodal · observability), each mapped to (a) the tafagent mode that addresses it, if any, and (b) the best-of-breed external tools the community already trusts (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). Search box matches across pain, scenario, and tool name. <em>Use case</em>: 'I have problem X — does tafagent solve it, and if not, who does?'",
@@ -1549,6 +1598,55 @@ export const TRANSLATIONS = {
1549
  "help.v082.cot.title": "📋 Linter JSON con consciencia CoT",
1550
  "help.v082.cot.body": "Los motores de constrained decoding (llguidance, Outlines, gramáticas SGLang) emiten propiedades JSON en el orden que declara tu schema. Si escribes <code>{ answer, reasoning }</code> el modelo se compromete con <code>answer</code> primero y el CoT se reduce a justificación post-hoc. Pega cualquier schema (o respuesta de ejemplo) — el linter clasifica cada campo como <em>razonamiento</em>, <em>respuesta</em> u <em>otro</em>, señala el ordenamiento, y emite una corrección reordenada para copiar de vuelta. <em>Caso de uso</em>: 'Mi prompt CoT funciona en texto pero degrada en modo JSON' → ejecuta linter, encuentra el orden invertido, corrige.",
1551
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1552
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — cada pain documentado mapeado a un mode tafagent o herramienta externa curada. No reinventes — encuentra.",
1553
  "help.v081.hub.title": "🧭 Solutions Hub",
1554
  "help.v081.hub.body": "tafagent como integrador, no silo. 30+ pains en 7 categorías (eval reliability · diagnósticos · setup · training · retrieval · multimodal · observability), cada uno mapeado a (a) el mode tafagent que lo resuelve, si existe, y (b) las herramientas externas best-of-breed que la comunidad ya usa (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). Caja de búsqueda matchea pain, scenario, y nombre de herramienta. <em>Caso de uso</em>: 'tengo problema X — ¿lo resuelve tafagent, y si no, quién?'",
@@ -2417,6 +2515,55 @@ export const TRANSLATIONS = {
2417
  "help.v082.cot.title": "📋 Linter JSON conscient de CoT",
2418
  "help.v082.cot.body": "Les moteurs de décodage contraint (llguidance, Outlines, grammaires SGLang) émettent les propriétés JSON dans l'ordre que votre schema déclare. Si vous écrivez <code>{ answer, reasoning }</code> le modèle s'engage sur <code>answer</code> en premier et le CoT se réduit à une justification a posteriori. Collez n'importe quel schema (ou réponse exemple) — le linter classe chaque champ comme <em>raisonnement</em>, <em>réponse</em> ou <em>autre</em>, signale l'ordre, et émet une correction réordonnée à copier. <em>Cas d'usage</em> : 'Mon prompt CoT marche en texte brut mais dégrade en mode JSON' → lancez le linter, trouvez l'ordre inversé, corrigez.",
2419
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2420
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — chaque pain documenté mappé à un mode tafagent ou outil externe curé. Ne réinventez pas — trouvez.",
2421
  "help.v081.hub.title": "🧭 Solutions Hub",
2422
  "help.v081.hub.body": "tafagent comme intégrateur, pas silo. 30+ pains à travers 7 catégories (eval reliability · diagnostics · setup · training · retrieval · multimodal · observability), chacun mappé à (a) le mode tafagent qui le résout, s'il existe, et (b) les outils externes best-of-breed que la communauté utilise déjà (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). La barre de recherche matche pain, scénario, et nom d'outil. <em>Cas d'usage</em> : 'j'ai le problème X — tafagent le résout-il, et sinon, qui ?'",
@@ -3285,6 +3432,55 @@ export const TRANSLATIONS = {
3285
  "help.v082.cot.title": "📋 JSON CoT 感知 Linter",
3286
  "help.v082.cot.body": "约束解码引擎(llguidance、Outlines、SGLang 语法)按 schema 声明的顺序输出 JSON 属性。如果你写 <code>{ answer, reasoning }</code>,模型先承诺 <code>answer</code>,CoT 就退化为事后辩护。粘贴任意 schema(或示例响应)——linter 把每个字段分类为<em>推理</em>、<em>答案</em>或<em>其他</em>,标记顺序,并输出可复制回去的重排修复。<em>用例</em>:『我的 CoT 提示在纯文本中正常但在 JSON 模式下退化』→ 运行 linter,找到颠倒的顺序,修复。",
3287
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3288
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — 每个文档化的问题都映射到一个 tafagent 模式或精选外部工具。别重复发明 — 去找。",
3289
  "help.v081.hub.title": "🧭 Solutions Hub",
3290
  "help.v081.hub.body": "tafagent 作为集成者而非孤岛。30+ 问题跨 7 类别(评估可靠性 · 诊断 · 设置 · 训练 · 检索 · 多模态 · 可观测性),每个映射到(a)解决它的 tafagent 模式(若存在),以及(b)社区已信任的最佳外部工具(RAGAS、MTEB、HELM、MCP Schema Validator、llm-stats、llguidance、GlitchMiner 等)。搜索框匹配 pain、场景和工具名称。<em>用例</em>:'我有问题 X — tafagent 解决它吗,如果不,谁解决?'",
 
545
  "help.v082.cot.title": "📋 JSON CoT-aware Linter",
546
  "help.v082.cot.body": "Constrained-decoding engines (llguidance, Outlines, SGLang grammars) emit JSON properties in the order your schema declares them. If you write <code>{ answer, reasoning }</code> the model commits to <code>answer</code> first and CoT collapses into post-hoc justification. Paste any schema (or example response) — the linter classifies each field as <em>reasoning</em>, <em>answer</em>, or <em>other</em>, flags the ordering, and emits a reordered fix you can copy back. <em>Use case</em>: 'My CoT prompt works in plaintext but degrades under JSON mode' → run linter, find the inverted order, fix.",
547
 
548
+ // v0.8.3 — anti-bullshit pack #9: PEFT Anti-Pattern Checker
549
+ "modes.peft": "🔧 PEFT Lint",
550
+ "mode_desc.peft": "Static linter for PEFT/LoRA training scripts. Catches the silent base-model load (peft #2115), QLoRA prepare/get_peft_model ordering, target_modules/arch mismatch, and lora_alpha conventions.",
551
+ "peft.title": "🔧 PEFT Anti-Pattern Checker",
552
+ "peft.tip": "<code>get_peft_model(base, config)</code> creates a FRESH adapter — it does NOT load saved weights. Users who want to resume from a checkpoint must call <code>PeftModel.from_pretrained(base, path)</code>. peft #2115 documents the silent base-model bug. This linter scans your training script for that pattern (and 3 others: QLoRA ordering, target_modules/arch mismatch, lora_alpha ratio).",
553
+ "peft.desc": "<strong>Don't burn 10 hours of training on a base model.</strong> Paste your PEFT setup code — the linter flags silent base-model loads, QLoRA ordering bugs, target_modules/arch mismatches, and lora_alpha conventions.",
554
+ "peft.input.placeholder": "from peft import LoraConfig, get_peft_model …",
555
+ "peft.lint_btn": "🔍 Lint",
556
+ "peft.example_bug_btn": "↳ Example: silent base-load",
557
+ "peft.example_qlora_btn": "↳ Example: QLoRA order bug",
558
+ "peft.example_clean_btn": "↳ Example: clean",
559
+ "peft.status.done": "✅ {verdict} — {n} finding(s)",
560
+ "peft.line": "line {n}",
561
+ "peft.summary": "{total} finding(s)",
562
+ "peft.attribution": "Refs:",
563
+ "peft.detected_at_line": "appears at line",
564
+ "peft.suggested_fix": "Suggested:",
565
+ "peft.detected_arch": "Detected arch",
566
+ "peft.from_model_id": "(from model id",
567
+ "peft.your_modules": "Your target_modules",
568
+ "peft.expected_modules": "Expected for this arch",
569
+ "peft.match_ratio": "{hits} of {total} match.",
570
+ "peft.ratio": "ratio",
571
+ "peft.alpha.convention": "convention is α=2r or α=r",
572
+ "peft.qlora_order.detail": "prepare_model_for_kbit_training (line {prepare_line}) runs AFTER get_peft_model (line {get_peft_model_line}). Reverse the order — call prepare FIRST, then get_peft_model.",
573
+ "peft.no_peft_calls.detail": "No get_peft_model / PeftModel.from_pretrained / LoraConfig calls detected. Paste a PEFT/LoRA setup snippet.",
574
+ "peft.verdict.errors_found": "❌ Errors found",
575
+ "peft.verdict.warnings_only": "⚠ Warnings",
576
+ "peft.verdict.info_only": "ℹ Info",
577
+ "peft.verdict.clean": "✅ Clean — no issues detected",
578
+ "peft.verdict.no_peft_calls": "ℹ No PEFT calls detected",
579
+ "peft.verdict.empty_input": "ℹ Empty input",
580
+ "peft.rule.silent_base_load.label": "Silent base-model load (peft #2115)",
581
+ "peft.rule.silent_base_load.explain": "<code>get_peft_model(base, config)</code> creates a NEW adapter — it does NOT load saved weights. The checkpoint hint in your code suggests you want to RESUME training from a saved adapter, but this code path will quietly start fresh and overwrite the run.",
582
+ "peft.rule.silent_base_load.fix": "Replace <code>get_peft_model(base, config)</code> with <code>PeftModel.from_pretrained(base, path)</code> when resuming. Verify with <code>model.get_layer_status()</code> after load.",
583
+ "peft.rule.qlora_order.label": "QLoRA ordering bug",
584
+ "peft.rule.qlora_order.explain": "<code>prepare_model_for_kbit_training</code> must be called BEFORE <code>get_peft_model</code>. Reversed, the kbit prep doesn't apply to the LoRA layers and gradient computation breaks (loss → NaN, or silent training of nothing).",
585
+ "peft.rule.qlora_order.fix": "Reorder: <code>base = prepare_model_for_kbit_training(base)</code> then <code>model = get_peft_model(base, config)</code>.",
586
+ "peft.rule.target_modules_mismatch.label": "target_modules / arch mismatch",
587
+ "peft.rule.target_modules_mismatch.explain": "Your <code>target_modules</code> list doesn't match the conventional module names for the architecture detected in your code. PEFT will silently apply LoRA to nothing (or to the wrong layers).",
588
+ "peft.rule.target_modules_mismatch.fix": "Verify module names with <code>print([n for n,_ in model.named_modules()])</code> on the loaded base model, or use the architecture-specific list shown above.",
589
+ "peft.rule.alpha_not_2r.label": "lora_alpha ≠ 2r convention",
590
+ "peft.rule.alpha_not_2r.explain": "Most published LoRA recipes use either <code>α = 2r</code> (effective unit scale) or <code>α = r</code> (reduced effective LR). A custom ratio works but warrants a sanity check.",
591
+ "peft.rule.alpha_not_2r.fix": "Sanity-check the ratio against your reference recipe. If intentional, ignore this finding.",
592
+ "peft.rule.no_peft_calls.label": "No PEFT calls detected",
593
+ "inv.v083.peft": "<strong>🔧 PEFT Lint</strong> — catches the silent <code>get_peft_model</code> base-load (peft #2115) + QLoRA order + target_modules / arch mismatch.",
594
+ "help.v083.peft.title": "🔧 PEFT Anti-Pattern Checker",
595
+ "help.v083.peft.body": "PEFT's <code>get_peft_model(base, config)</code> creates a FRESH adapter — it does not load saved weights from a path. Users who paste tutorial code and try to resume from a checkpoint silently throw away their training. peft #2115 has the canonical bug report. This linter scans your training script for the pattern + 3 related issues (QLoRA ordering, target_modules/arch mismatch, lora_alpha ratio) and reports findings with line numbers and suggested fixes. <em>Use case</em>: before you launch a 10-hour LoRA fine-tune, paste your script — catch the silent bugs in 200ms.",
596
+
597
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — every documented pain mapped to a tafagent mode or curated external tool. Don't reinvent — find.",
598
  "help.v081.hub.title": "🧭 Solutions Hub",
599
  "help.v081.hub.body": "tafagent as integrator, not silo. 30+ pains across 7 categories (eval reliability · diagnostics · setup · training · retrieval · multimodal · observability), each mapped to (a) the tafagent mode that addresses it, if any, and (b) the best-of-breed external tools the community already trusts (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). Search box matches across pain, scenario, and tool name. <em>Use case</em>: 'I have problem X — does tafagent solve it, and if not, who does?'",
 
1598
  "help.v082.cot.title": "📋 Linter JSON con consciencia CoT",
1599
  "help.v082.cot.body": "Los motores de constrained decoding (llguidance, Outlines, gramáticas SGLang) emiten propiedades JSON en el orden que declara tu schema. Si escribes <code>{ answer, reasoning }</code> el modelo se compromete con <code>answer</code> primero y el CoT se reduce a justificación post-hoc. Pega cualquier schema (o respuesta de ejemplo) — el linter clasifica cada campo como <em>razonamiento</em>, <em>respuesta</em> u <em>otro</em>, señala el ordenamiento, y emite una corrección reordenada para copiar de vuelta. <em>Caso de uso</em>: 'Mi prompt CoT funciona en texto pero degrada en modo JSON' → ejecuta linter, encuentra el orden invertido, corrige.",
1600
 
1601
+ // v0.8.3 — anti-bullshit pack #9: PEFT Anti-Pattern Checker
1602
+ "modes.peft": "🔧 PEFT Lint",
1603
+ "mode_desc.peft": "Linter estático para scripts de entrenamiento PEFT/LoRA. Detecta carga silenciosa del modelo base (peft #2115), orden de prepare_model_for_kbit_training/get_peft_model en QLoRA, mismatch de target_modules/arch, y conveniones de lora_alpha.",
1604
+ "peft.title": "🔧 Verificador de anti-patrones PEFT",
1605
+ "peft.tip": "<code>get_peft_model(base, config)</code> crea un adapter NUEVO — NO carga pesos guardados. Quien quiera reanudar desde un checkpoint debe llamar <code>PeftModel.from_pretrained(base, path)</code>. peft #2115 documenta el bug de carga silenciosa del modelo base. Este linter escanea tu script en busca de ese patrón (y otros 3: orden QLoRA, mismatch target_modules/arch, ratio lora_alpha).",
1606
+ "peft.desc": "<strong>No quemes 10 horas de entrenamiento sobre un modelo base.</strong> Pega tu código de setup PEFT — el linter señala cargas silenciosas del base, bugs de orden QLoRA, mismatches target_modules/arch, y conveniones lora_alpha.",
1607
+ "peft.input.placeholder": "from peft import LoraConfig, get_peft_model …",
1608
+ "peft.lint_btn": "🔍 Lintear",
1609
+ "peft.example_bug_btn": "↳ Ejemplo: carga silenciosa del base",
1610
+ "peft.example_qlora_btn": "↳ Ejemplo: bug de orden QLoRA",
1611
+ "peft.example_clean_btn": "↳ Ejemplo: limpio",
1612
+ "peft.status.done": "✅ {verdict} — {n} hallazgo(s)",
1613
+ "peft.line": "línea {n}",
1614
+ "peft.summary": "{total} hallazgo(s)",
1615
+ "peft.attribution": "Referencias:",
1616
+ "peft.detected_at_line": "aparece en la línea",
1617
+ "peft.suggested_fix": "Sugerencia:",
1618
+ "peft.detected_arch": "Arch detectada",
1619
+ "peft.from_model_id": "(desde model id",
1620
+ "peft.your_modules": "Tus target_modules",
1621
+ "peft.expected_modules": "Esperados para esta arch",
1622
+ "peft.match_ratio": "{hits} de {total} coinciden.",
1623
+ "peft.ratio": "ratio",
1624
+ "peft.alpha.convention": "la convención es α=2r o α=r",
1625
+ "peft.qlora_order.detail": "prepare_model_for_kbit_training (línea {prepare_line}) corre DESPUÉS de get_peft_model (línea {get_peft_model_line}). Invierte el orden — llama prepare PRIMERO, luego get_peft_model.",
1626
+ "peft.no_peft_calls.detail": "No se detectan llamadas a get_peft_model / PeftModel.from_pretrained / LoraConfig. Pega un snippet de setup PEFT/LoRA.",
1627
+ "peft.verdict.errors_found": "❌ Errores encontrados",
1628
+ "peft.verdict.warnings_only": "⚠ Avisos",
1629
+ "peft.verdict.info_only": "ℹ Info",
1630
+ "peft.verdict.clean": "✅ Limpio — sin issues detectados",
1631
+ "peft.verdict.no_peft_calls": "ℹ Sin llamadas PEFT detectadas",
1632
+ "peft.verdict.empty_input": "ℹ Entrada vacía",
1633
+ "peft.rule.silent_base_load.label": "Carga silenciosa del modelo base (peft #2115)",
1634
+ "peft.rule.silent_base_load.explain": "<code>get_peft_model(base, config)</code> crea un adapter NUEVO — NO carga pesos guardados. La pista de checkpoint en tu código sugiere que quieres REANUDAR el entrenamiento desde un adapter guardado, pero esta ruta arrancará silenciosamente desde cero y sobrescribirá la corrida.",
1635
+ "peft.rule.silent_base_load.fix": "Reemplaza <code>get_peft_model(base, config)</code> por <code>PeftModel.from_pretrained(base, path)</code> al reanudar. Verifica con <code>model.get_layer_status()</code> tras cargar.",
1636
+ "peft.rule.qlora_order.label": "Bug de orden QLoRA",
1637
+ "peft.rule.qlora_order.explain": "<code>prepare_model_for_kbit_training</code> debe llamarse ANTES de <code>get_peft_model</code>. Invertido, la prep kbit no aplica a las capas LoRA y el cálculo del gradiente se rompe (loss → NaN, o entrenamiento silencioso de nada).",
1638
+ "peft.rule.qlora_order.fix": "Reordena: <code>base = prepare_model_for_kbit_training(base)</code> luego <code>model = get_peft_model(base, config)</code>.",
1639
+ "peft.rule.target_modules_mismatch.label": "Mismatch target_modules / arch",
1640
+ "peft.rule.target_modules_mismatch.explain": "Tu lista <code>target_modules</code> no coincide con los nombres convencionales para la arquitectura detectada en tu código. PEFT aplicará LoRA silenciosamente a nada (o a las capas equivocadas).",
1641
+ "peft.rule.target_modules_mismatch.fix": "Verifica los nombres con <code>print([n for n,_ in model.named_modules()])</code> sobre el modelo base cargado, o usa la lista específica de la arch mostrada arriba.",
1642
+ "peft.rule.alpha_not_2r.label": "lora_alpha ≠ 2r (convención)",
1643
+ "peft.rule.alpha_not_2r.explain": "La mayoría de recetas LoRA publicadas usan o <code>α = 2r</code> (escala efectiva unitaria) o <code>α = r</code> (LR efectivo reducido). Un ratio custom funciona pero merece una verificación.",
1644
+ "peft.rule.alpha_not_2r.fix": "Verifica el ratio contra tu receta de referencia. Si es intencional, ignora este hallazgo.",
1645
+ "peft.rule.no_peft_calls.label": "Sin llamadas PEFT detectadas",
1646
+ "inv.v083.peft": "<strong>🔧 PEFT Lint</strong> — detecta la carga silenciosa de <code>get_peft_model</code> sobre el base (peft #2115) + orden QLoRA + mismatch target_modules / arch.",
1647
+ "help.v083.peft.title": "🔧 Verificador de anti-patrones PEFT",
1648
+ "help.v083.peft.body": "El <code>get_peft_model(base, config)</code> de PEFT crea un adapter NUEVO — no carga pesos guardados desde una ruta. Quien pega código de tutorial e intenta reanudar desde un checkpoint tira silenciosamente su entrenamiento. peft #2115 tiene el bug report canónico. Este linter escanea tu script buscando el patrón + 3 issues relacionados (orden QLoRA, mismatch target_modules/arch, ratio lora_alpha) y reporta hallazgos con números de línea y sugerencias. <em>Caso de uso</em>: antes de lanzar un fine-tune LoRA de 10 horas, pega tu script — atrapa los bugs silenciosos en 200ms.",
1649
+
1650
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — cada pain documentado mapeado a un mode tafagent o herramienta externa curada. No reinventes — encuentra.",
1651
  "help.v081.hub.title": "🧭 Solutions Hub",
1652
  "help.v081.hub.body": "tafagent como integrador, no silo. 30+ pains en 7 categorías (eval reliability · diagnósticos · setup · training · retrieval · multimodal · observability), cada uno mapeado a (a) el mode tafagent que lo resuelve, si existe, y (b) las herramientas externas best-of-breed que la comunidad ya usa (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). Caja de búsqueda matchea pain, scenario, y nombre de herramienta. <em>Caso de uso</em>: 'tengo problema X — ¿lo resuelve tafagent, y si no, quién?'",
 
2515
  "help.v082.cot.title": "📋 Linter JSON conscient de CoT",
2516
  "help.v082.cot.body": "Les moteurs de décodage contraint (llguidance, Outlines, grammaires SGLang) émettent les propriétés JSON dans l'ordre que votre schema déclare. Si vous écrivez <code>{ answer, reasoning }</code> le modèle s'engage sur <code>answer</code> en premier et le CoT se réduit à une justification a posteriori. Collez n'importe quel schema (ou réponse exemple) — le linter classe chaque champ comme <em>raisonnement</em>, <em>réponse</em> ou <em>autre</em>, signale l'ordre, et émet une correction réordonnée à copier. <em>Cas d'usage</em> : 'Mon prompt CoT marche en texte brut mais dégrade en mode JSON' → lancez le linter, trouvez l'ordre inversé, corrigez.",
2517
 
2518
+ // v0.8.3 — anti-bullshit pack #9: PEFT Anti-Pattern Checker
2519
+ "modes.peft": "🔧 PEFT Lint",
2520
+ "mode_desc.peft": "Linter statique pour les scripts d'entraînement PEFT/LoRA. Attrape le chargement silencieux du modèle de base (peft #2115), l'ordre prepare/get_peft_model en QLoRA, le mismatch target_modules/arch, et les conventions de lora_alpha.",
2521
+ "peft.title": "🔧 Vérificateur d'anti-patterns PEFT",
2522
+ "peft.tip": "<code>get_peft_model(base, config)</code> crée un nouvel adaptateur — il ne CHARGE PAS les poids sauvegardés. Pour reprendre depuis un checkpoint il faut appeler <code>PeftModel.from_pretrained(base, path)</code>. peft #2115 documente le bug du chargement silencieux. Ce linter scanne votre script à la recherche de ce pattern (et 3 autres : ordre QLoRA, mismatch target_modules/arch, ratio lora_alpha).",
2523
+ "peft.desc": "<strong>Ne brûlez pas 10 heures d'entraînement sur un modèle de base.</strong> Collez votre code de setup PEFT — le linter signale les chargements silencieux du base, les bugs d'ordre QLoRA, les mismatches target_modules/arch, et les conventions lora_alpha.",
2524
+ "peft.input.placeholder": "from peft import LoraConfig, get_peft_model …",
2525
+ "peft.lint_btn": "🔍 Linter",
2526
+ "peft.example_bug_btn": "↳ Exemple : chargement silencieux du base",
2527
+ "peft.example_qlora_btn": "↳ Exemple : bug d'ordre QLoRA",
2528
+ "peft.example_clean_btn": "↳ Exemple : propre",
2529
+ "peft.status.done": "✅ {verdict} — {n} découverte(s)",
2530
+ "peft.line": "ligne {n}",
2531
+ "peft.summary": "{total} découverte(s)",
2532
+ "peft.attribution": "Réfs :",
2533
+ "peft.detected_at_line": "apparaît à la ligne",
2534
+ "peft.suggested_fix": "Suggéré :",
2535
+ "peft.detected_arch": "Arch détectée",
2536
+ "peft.from_model_id": "(depuis model id",
2537
+ "peft.your_modules": "Vos target_modules",
2538
+ "peft.expected_modules": "Attendus pour cette arch",
2539
+ "peft.match_ratio": "{hits} sur {total} correspondent.",
2540
+ "peft.ratio": "ratio",
2541
+ "peft.alpha.convention": "la convention est α=2r ou α=r",
2542
+ "peft.qlora_order.detail": "prepare_model_for_kbit_training (ligne {prepare_line}) s'exécute APRÈS get_peft_model (ligne {get_peft_model_line}). Inversez l'ordre — appelez prepare D'ABORD, puis get_peft_model.",
2543
+ "peft.no_peft_calls.detail": "Aucun appel à get_peft_model / PeftModel.from_pretrained / LoraConfig détecté. Collez un snippet de setup PEFT/LoRA.",
2544
+ "peft.verdict.errors_found": "❌ Erreurs trouvées",
2545
+ "peft.verdict.warnings_only": "⚠ Avertissements",
2546
+ "peft.verdict.info_only": "ℹ Info",
2547
+ "peft.verdict.clean": "✅ Propre — aucun problème détecté",
2548
+ "peft.verdict.no_peft_calls": "ℹ Aucun appel PEFT détecté",
2549
+ "peft.verdict.empty_input": "ℹ Entrée vide",
2550
+ "peft.rule.silent_base_load.label": "Chargement silencieux du modèle de base (peft #2115)",
2551
+ "peft.rule.silent_base_load.explain": "<code>get_peft_model(base, config)</code> crée un NOUVEL adaptateur — il NE charge PAS les poids sauvegardés. L'indice de checkpoint dans votre code suggère que vous voulez REPRENDRE l'entraînement depuis un adaptateur sauvegardé, mais ce chemin de code redémarrera silencieusement à zéro et écrasera la run.",
2552
+ "peft.rule.silent_base_load.fix": "Remplacez <code>get_peft_model(base, config)</code> par <code>PeftModel.from_pretrained(base, path)</code> à la reprise. Vérifiez avec <code>model.get_layer_status()</code> après le chargement.",
2553
+ "peft.rule.qlora_order.label": "Bug d'ordre QLoRA",
2554
+ "peft.rule.qlora_order.explain": "<code>prepare_model_for_kbit_training</code> doit être appelé AVANT <code>get_peft_model</code>. Inversé, la préparation kbit ne s'applique pas aux couches LoRA et le calcul du gradient casse (loss → NaN, ou entraînement silencieux de rien).",
2555
+ "peft.rule.qlora_order.fix": "Réordonnez : <code>base = prepare_model_for_kbit_training(base)</code> puis <code>model = get_peft_model(base, config)</code>.",
2556
+ "peft.rule.target_modules_mismatch.label": "Mismatch target_modules / arch",
2557
+ "peft.rule.target_modules_mismatch.explain": "Votre liste <code>target_modules</code> ne correspond pas aux noms de modules conventionnels pour l'architecture détectée dans votre code. PEFT appliquera LoRA silencieusement à rien (ou aux mauvaises couches).",
2558
+ "peft.rule.target_modules_mismatch.fix": "Vérifiez les noms avec <code>print([n for n,_ in model.named_modules()])</code> sur le modèle de base chargé, ou utilisez la liste spécifique à l'arch montrée ci-dessus.",
2559
+ "peft.rule.alpha_not_2r.label": "lora_alpha ≠ 2r (convention)",
2560
+ "peft.rule.alpha_not_2r.explain": "La plupart des recettes LoRA publiées utilisent soit <code>α = 2r</code> (échelle effective unitaire) soit <code>α = r</code> (LR effectif réduit). Un ratio custom marche mais mérite une vérification.",
2561
+ "peft.rule.alpha_not_2r.fix": "Vérifiez le ratio contre votre recette de référence. Si intentionnel, ignorez cette découverte.",
2562
+ "peft.rule.no_peft_calls.label": "Aucun appel PEFT détecté",
2563
+ "inv.v083.peft": "<strong>🔧 PEFT Lint</strong> — attrape le chargement silencieux de <code>get_peft_model</code> sur le base (peft #2115) + ordre QLoRA + mismatch target_modules / arch.",
2564
+ "help.v083.peft.title": "🔧 Vérificateur d'anti-patterns PEFT",
2565
+ "help.v083.peft.body": "Le <code>get_peft_model(base, config)</code> de PEFT crée un NOUVEL adaptateur — il ne charge pas les poids sauvegardés depuis un chemin. Quiconque colle du code de tuto et essaie de reprendre depuis un checkpoint jette silencieusement son entraînement. peft #2115 contient le bug report canonique. Ce linter scanne votre script à la recherche du pattern + 3 problèmes liés (ordre QLoRA, mismatch target_modules/arch, ratio lora_alpha) et rapporte les découvertes avec numéros de ligne et corrections suggérées. <em>Cas d'usage</em> : avant de lancer un fine-tune LoRA de 10 heures, collez votre script — attrapez les bugs silencieux en 200ms.",
2566
+
2567
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — chaque pain documenté mappé à un mode tafagent ou outil externe curé. Ne réinventez pas — trouvez.",
2568
  "help.v081.hub.title": "🧭 Solutions Hub",
2569
  "help.v081.hub.body": "tafagent comme intégrateur, pas silo. 30+ pains à travers 7 catégories (eval reliability · diagnostics · setup · training · retrieval · multimodal · observability), chacun mappé à (a) le mode tafagent qui le résout, s'il existe, et (b) les outils externes best-of-breed que la communauté utilise déjà (RAGAS, MTEB, HELM, MCP Schema Validator, llm-stats, llguidance, GlitchMiner, etc.). La barre de recherche matche pain, scénario, et nom d'outil. <em>Cas d'usage</em> : 'j'ai le problème X — tafagent le résout-il, et sinon, qui ?'",
 
3432
  "help.v082.cot.title": "📋 JSON CoT 感知 Linter",
3433
  "help.v082.cot.body": "约束解码引擎(llguidance、Outlines、SGLang 语法)按 schema 声明的顺序输出 JSON 属性。如果你写 <code>{ answer, reasoning }</code>,模型先承诺 <code>answer</code>,CoT 就退化为事后辩护。粘贴任意 schema(或示例响应)——linter 把每个字段分类为<em>推理</em>、<em>答案</em>或<em>其他</em>,标记顺序,并输出可复制回去的重排修复。<em>用例</em>:『我的 CoT 提示在纯文本中正常但在 JSON 模式下退化』→ 运行 linter,找到颠倒的顺序,修复。",
3434
 
3435
+ // v0.8.3 — anti-bullshit pack #9: PEFT Anti-Pattern Checker
3436
+ "modes.peft": "🔧 PEFT Lint",
3437
+ "mode_desc.peft": "PEFT/LoRA 训练脚本的静态 linter。捕获基础模型的静默加载(peft #2115)、QLoRA 中 prepare/get_peft_model 顺序、target_modules/架构不匹配、以及 lora_alpha 约定。",
3438
+ "peft.title": "🔧 PEFT 反模式检查器",
3439
+ "peft.tip": "<code>get_peft_model(base, config)</code> 创建一个全新的 adapter——它不加载已保存的权重。想从 checkpoint 恢复必须调用 <code>PeftModel.from_pretrained(base, path)</code>。peft #2115 记录了基础模型静默加载的 bug。这个 linter 扫描你的训练脚本查找此模式(以及另外 3 个:QLoRA 顺序、target_modules/架构不匹配、lora_alpha 比率)。",
3440
+ "peft.desc": "<strong>不要在基础模型上烧掉 10 小时的训练。</strong> 粘贴你的 PEFT 设置代码——linter 会标记基础模型的静默加载、QLoRA 顺序 bug、target_modules/架构不匹配,以及 lora_alpha 约定。",
3441
+ "peft.input.placeholder": "from peft import LoraConfig, get_peft_model …",
3442
+ "peft.lint_btn": "🔍 Lint",
3443
+ "peft.example_bug_btn": "↳ 示例:基础模型静默加载",
3444
+ "peft.example_qlora_btn": "↳ 示例:QLoRA 顺序 bug",
3445
+ "peft.example_clean_btn": "�� 示例:干净",
3446
+ "peft.status.done": "✅ {verdict} — {n} 项发现",
3447
+ "peft.line": "第 {n} 行",
3448
+ "peft.summary": "{total} 项发现",
3449
+ "peft.attribution": "参考:",
3450
+ "peft.detected_at_line": "出现在第",
3451
+ "peft.suggested_fix": "建议:",
3452
+ "peft.detected_arch": "检测到的架构",
3453
+ "peft.from_model_id": "(来自 model id",
3454
+ "peft.your_modules": "你的 target_modules",
3455
+ "peft.expected_modules": "此架构预期",
3456
+ "peft.match_ratio": "{hits} / {total} 匹配。",
3457
+ "peft.ratio": "比率",
3458
+ "peft.alpha.convention": "约定为 α=2r 或 α=r",
3459
+ "peft.qlora_order.detail": "prepare_model_for_kbit_training(第 {prepare_line} 行)在 get_peft_model(第 {get_peft_model_line} 行)之后运行。请反转顺序——先调用 prepare,然后 get_peft_model。",
3460
+ "peft.no_peft_calls.detail": "未检测到 get_peft_model / PeftModel.from_pretrained / LoraConfig 调用。粘贴 PEFT/LoRA 设置代码片段。",
3461
+ "peft.verdict.errors_found": "❌ 发现错误",
3462
+ "peft.verdict.warnings_only": "⚠ 警告",
3463
+ "peft.verdict.info_only": "ℹ 信息",
3464
+ "peft.verdict.clean": "✅ 干净——未检测到问题",
3465
+ "peft.verdict.no_peft_calls": "ℹ 未检测到 PEFT 调用",
3466
+ "peft.verdict.empty_input": "ℹ 空输入",
3467
+ "peft.rule.silent_base_load.label": "基础模型静默加载(peft #2115)",
3468
+ "peft.rule.silent_base_load.explain": "<code>get_peft_model(base, config)</code> 创建一个新的 adapter——它不加载已保存的权重。你代码中的 checkpoint 提示表明你想从已保存的 adapter 恢复训练,但这个代码路径会悄悄从头开始并覆盖该次运行。",
3469
+ "peft.rule.silent_base_load.fix": "恢复时请用 <code>PeftModel.from_pretrained(base, path)</code> 替换 <code>get_peft_model(base, config)</code>。加载后用 <code>model.get_layer_status()</code> 验证。",
3470
+ "peft.rule.qlora_order.label": "QLoRA 顺序 bug",
3471
+ "peft.rule.qlora_order.explain": "<code>prepare_model_for_kbit_training</code> 必须在 <code>get_peft_model</code> 之前调用。反转后,kbit 准备不会应用到 LoRA 层,梯度计算会破裂(loss → NaN,或静默训练空内容)。",
3472
+ "peft.rule.qlora_order.fix": "重新排序:<code>base = prepare_model_for_kbit_training(base)</code> 然后 <code>model = get_peft_model(base, config)</code>。",
3473
+ "peft.rule.target_modules_mismatch.label": "target_modules / 架构不匹配",
3474
+ "peft.rule.target_modules_mismatch.explain": "你的 <code>target_modules</code> 列表与代码中检测到的架构的常规模块名不匹配。PEFT 会静默地把 LoRA 应用到无(或错误的层)。",
3475
+ "peft.rule.target_modules_mismatch.fix": "在加载的基础模型上用 <code>print([n for n,_ in model.named_modules()])</code> 验证模块名,或使用上面显示的特定架构列表。",
3476
+ "peft.rule.alpha_not_2r.label": "lora_alpha ≠ 2r(约定)",
3477
+ "peft.rule.alpha_not_2r.explain": "大多数已发表的 LoRA 配方使用 <code>α = 2r</code>(有效单位尺度)或 <code>α = r</code>(降低有效 LR)。自定义比率可行但值得检查。",
3478
+ "peft.rule.alpha_not_2r.fix": "对照参考配方核对比率。如果是有意的,忽略此发现。",
3479
+ "peft.rule.no_peft_calls.label": "未检测到 PEFT 调用",
3480
+ "inv.v083.peft": "<strong>🔧 PEFT Lint</strong> — 捕获 <code>get_peft_model</code> 在基础模型上的静默加载(peft #2115)+ QLoRA 顺序 + target_modules / 架构不匹配。",
3481
+ "help.v083.peft.title": "🔧 PEFT 反模式检查器",
3482
+ "help.v083.peft.body": "PEFT 的 <code>get_peft_model(base, config)</code> 创建一个新的 adapter——它不从路径加载已保存的权重。粘贴教程代码并尝试从 checkpoint 恢复的人会静默地丢掉训练。peft #2115 是规范的 bug 报告。这个 linter 扫描你的脚本查找该模式 + 3 个相关问题(QLoRA 顺序、target_modules/架构不匹配、lora_alpha 比率),并报告带行号和建议修复的发现。<em>用例</em>:在启动 10 小时的 LoRA fine-tune 之前,粘贴你的脚本——在 200ms 内捕获静默 bug。",
3483
+
3484
  "inv.v081.hub": "<strong>🧭 Solutions Hub</strong> — 每个文档化的问题都映射到一个 tafagent 模式或精选外部工具。别重复发明 — 去找。",
3485
  "help.v081.hub.title": "🧭 Solutions Hub",
3486
  "help.v081.hub.body": "tafagent 作为集成者而非孤岛。30+ 问题跨 7 类别(评估可靠性 · 诊断 · 设置 · 训练 · 检索 · 多模态 · 可观测性),每个映射到(a)解决它的 tafagent 模式(若存在),以及(b)社区已信任的最佳外部工具(RAGAS、MTEB、HELM、MCP Schema Validator、llm-stats、llguidance、GlitchMiner 等)。搜索框匹配 pain、场景和工具名称。<em>用例</em>:'我有问题 X — tafagent 解决它吗,如果不,谁解决?'",
js/main.js CHANGED
@@ -28,6 +28,7 @@ import {
28
  hubStats, getCategoryMeta,
29
  } from "./solutions_hub.js";
30
  import { lintJsonCot, reorderJsonText, classifyFieldName } from "./json_cot_linter.js";
 
31
 
32
  // Attach HF Hub search-as-you-type to all 5 model id inputs (Profile, Recipe,
33
  // Unmask, Template, Quant). Hits public huggingface.co/api/models. Idempotent.
@@ -218,6 +219,7 @@ document.addEventListener("click", (e) => {
218
  quant: "quant-section", drift: "drift-section", niah: "niah-section",
219
  saturation: "saturation-section",
220
  cot: "cot-section",
 
221
  hub: "hub-section",
222
  }[targetMode];
223
  if (sectionId) {
@@ -243,7 +245,7 @@ document.querySelectorAll(".mode-btn").forEach(btn => {
243
  "diagnose-section", "phase-section", "unmask-section",
244
  "template-section", "arena-section", "contam-section",
245
  "quant-section", "drift-section", "niah-section",
246
- "saturation-section", "cot-section", "hub-section"].forEach(id => {
247
  const el = $(id);
248
  if (el) el.style.display = "none";
249
  });
@@ -256,6 +258,7 @@ document.querySelectorAll(".mode-btn").forEach(btn => {
256
  quant: "quant-section", drift: "drift-section", niah: "niah-section",
257
  saturation: "saturation-section",
258
  cot: "cot-section",
 
259
  hub: "hub-section",
260
  };
261
  const sectionId = sectionMap[mode];
@@ -264,6 +267,7 @@ document.querySelectorAll(".mode-btn").forEach(btn => {
264
  if (mode === "phase") initPhaseDiagram();
265
  if (mode === "saturation") initSaturation();
266
  if (mode === "cot") initCot();
 
267
  if (mode === "hub") initHub();
268
  });
269
  });
@@ -3555,6 +3559,159 @@ $("cot-example-bad-btn")?.addEventListener("click", () => {
3555
  runCotLint();
3556
  });
3557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3558
  // ════════════════════════════════════════════════════════════════════
3559
  // Bootstrap
3560
  // ════════════════════════════════════════════════════════════════════
 
28
  hubStats, getCategoryMeta,
29
  } from "./solutions_hub.js";
30
  import { lintJsonCot, reorderJsonText, classifyFieldName } from "./json_cot_linter.js";
31
+ import { lintPeftCode, ARCH_TARGET_MODULES } from "./peft_anti_pattern.js";
32
 
33
  // Attach HF Hub search-as-you-type to all 5 model id inputs (Profile, Recipe,
34
  // Unmask, Template, Quant). Hits public huggingface.co/api/models. Idempotent.
 
219
  quant: "quant-section", drift: "drift-section", niah: "niah-section",
220
  saturation: "saturation-section",
221
  cot: "cot-section",
222
+ peft: "peft-section",
223
  hub: "hub-section",
224
  }[targetMode];
225
  if (sectionId) {
 
245
  "diagnose-section", "phase-section", "unmask-section",
246
  "template-section", "arena-section", "contam-section",
247
  "quant-section", "drift-section", "niah-section",
248
+ "saturation-section", "cot-section", "peft-section", "hub-section"].forEach(id => {
249
  const el = $(id);
250
  if (el) el.style.display = "none";
251
  });
 
258
  quant: "quant-section", drift: "drift-section", niah: "niah-section",
259
  saturation: "saturation-section",
260
  cot: "cot-section",
261
+ peft: "peft-section",
262
  hub: "hub-section",
263
  };
264
  const sectionId = sectionMap[mode];
 
267
  if (mode === "phase") initPhaseDiagram();
268
  if (mode === "saturation") initSaturation();
269
  if (mode === "cot") initCot();
270
+ if (mode === "peft") initPeft();
271
  if (mode === "hub") initHub();
272
  });
273
  });
 
3559
  runCotLint();
3560
  });
3561
 
3562
+ // ════════════════════════════════════════════════════════════════════
3563
+ // 🔧 PEFT Anti-Pattern Checker (v0.8.3 anti-bullshit pack #9)
3564
+ // ════════════════════════════════════════════════════════════════════
3565
+ const PEFT_SEVERITY_BG = {
3566
+ error: "#f85149",
3567
+ warning: "#d29922",
3568
+ info: "#58a6ff",
3569
+ };
3570
+ const PEFT_VERDICT_BG = {
3571
+ errors_found: "#f85149",
3572
+ warnings_only: "#d29922",
3573
+ info_only: "#58a6ff",
3574
+ clean: "#3fb950",
3575
+ no_peft_calls: "#8b949e",
3576
+ empty_input: "#8b949e",
3577
+ };
3578
+
3579
+ let __peftInited = false;
3580
+
3581
+ function initPeft() {
3582
+ if (__peftInited) return;
3583
+ __peftInited = true;
3584
+ // No-op (no async data); placeholder kept for symmetry with other modes.
3585
+ }
3586
+
3587
+ function renderPeftFinding(f) {
3588
+ const sevBg = PEFT_SEVERITY_BG[f.severity] || "#8b949e";
3589
+ const sevBadge = `<span class="badge" style="background:${sevBg};">${f.severity.toUpperCase()}</span>`;
3590
+ const ruleLabel = t(`peft.rule.${f.rule}.label`) || f.rule;
3591
+ const lineLabel = f.line != null
3592
+ ? `<span class="subtle" style="font-size:0.85em;">${tFmt("peft.line", { n: f.line }) || `line ${f.line}`}</span>`
3593
+ : "";
3594
+ const explainer = t(`peft.rule.${f.rule}.explain`) || "";
3595
+ const fixHint = t(`peft.rule.${f.rule}.fix`) || "";
3596
+ // Per-rule rendering details
3597
+ let detail = "";
3598
+ if (f.rule === "silent_base_load") {
3599
+ detail = `<p><code>${escapeHtml(f.params.checkpoint_hint)}</code> ${t("peft.detected_at_line") || "appears at line"} ${f.params.checkpoint_line}</p>
3600
+ <p><strong>${t("peft.suggested_fix") || "Suggested:"}</strong> <code>${escapeHtml(f.params.fix)}</code></p>`;
3601
+ } else if (f.rule === "qlora_order") {
3602
+ detail = `<p>${tFmt("peft.qlora_order.detail", f.params) || `prepare_model_for_kbit_training (line ${f.params.prepare_line}) runs AFTER get_peft_model (line ${f.params.get_peft_model_line}). Reverse the order.`}</p>`;
3603
+ } else if (f.rule === "target_modules_mismatch") {
3604
+ detail = `
3605
+ <p><strong>${t("peft.detected_arch") || "Detected arch"}:</strong> <code>${escapeHtml(f.params.detected_arch)}</code> ${t("peft.from_model_id") || "(from model id"} <code>${escapeHtml(f.params.detected_from)}</code>)</p>
3606
+ <p><strong>${t("peft.your_modules") || "Your target_modules"}:</strong> <code>${escapeHtml(f.params.user_modules.join(", "))}</code></p>
3607
+ <p><strong>${t("peft.expected_modules") || "Expected for this arch"}:</strong> <code>${escapeHtml(f.params.expected_modules.join(", "))}</code></p>
3608
+ <p class="subtle" style="font-size:0.85em;">${tFmt("peft.match_ratio", f.params) || `${f.params.hits} of ${f.params.total} match.`}</p>
3609
+ `;
3610
+ } else if (f.rule === "alpha_not_2r") {
3611
+ detail = `<p><code>r=${f.params.r}, lora_alpha=${f.params.lora_alpha}</code> → ${t("peft.ratio") || "ratio"} ${f.params.ratio}× (${t("peft.alpha.convention") || "convention is α=2r or α=r"})</p>`;
3612
+ } else if (f.rule === "no_peft_calls") {
3613
+ detail = `<p>${t("peft.no_peft_calls.detail") || "No get_peft_model / PeftModel.from_pretrained / LoraConfig calls detected. Paste a PEFT/LoRA setup snippet."}</p>`;
3614
+ }
3615
+ return `
3616
+ <details open class="unmask-panel" style="margin: 0.5em 0;">
3617
+ <summary class="unmask-panel-title">
3618
+ ${sevBadge} <strong>${ruleLabel}</strong> ${lineLabel}
3619
+ </summary>
3620
+ ${explainer ? `<p>${explainer}</p>` : ""}
3621
+ ${detail}
3622
+ ${fixHint ? `<p class="recipe-desc" style="margin-top:0.5em;">${fixHint}</p>` : ""}
3623
+ </details>
3624
+ `;
3625
+ }
3626
+
3627
+ function renderPeftResult(result) {
3628
+ const verdict = t(`peft.verdict.${result.code}`) || result.code;
3629
+ const verdictBg = PEFT_VERDICT_BG[result.code] || "#8b949e";
3630
+ const verdictBadge = `<span class="badge" style="background:${verdictBg};">${verdict}</span>`;
3631
+ const findings = result.findings || [];
3632
+ const findingsHtml = findings.map(renderPeftFinding).join("");
3633
+ const summary = result.summary
3634
+ ? `<p class="subtle" style="font-size:0.9em;">${tFmt("peft.summary", result.summary) || `${result.summary.total} finding(s)`}</p>`
3635
+ : "";
3636
+
3637
+ // Source attribution
3638
+ const attribution = `
3639
+ <p class="recipe-desc subtle" style="font-size:0.82em;margin-top:1em;">
3640
+ ${t("peft.attribution") || "Refs:"}
3641
+ <a href="https://github.com/huggingface/peft/issues/2115" target="_blank" rel="noopener noreferrer">peft #2115</a> ·
3642
+ <a href="https://huggingface.co/docs/peft/main/en/developer_guides/troubleshooting" target="_blank" rel="noopener noreferrer">PEFT troubleshooting</a> ·
3643
+ <a href="https://huggingface.co/docs/peft/main/en/package_reference/peft_model" target="_blank" rel="noopener noreferrer">get_layer_status / get_model_status</a>
3644
+ </p>
3645
+ `;
3646
+
3647
+ return `<div class="arena-result">
3648
+ <p style="font-size:1.1em;">${verdictBadge}</p>
3649
+ ${summary}
3650
+ ${findingsHtml}
3651
+ ${attribution}
3652
+ </div>`;
3653
+ }
3654
+
3655
+ function runPeftLint() {
3656
+ const text = $("peft-input")?.value || "";
3657
+ const result = lintPeftCode(text);
3658
+ $("peft-output").innerHTML = renderPeftResult(result);
3659
+ $("peft-status").textContent = tFmt("peft.status.done", {
3660
+ verdict: t(`peft.verdict.${result.code}`) || result.code,
3661
+ n: result.findings?.length || 0,
3662
+ });
3663
+ }
3664
+
3665
+ const PEFT_EXAMPLE_BUG = `from peft import LoraConfig, get_peft_model
3666
+ from transformers import AutoModelForCausalLM
3667
+
3668
+ base = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
3669
+ config = LoraConfig(
3670
+ r=16,
3671
+ lora_alpha=32,
3672
+ target_modules=["q_proj", "v_proj"],
3673
+ )
3674
+ model = get_peft_model(base, config)
3675
+ # resume from saved checkpoint?
3676
+ model.load_state_dict("./outputs/checkpoint-1000/adapter_model.bin")
3677
+ `;
3678
+
3679
+ const PEFT_EXAMPLE_QLORA = `from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
3680
+ from transformers import AutoModelForCausalLM, BitsAndBytesConfig
3681
+
3682
+ bnb = BitsAndBytesConfig(load_in_4bit=True)
3683
+ base = AutoModelForCausalLM.from_pretrained(
3684
+ "meta-llama/Llama-3-8B",
3685
+ quantization_config=bnb,
3686
+ )
3687
+ config = LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"])
3688
+ model = get_peft_model(base, config)
3689
+ # WRONG ORDER: prepare_model_for_kbit_training must come BEFORE get_peft_model
3690
+ model = prepare_model_for_kbit_training(model)
3691
+ `;
3692
+
3693
+ const PEFT_EXAMPLE_CLEAN = `from peft import PeftModel
3694
+ from transformers import AutoModelForCausalLM
3695
+
3696
+ base = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
3697
+ # Resume from saved adapter — correct PEFT pattern.
3698
+ model = PeftModel.from_pretrained(base, "./outputs/checkpoint-1000")
3699
+ `;
3700
+
3701
+ $("peft-lint-btn")?.addEventListener("click", runPeftLint);
3702
+ $("peft-example-bug-btn")?.addEventListener("click", () => {
3703
+ $("peft-input").value = PEFT_EXAMPLE_BUG;
3704
+ runPeftLint();
3705
+ });
3706
+ $("peft-example-qlora-btn")?.addEventListener("click", () => {
3707
+ $("peft-input").value = PEFT_EXAMPLE_QLORA;
3708
+ runPeftLint();
3709
+ });
3710
+ $("peft-example-clean-btn")?.addEventListener("click", () => {
3711
+ $("peft-input").value = PEFT_EXAMPLE_CLEAN;
3712
+ runPeftLint();
3713
+ });
3714
+
3715
  // ════════════════════════════════════════════════════════════════════
3716
  // Bootstrap
3717
  // ════════════════════════════════════════════════════════════════════
js/peft_anti_pattern.js ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // PEFT Anti-Pattern Checker (v0.8.3 anti-bullshit pack #9)
2
+ //
3
+ // Static linter for PEFT / LoRA training code paths. Targets the most
4
+ // expensive bugs the community has documented: silent base-model loads
5
+ // (peft #2115), QLoRA ordering errors, and arch/target_modules mismatch.
6
+ //
7
+ // Pain (Solutions Hub `peft_loading`): `get_peft_model()` called before
8
+ // `PeftModel.from_pretrained()` silently loads the base model and a
9
+ // FRESH adapter, ignoring the user's saved LoRA weights. Hours of
10
+ // training thrown away with no error.
11
+ //
12
+ // Source citations:
13
+ // - peft #2115 — original silent-load bug
14
+ // - https://huggingface.co/docs/peft/main/en/developer_guides/troubleshooting
15
+ // - PEFT `get_layer_status() / get_model_status()` runtime check
16
+ //
17
+ // Pure logic — no human strings. Returns codes+params; main.js does
18
+ // the i18n lookup. Same shape as json_cot_linter.js.
19
+
20
+ // =============================================================================
21
+ // Token/pattern definitions
22
+ // =============================================================================
23
+
24
+ // Rough comment + string stripping. NOT a real Python parser; we only
25
+ // need to remove obvious noise so regex matches don't fire inside
26
+ // docstrings or commented-out code. Anything still in scope after this
27
+ // is treated as "live" Python.
28
+ function stripCommentsAndStrings(code) {
29
+ // Remove triple-quoted strings (greedy match across newlines)
30
+ let s = code.replace(/"""[\s\S]*?"""/g, "");
31
+ s = s.replace(/'''[\s\S]*?'''/g, "");
32
+ // Remove single-line strings (but keep the line so line numbers stay valid)
33
+ s = s.replace(/"(?:\\.|[^"\\\n])*"/g, '""');
34
+ s = s.replace(/'(?:\\.|[^'\\\n])*'/g, "''");
35
+ // Remove `# ...` comments to end of line
36
+ s = s.replace(/#[^\n]*/g, "");
37
+ return s;
38
+ }
39
+
40
+ function findFirstMatchLine(stripped, pattern) {
41
+ const lines = stripped.split("\n");
42
+ for (let i = 0; i < lines.length; i++) {
43
+ if (pattern.test(lines[i])) return i + 1; // 1-indexed
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function findAllMatchLines(stripped, pattern) {
49
+ const out = [];
50
+ const lines = stripped.split("\n");
51
+ for (let i = 0; i < lines.length; i++) {
52
+ if (pattern.test(lines[i])) out.push(i + 1);
53
+ }
54
+ return out;
55
+ }
56
+
57
+ // Extract STRING LITERALS from the ORIGINAL code (we kept them in the
58
+ // raw text so we can scan their contents for adapter/checkpoint hints).
59
+ // Returns array of { value, line }.
60
+ function extractStringLiterals(code) {
61
+ const out = [];
62
+ const re = /(["'])((?:\\.|(?!\1)[^\\\n])*)\1/g;
63
+ const lines = code.split("\n");
64
+ let lineStart = 0;
65
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
66
+ const line = lines[lineIdx];
67
+ let m;
68
+ re.lastIndex = 0;
69
+ const lineRe = new RegExp(re.source, re.flags);
70
+ while ((m = lineRe.exec(line)) !== null) {
71
+ out.push({ value: m[2], line: lineIdx + 1 });
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+
77
+ // Extract `target_modules=[...]` literal lists. Returns array of
78
+ // { modules: [..], line }. Best-effort; only catches literal lists.
79
+ function extractTargetModules(code) {
80
+ const out = [];
81
+ const re = /target_modules\s*=\s*\[([^\]]*)\]/g;
82
+ let m;
83
+ while ((m = re.exec(code)) !== null) {
84
+ const inner = m[1];
85
+ const modules = (inner.match(/["']([^"']+)["']/g) || []).map(s => s.slice(1, -1));
86
+ // Compute line number
87
+ const before = code.slice(0, m.index);
88
+ const line = before.split("\n").length;
89
+ out.push({ modules, line });
90
+ }
91
+ return out;
92
+ }
93
+
94
+ // Extract `r=N` and `lora_alpha=N` from the same call site. Best-effort.
95
+ function extractLoraConfig(code) {
96
+ const out = [];
97
+ // Find LoraConfig(...) calls and capture the args block (single-line or balanced).
98
+ const re = /LoraConfig\s*\(([^)]*)\)/g;
99
+ let m;
100
+ while ((m = re.exec(code)) !== null) {
101
+ const args = m[1];
102
+ const r = args.match(/\br\s*=\s*(\d+)/);
103
+ const alpha = args.match(/lora_alpha\s*=\s*(\d+)/);
104
+ const before = code.slice(0, m.index);
105
+ const line = before.split("\n").length;
106
+ out.push({
107
+ r: r ? parseInt(r[1], 10) : null,
108
+ lora_alpha: alpha ? parseInt(alpha[1], 10) : null,
109
+ line,
110
+ });
111
+ }
112
+ return out;
113
+ }
114
+
115
+ // =============================================================================
116
+ // Architecture → conventional target_modules
117
+ // =============================================================================
118
+
119
+ // Mapping built from public PEFT docs + transformers configs. Conservative:
120
+ // only architectures with stable, well-documented module names. When the
121
+ // user's target_modules don't match the listed arch family, we flag it.
122
+ const ARCH_TARGET_MODULES = {
123
+ llama: ["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
124
+ mistral: ["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
125
+ qwen2: ["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
126
+ phi: ["q_proj","k_proj","v_proj","dense","fc1","fc2"],
127
+ phi3: ["qkv_proj","o_proj","gate_up_proj","down_proj"],
128
+ gemma: ["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
129
+ falcon: ["query_key_value","dense","dense_h_to_4h","dense_4h_to_h"],
130
+ bloom: ["query_key_value","dense","dense_h_to_4h","dense_4h_to_h"],
131
+ gpt2: ["c_attn","c_proj","c_fc"],
132
+ gptneox: ["query_key_value","dense","dense_h_to_4h","dense_4h_to_h"],
133
+ mpt: ["Wqkv","out_proj","up_proj","down_proj"],
134
+ };
135
+
136
+ // Token hints in HF model ids that map to the keys above. Any one of
137
+ // these matching is enough to claim "the user is targeting arch X".
138
+ const ARCH_ID_HINTS = {
139
+ llama: /\b(?:llama|llama-?[123]|tinyllama|vicuna|alpaca|deepseek|mixtral)\b/i,
140
+ mistral: /\bmistral\b/i,
141
+ qwen2: /\bqwen2?\b/i,
142
+ phi: /\bphi-?[12]\b/i,
143
+ phi3: /\bphi-?3\b/i,
144
+ gemma: /\bgemma\b/i,
145
+ falcon: /\bfalcon\b/i,
146
+ bloom: /\bbloom\b/i,
147
+ gpt2: /\bgpt-?2\b/i,
148
+ gptneox: /\b(?:gpt-?neox|pythia|dolly)\b/i,
149
+ mpt: /\bmpt\b/i,
150
+ };
151
+
152
+ function detectArch(stringLiterals) {
153
+ for (const lit of stringLiterals) {
154
+ for (const [arch, hint] of Object.entries(ARCH_ID_HINTS)) {
155
+ if (hint.test(lit.value)) {
156
+ return { arch, source: lit.value, line: lit.line };
157
+ }
158
+ }
159
+ }
160
+ return null;
161
+ }
162
+
163
+ // =============================================================================
164
+ // Heuristics for detecting "this string is a saved adapter checkpoint path"
165
+ // =============================================================================
166
+
167
+ const CHECKPOINT_HINT_RE =
168
+ /(?:adapter[_-]?(?:config|model)|adapter\.safetensors|adapter_model\.bin|peft[_-]?model|lora[_-]?weights?|checkpoint(?:[-_/]\d+)?|\boutput[_-]?dir\b|trained?[_-]?lora)/i;
169
+
170
+ function findAdapterCheckpointHint(stringLiterals) {
171
+ for (const lit of stringLiterals) {
172
+ if (CHECKPOINT_HINT_RE.test(lit.value)) return lit;
173
+ }
174
+ return null;
175
+ }
176
+
177
+ // =============================================================================
178
+ // Public entry point
179
+ // =============================================================================
180
+
181
+ const RULES = {
182
+ // Strong correctness issues — almost certainly a bug
183
+ silent_base_load: { severity: "error" },
184
+ qlora_order: { severity: "error" },
185
+ target_modules_mismatch: { severity: "warning" },
186
+ // Optional / informational
187
+ alpha_not_2r: { severity: "info" },
188
+ no_peft_calls: { severity: "info" },
189
+ };
190
+
191
+ export function lintPeftCode(text) {
192
+ if (typeof text !== "string" || !text.trim()) {
193
+ return { code: "empty_input", findings: [] };
194
+ }
195
+
196
+ const stripped = stripCommentsAndStrings(text);
197
+
198
+ // Bail if no PEFT-related calls at all — unhelpful otherwise.
199
+ const hasGetPeftModel = /\bget_peft_model\s*\(/.test(stripped);
200
+ const hasFromPretrained = /\bPeftModel\s*\.\s*from_pretrained\s*\(/.test(stripped);
201
+ const hasPrepareKbit = /\bprepare_model_for_kbit_training\s*\(/.test(stripped);
202
+ const hasLoraConfig = /\bLoraConfig\s*\(/.test(stripped);
203
+ const hasBnbConfig = /\bBitsAndBytesConfig\s*\(/.test(stripped);
204
+
205
+ if (
206
+ !hasGetPeftModel &&
207
+ !hasFromPretrained &&
208
+ !hasPrepareKbit &&
209
+ !hasLoraConfig
210
+ ) {
211
+ return {
212
+ code: "no_peft_calls",
213
+ findings: [{
214
+ rule: "no_peft_calls",
215
+ severity: "info",
216
+ line: null,
217
+ params: {},
218
+ }],
219
+ };
220
+ }
221
+
222
+ const findings = [];
223
+ const stringLiterals = extractStringLiterals(text);
224
+
225
+ // ─── Rule A: silent base-model load (peft #2115) ─────────────────────────
226
+ // Pattern: `get_peft_model(...)` is the only model-creation path AND
227
+ // there's a string literal that looks like a saved adapter path.
228
+ // Likely user wants to LOAD a saved adapter but is creating a new one.
229
+ if (hasGetPeftModel && !hasFromPretrained) {
230
+ const hint = findAdapterCheckpointHint(stringLiterals);
231
+ if (hint) {
232
+ const getPeftLine = findFirstMatchLine(stripped, /\bget_peft_model\s*\(/);
233
+ findings.push({
234
+ rule: "silent_base_load",
235
+ severity: "error",
236
+ line: getPeftLine,
237
+ params: {
238
+ checkpoint_hint: hint.value,
239
+ checkpoint_line: hint.line,
240
+ fix: `PeftModel.from_pretrained(base_model, ${JSON.stringify(hint.value)})`,
241
+ },
242
+ });
243
+ }
244
+ }
245
+
246
+ // ─── Rule B: QLoRA ordering — prepare_model_for_kbit_training AFTER get_peft_model ──
247
+ if (hasPrepareKbit && hasGetPeftModel) {
248
+ const prepLine = findFirstMatchLine(stripped, /\bprepare_model_for_kbit_training\s*\(/);
249
+ const peftLine = findFirstMatchLine(stripped, /\bget_peft_model\s*\(/);
250
+ if (prepLine !== null && peftLine !== null && prepLine > peftLine) {
251
+ findings.push({
252
+ rule: "qlora_order",
253
+ severity: "error",
254
+ line: prepLine,
255
+ params: {
256
+ prepare_line: prepLine,
257
+ get_peft_model_line: peftLine,
258
+ },
259
+ });
260
+ }
261
+ }
262
+
263
+ // ─── Rule C: target_modules / arch mismatch ─────────────────────────────
264
+ const targetModuleCalls = extractTargetModules(text);
265
+ const detectedArch = detectArch(stringLiterals);
266
+ if (targetModuleCalls.length > 0 && detectedArch !== null) {
267
+ const expected = ARCH_TARGET_MODULES[detectedArch.arch];
268
+ if (expected) {
269
+ const expectedSet = new Set(expected);
270
+ for (const tm of targetModuleCalls) {
271
+ if (tm.modules.length === 0) continue;
272
+ const hits = tm.modules.filter(m => expectedSet.has(m)).length;
273
+ const ratio = hits / tm.modules.length;
274
+ // Less than half of user's specified modules are in the expected list.
275
+ if (ratio < 0.5) {
276
+ findings.push({
277
+ rule: "target_modules_mismatch",
278
+ severity: "warning",
279
+ line: tm.line,
280
+ params: {
281
+ user_modules: tm.modules,
282
+ detected_arch: detectedArch.arch,
283
+ detected_from: detectedArch.source,
284
+ expected_modules: expected,
285
+ hits,
286
+ total: tm.modules.length,
287
+ },
288
+ });
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ // ─── Rule D: lora_alpha ≠ 2*r convention ────────────────────────────────
295
+ // Common rule of thumb: alpha = 2*r gives roughly unit-scale LoRA.
296
+ // alpha = r is also seen but reduces effective LR. Anything else is
297
+ // worth surfacing as info.
298
+ const loraCfgs = extractLoraConfig(text);
299
+ for (const cfg of loraCfgs) {
300
+ if (cfg.r != null && cfg.lora_alpha != null) {
301
+ const ratio = cfg.lora_alpha / cfg.r;
302
+ if (ratio !== 1 && ratio !== 2) {
303
+ findings.push({
304
+ rule: "alpha_not_2r",
305
+ severity: "info",
306
+ line: cfg.line,
307
+ params: {
308
+ r: cfg.r,
309
+ lora_alpha: cfg.lora_alpha,
310
+ ratio: Math.round(ratio * 100) / 100,
311
+ },
312
+ });
313
+ }
314
+ }
315
+ }
316
+
317
+ // ─── Aggregate verdict code ──────────────────────────────────────────────
318
+ let code;
319
+ if (findings.length === 0) {
320
+ code = "clean";
321
+ } else if (findings.some(f => f.severity === "error")) {
322
+ code = "errors_found";
323
+ } else if (findings.some(f => f.severity === "warning")) {
324
+ code = "warnings_only";
325
+ } else {
326
+ code = "info_only";
327
+ }
328
+ return { code, findings, summary: { total: findings.length } };
329
+ }
330
+
331
+ export { ARCH_TARGET_MODULES, ARCH_ID_HINTS };