Add or update a model preset
A preset is ground truth for the physics. Every number below changes the prediction, so
copy it from the official config.json, never from memory or a blog post. Today is later
than the assistant's training data: fetch the config.
1. Get the facts (do not skip)
- Fetch
https://huggingface.co/<org>/<model>/raw/main/config.json (WebFetch works; it is
plain JSON). For multimodal repos the text model is under text_config /
language_config — use those values. Also read https://huggingface.co/api/models/<org>/<model>
for safetensors.total (true parameter count), cardData.license, createdAt.
- If the config is not public (API-only model), do not invent dimensions. Either skip it or
add it with
specStatus: 'preview' and a specNote that names the proxy you used.
- Record the source URL — it goes in
specSourceUrl.
2. Map config → preset fields
| Preset field |
From config |
Notes |
totalParamsB |
safetensors.total / 1e9 (preferred) or the model card |
Include vision tower + MTP head if they are in the checkpoint (that is what gets downloaded/loaded). |
activeParamsB |
model card "A{n}B", or compute: total − (non-routed experts) |
Dense: omit (defaults to total). MoE: attention + shared experts + routed-active experts + embeddings/LM head. Gemma "E" models: resident > active (per-layer embeddings) → set both. |
hiddenSize |
hidden_size |
|
numLayers |
num_hidden_layers |
Text layers only. |
numHeads |
num_attention_heads |
|
numKVHeads |
num_key_value_heads |
MLA models: still record the head count; KV size comes from kvLoraRank. |
headDim |
head_dim |
Required when it differs from hidden/heads (Qwen 3.5+: 256, Gemma 3/4: 256, Muse Glimmer: 128, Nemotron: 128). The engine derives hidden/heads only as a fallback. |
intermediateSize |
intermediate_size |
Dense FFN width (MoE models: the dense-layer / shared width if present). |
moeIntermediateSize |
moe_intermediate_size |
Per-expert width; informational today, used by the HF importer's active-param math. |
isMoE, numExperts, activeExperts |
n_routed_experts/num_experts, num_experts_per_tok |
Also set routingType: 'moe'. Shared experts are not "active experts"; fold them into activeParamsB. |
attentionMechanism |
see table below |
One of standard, grouped_query, multi_query, mla, hybrid_linear, hybrid_ssm, sliding_window. |
fullAttentionLayers |
count of full_attention/global in layer_types, or num_global_layers |
Number of layers that keep a full per-token KV. |
fullAttentionInterval |
full_attention_interval |
Alternative to the count for "1 in N" hybrids (Qwen 3.5+: 4 → 1 full per 4). |
slidingWindow |
sliding_window (only if use_sliding_window is not false) |
Window for the non-full layers. |
kvLoraRank, qLoraRank, qkRopeHeadDim |
same names |
MLA (DeepSeek V3/R1, GLM-5, Kimi, Ling). |
useMTP, mtpModules |
num_nextn_predict_layers, mtp_num_hidden_layers |
Enables the mtp speculation method (the engine models the MTP head as one target-width layer re-reading the LM head per draft token); without it MTP is "not shipped" and modeled off. |
vocabSize |
vocab_size |
Sizes the LM-head read that MTP/EAGLE drafts pay per token (Qwen 3.5+: 248320, Gemma 4: 262144, Llama 4 / Muse: 202048, DeepSeek: 129280). |
contextLength |
max_position_embeddings (after rope scaling) |
Drives the context sweep range and memory warnings. |
hasVision |
vision_config present |
|
nativeBytesPerParam |
only for natively low-bit checkpoints |
gpt-oss MXFP4 ≈ 0.56–0.66, Kimi K3 MXFP4 ≈ 0.56, FP8-native Mistral = 1. Caps "fp16" sizing at what actually ships. |
label, hfId |
display name, canonical repo |
label is what users see; keep the family's naming style ("Qwen 3.8 27B", "Gemma 4 26B A4B"). |
specStatus, specSourceUrl, specNote |
'verified' when every dimension came from config.json |
The note is shown to users: say what was verified and the date. |
Choosing attentionMechanism
| Architecture |
Setting |
| Plain GQA/MHA (Llama, Mistral, Qwen 2.5/3 dense & MoE) |
grouped_query (or standard when KV heads = heads) |
| Gated DeltaNet / KDA hybrids (Qwen 3.5/3.6/3.8, Qwen3-Next/Coder-Next, Ornith, Kimi Linear) |
hybrid_linear + fullAttentionInterval or fullAttentionLayers; head_dim usually 256 |
| Mamba/SSM hybrids (Nemotron-H/3/3.5, LFM2/2.5, Jamba) |
hybrid_ssm + architectureType: 'hybrid_ssm_transformer' + fullAttentionLayers (number of attention layers) |
| MLA (DeepSeek V3/R1, GLM-4.7 Flash/5.x, Kimi K2/K3) |
mla + kvLoraRank (+ fullAttentionInterval if KDA-hybrid like Kimi K3) |
| Sliding-window mixes (Gemma 3/4, gpt-oss, Muse Glimmer, Mistral 7B) |
sliding_window + slidingWindow + fullAttentionLayers (gpt-oss alternates: half the layers are full) |
| DeepSeek V4 sparse attention |
keep the existing V4 presets' pattern (hybrid_linear + slidingWindow: 128); the DSA indexer is not modeled |
Why this matters: the engine computes KV bytes as
Σ_layers min(depth, window) × 2 × kvHeads × headDim × 2 bytes (MLA: (kvLoraRank + ropeDim) × 2), and attention FLOPs from the same layer mix. A wrong mix is a 2–10× KV error at long context.
3. Where to add it (all five, in order)
MODEL_PRESETS in engine.js (the physics engine that index.html loads before its page
script), next to its family. Key convention: family[version]_size[_aNb] → qwen3.8_27b,
gemma4_26b_a4b, ornith_1.5_397b_a17b, muse_glimmer_30b. Keys must be unique (the integrity
test fails on duplicates — later keys would silently override). npm test re-stamps the
engine.js?v=<hash> cache key in index.html and rebuilds dist/ (the SDK ships the same
catalog) — commit those too.
When the model replaces an older release, add old_key: 'new_key' to PRESET_SUPERSEDED_BY
(engine.js): the landing page then prefers the new model, the picker shows an upgrade link on the
old one, and the SDK reports supersededBy. Keep the old preset — its gold rows still calibrate.
- Picker groups: the
modelCategories map and featuredModelKeys inside the DOMContentLoaded
handler (search const modelCategories = {). Uncategorized presets fall into "Other" — don't leave
them there. Put the newest release first in its family and in "New & popular".
- Evidence rule:
MODEL_PRESET_RULES in scripts/refresh-localmaxxing.mjs — a regex on the
Hugging Face id (base model and community quants both resolve through baseModel). Without it, no
community run of the model becomes gold evidence and the planner cannot calibrate it. Put more
specific patterns first (muse-glimmer-30b-assistant before muse-glimmer-30b).
- Showcase (optional):
QUICKSTART_SHOWCASES for a landing-page row when a setup is interesting
and no measured row exists yet.
- README model list when it is a headline family.
Pruned/REAP/abliterated/"distill" derivatives are different models: give them their own preset
(see deepseek_v4_flash_reap_180b) or leave them out — mapping them onto the base preset makes real runs
"beat physics".
4. Sanity-check the physics before trusting it
Run the engine for one obvious setup and compare with the back-of-envelope:
node -e "import('file:///'+process.cwd().replace(/\\\\/g,'/')+'/tests/load-index-app.mjs').then(({loadApp})=>{const app=loadApp();const H=app.hooks;
H.setDevices([{id:1,template:'RTX 5090',...JSON.parse(JSON.stringify(H.DEVICE_TEMPLATES['RTX 5090'])),name:'RTX 5090'}]);
app.applyPreset('<key>');app.setValue('quantizationType','q4');app.setValue('runtimeFramework','llama_cpp');app.setValue('parallelismStrategy','pipeline');
app.setValue('batchSize',1);app.setValue('promptTokens',2048);app.setValue('outputTokens',256);app.setValue('seqLength',2304);
const m=H.calculateMetrics()[0];console.log({decode:m.decodeTokensPerSecond,prefill:m.prefillTokensPerSecond,weightsGB:m.activeWeightSizeGB,kvGB:m.decodeKvCacheGB,b:m.decodeTimeBreakdown});})"
Expectations: decode ≈ 1 / (activeBytes/(BW×0.78) + layers×~45µs×(2 for GDN)×(1.4 MoE) + 0.2 ms)
(active bytes use the quant format when one is set — Q4_K_M is 4.9 bits/weight, UD-IQ4_XS 4.0,
NVFP4 4.5, AWQ/GPTQ 4.3, Q8_0 8.5 — times a 1.16 storage overhead for k-quants; see QUANT_FORMATS);
KV per token ≈ fullLayers × 2 × kvHeads × headDim × 2 bytes (e.g. Qwen 3.8 27B: 16 × 2 × 4 × 256 × 2 = 64 KB).
If a community number exists (Localmaxxing, r/LocalLLaMA, the model card), the engine should be within
~1.5× of it at the same depth; if not, the preset is wrong before the physics is.
5. Verify
npm test — duplicate keys, preset metadata tests, anchors, XSS, waterfall consistency.
npm run refresh:localmaxxing — pulls the latest runs (needs network); then npm run audit:gold
and look for the new preset in the worst-outlier list. A row that beats the physical roofline means
the preset (bytes, layer mix, KV) or the row's semantics is wrong — never an efficiency constant.
- If the model is important, add an anchor to
tests/integrity.test.mjs ("decode anchors hold…")
with the measured number and a ±35% band, citing where the measurement came from, and a KNOWN
band in tests/sanity-matrix.test.mjs (wide tolerance: these catch unrealistic outliers, not drift).
Add the preset to the matrix's MODELS list so every physics invariant is exercised on it.
- Browser check: load the preset in the planner; the execution map shows the head dim and
"n Q / m KV heads"; the "How it scales" decode curve should bend where the KV cache becomes
comparable to the weight bytes.
6. Commit
One commit per family is fine. Mention config verification in the message
(specStatus: 'verified' presets are trusted by users).
1---2name: add-model3description: Add or update an LLM preset in MODEL_PRESETS (engine.js) from its official config.json so the planner's physics (KV cache, FLOPs, fixed per-layer overhead) are right for it; wires the picker, the evidence rules, and the tests. Use whenever a user asks to add/support/update a model (Qwen, Gemma, DeepSeek, Llama, Mistral, Kimi, GLM, MiniMax, Nemotron, Muse, Granite, LFM…) or to refresh a preset's specs.4---56# Add or update a model preset78A preset is ground truth for the physics. Every number below changes the prediction, so9copy it from the official `config.json`, never from memory or a blog post. Today is later10than the assistant's training data: **fetch the config**.1112## 1. Get the facts (do not skip)13141. Fetch `https://huggingface.co/<org>/<model>/raw/main/config.json` (WebFetch works; it is15 plain JSON). For multimodal repos the text model is under `text_config` /16 `language_config` — use those values. Also read `https://huggingface.co/api/models/<org>/<model>`17 for `safetensors.total` (true parameter count), `cardData.license`, `createdAt`.182. If the config is not public (API-only model), do not invent dimensions. Either skip it or19 add it with `specStatus: 'preview'` and a `specNote` that names the proxy you used.203. Record the source URL — it goes in `specSourceUrl`.2122## 2. Map config → preset fields2324| Preset field | From config | Notes |25|---|---|---|26| `totalParamsB` | `safetensors.total / 1e9` (preferred) or the model card | Include vision tower + MTP head if they are in the checkpoint (that is what gets downloaded/loaded). |27| `activeParamsB` | model card "A{n}B", or compute: total − (non-routed experts) | Dense: omit (defaults to total). MoE: attention + shared experts + routed-active experts + embeddings/LM head. Gemma "E" models: resident > active (per-layer embeddings) → set both. |28| `hiddenSize` | `hidden_size` | |29| `numLayers` | `num_hidden_layers` | Text layers only. |30| `numHeads` | `num_attention_heads` | |31| `numKVHeads` | `num_key_value_heads` | MLA models: still record the head count; KV size comes from `kvLoraRank`. |32| `headDim` | `head_dim` | **Required when it differs from hidden/heads** (Qwen 3.5+: 256, Gemma 3/4: 256, Muse Glimmer: 128, Nemotron: 128). The engine derives hidden/heads only as a fallback. |33| `intermediateSize` | `intermediate_size` | Dense FFN width (MoE models: the dense-layer / shared width if present). |34| `moeIntermediateSize` | `moe_intermediate_size` | Per-expert width; informational today, used by the HF importer's active-param math. |35| `isMoE`, `numExperts`, `activeExperts` | `n_routed_experts`/`num_experts`, `num_experts_per_tok` | Also set `routingType: 'moe'`. Shared experts are not "active experts"; fold them into `activeParamsB`. |36| `attentionMechanism` | see table below | One of `standard`, `grouped_query`, `multi_query`, `mla`, `hybrid_linear`, `hybrid_ssm`, `sliding_window`. |37| `fullAttentionLayers` | count of `full_attention`/`global` in `layer_types`, or `num_global_layers` | Number of layers that keep a full per-token KV. |38| `fullAttentionInterval` | `full_attention_interval` | Alternative to the count for "1 in N" hybrids (Qwen 3.5+: 4 → 1 full per 4). |39| `slidingWindow` | `sliding_window` (only if `use_sliding_window` is not false) | Window for the non-full layers. |40| `kvLoraRank`, `qLoraRank`, `qkRopeHeadDim` | same names | MLA (DeepSeek V3/R1, GLM-5, Kimi, Ling). |41| `useMTP`, `mtpModules` | `num_nextn_predict_layers`, `mtp_num_hidden_layers` | Enables the `mtp` speculation method (the engine models the MTP head as one target-width layer re-reading the LM head per draft token); without it MTP is "not shipped" and modeled off. |42| `vocabSize` | `vocab_size` | Sizes the LM-head read that MTP/EAGLE drafts pay per token (Qwen 3.5+: 248320, Gemma 4: 262144, Llama 4 / Muse: 202048, DeepSeek: 129280). |43| `contextLength` | `max_position_embeddings` (after rope scaling) | Drives the context sweep range and memory warnings. |44| `hasVision` | `vision_config` present | |45| `nativeBytesPerParam` | only for natively low-bit checkpoints | gpt-oss MXFP4 ≈ 0.56–0.66, Kimi K3 MXFP4 ≈ 0.56, FP8-native Mistral = 1. Caps "fp16" sizing at what actually ships. |46| `label`, `hfId` | display name, canonical repo | `label` is what users see; keep the family's naming style ("Qwen 3.8 27B", "Gemma 4 26B A4B"). |47| `specStatus`, `specSourceUrl`, `specNote` | `'verified'` when every dimension came from config.json | The note is shown to users: say what was verified and the date. |4849### Choosing `attentionMechanism`5051| Architecture | Setting |52|---|---|53| Plain GQA/MHA (Llama, Mistral, Qwen 2.5/3 dense & MoE) | `grouped_query` (or `standard` when KV heads = heads) |54| Gated DeltaNet / KDA hybrids (Qwen 3.5/3.6/3.8, Qwen3-Next/Coder-Next, Ornith, Kimi Linear) | `hybrid_linear` + `fullAttentionInterval` **or** `fullAttentionLayers`; head_dim usually 256 |55| Mamba/SSM hybrids (Nemotron-H/3/3.5, LFM2/2.5, Jamba) | `hybrid_ssm` + `architectureType: 'hybrid_ssm_transformer'` + `fullAttentionLayers` (number of attention layers) |56| MLA (DeepSeek V3/R1, GLM-4.7 Flash/5.x, Kimi K2/K3) | `mla` + `kvLoraRank` (+ `fullAttentionInterval` if KDA-hybrid like Kimi K3) |57| Sliding-window mixes (Gemma 3/4, gpt-oss, Muse Glimmer, Mistral 7B) | `sliding_window` + `slidingWindow` + `fullAttentionLayers` (gpt-oss alternates: half the layers are full) |58| DeepSeek V4 sparse attention | keep the existing V4 presets' pattern (`hybrid_linear` + `slidingWindow: 128`); the DSA indexer is not modeled |5960Why this matters: the engine computes KV bytes as61`Σ_layers min(depth, window) × 2 × kvHeads × headDim × 2 bytes` (MLA: `(kvLoraRank + ropeDim) × 2`), and attention FLOPs from the same layer mix. A wrong mix is a 2–10× KV error at long context.6263## 3. Where to add it (all five, in order)64651. **`MODEL_PRESETS`** in `engine.js` (the physics engine that `index.html` loads before its page66 script), next to its family. Key convention: `family[version]_size[_aNb]` → `qwen3.8_27b`,67 `gemma4_26b_a4b`, `ornith_1.5_397b_a17b`, `muse_glimmer_30b`. Keys must be unique (the integrity68 test fails on duplicates — later keys would silently override). `npm test` re-stamps the69 `engine.js?v=<hash>` cache key in `index.html` and rebuilds `dist/` (the SDK ships the same70 catalog) — commit those too.71 When the model replaces an older release, add `old_key: 'new_key'` to `PRESET_SUPERSEDED_BY`72 (engine.js): the landing page then prefers the new model, the picker shows an upgrade link on the73 old one, and the SDK reports `supersededBy`. Keep the old preset — its gold rows still calibrate.742. **Picker groups**: the `modelCategories` map and `featuredModelKeys` inside the `DOMContentLoaded`75 handler (search `const modelCategories = {`). Uncategorized presets fall into "Other" — don't leave76 them there. Put the newest release first in its family and in "New & popular".773. **Evidence rule**: `MODEL_PRESET_RULES` in `scripts/refresh-localmaxxing.mjs` — a regex on the78 Hugging Face id (base model and community quants both resolve through `baseModel`). Without it, no79 community run of the model becomes gold evidence and the planner cannot calibrate it. Put more80 specific patterns first (`muse-glimmer-30b-assistant` before `muse-glimmer-30b`).814. **Showcase (optional)**: `QUICKSTART_SHOWCASES` for a landing-page row when a setup is interesting82 and no measured row exists yet.835. **README model list** when it is a headline family.8485Pruned/REAP/abliterated/"distill" derivatives are different models: give them their own preset86(see `deepseek_v4_flash_reap_180b`) or leave them out — mapping them onto the base preset makes real runs87"beat physics".8889## 4. Sanity-check the physics before trusting it9091Run the engine for one obvious setup and compare with the back-of-envelope:9293```94node -e "import('file:///'+process.cwd().replace(/\\\\/g,'/')+'/tests/load-index-app.mjs').then(({loadApp})=>{const app=loadApp();const H=app.hooks;95H.setDevices([{id:1,template:'RTX 5090',...JSON.parse(JSON.stringify(H.DEVICE_TEMPLATES['RTX 5090'])),name:'RTX 5090'}]);96app.applyPreset('<key>');app.setValue('quantizationType','q4');app.setValue('runtimeFramework','llama_cpp');app.setValue('parallelismStrategy','pipeline');97app.setValue('batchSize',1);app.setValue('promptTokens',2048);app.setValue('outputTokens',256);app.setValue('seqLength',2304);98const m=H.calculateMetrics()[0];console.log({decode:m.decodeTokensPerSecond,prefill:m.prefillTokensPerSecond,weightsGB:m.activeWeightSizeGB,kvGB:m.decodeKvCacheGB,b:m.decodeTimeBreakdown});})"99```100101Expectations: decode ≈ `1 / (activeBytes/(BW×0.78) + layers×~45µs×(2 for GDN)×(1.4 MoE) + 0.2 ms)`102(active bytes use the quant *format* when one is set — Q4_K_M is 4.9 bits/weight, UD-IQ4_XS 4.0,103NVFP4 4.5, AWQ/GPTQ 4.3, Q8_0 8.5 — times a 1.16 storage overhead for k-quants; see `QUANT_FORMATS`);104KV per token ≈ `fullLayers × 2 × kvHeads × headDim × 2 bytes` (e.g. Qwen 3.8 27B: 16 × 2 × 4 × 256 × 2 = 64 KB).105If a community number exists (Localmaxxing, r/LocalLLaMA, the model card), the engine should be within106~1.5× of it at the same depth; if not, the preset is wrong before the physics is.107108## 5. Verify109110- `npm test` — duplicate keys, preset metadata tests, anchors, XSS, waterfall consistency.111- `npm run refresh:localmaxxing` — pulls the latest runs (needs network); then `npm run audit:gold`112 and look for the new preset in the worst-outlier list. A row that beats the physical roofline means113 the preset (bytes, layer mix, KV) or the row's semantics is wrong — never an efficiency constant.114- If the model is important, add an anchor to `tests/integrity.test.mjs` ("decode anchors hold…")115 with the measured number and a ±35% band, citing where the measurement came from, and a `KNOWN`116 band in `tests/sanity-matrix.test.mjs` (wide tolerance: these catch unrealistic outliers, not drift).117 Add the preset to the matrix's `MODELS` list so every physics invariant is exercised on it.118- Browser check: load the preset in the planner; the execution map shows the head dim and119 "n Q / m KV heads"; the "How it scales" decode curve should bend where the KV cache becomes120 comparable to the weight bytes.121122## 6. Commit123124One commit per family is fine. Mention config verification in the message125(`specStatus: 'verified'` presets are trusted by users).