Spaces:
Running
Running
Add conditional full-attention KV comparison
#8
by alphabell-research - opened
- README.md +2 -0
- architecture/comparison.css +25 -2
- architecture/comparison.js +40 -1
- architecture/index.html +17 -2
- architecture/kv-cache.js +22 -0
- tests/architecture.test.cjs +19 -2
README.md
CHANGED
|
@@ -41,6 +41,8 @@ Explore two different architectures with real Q8_0 tensor captures: Flint's spar
|
|
| 41 |
|
| 42 |
[**Explore the layer map**](https://alphabell-model-observatory.static.hf.space/architecture/index.html) to compare Nanbeige4.3-33B's 52 full-attention layers with the Qwen3.8-27B text decoder's 48 recurrent linear and 16 full-attention layers. This comparison uses published configurations; the measured captures in this Space are Flint and Qwen.
|
| 43 |
|
|
|
|
|
|
|
| 44 |
[Read the architecture explainer](https://huggingface.co/blog/alphabell/nanbeige-qwen-layer-map) for pinned sources and a matched runtime test that could measure how context length affects each model.
|
| 45 |
|
| 46 |
## Five measured experiments
|
|
|
|
| 41 |
|
| 42 |
[**Explore the layer map**](https://alphabell-model-observatory.static.hf.space/architecture/index.html) to compare Nanbeige4.3-33B's 52 full-attention layers with the Qwen3.8-27B text decoder's 48 recurrent linear and 16 full-attention layers. This comparison uses published configurations; the measured captures in this Space are Flint and Qwen.
|
| 43 |
|
| 44 |
+
The page also calculates raw f16 full-attention K/V storage across context lengths if a runtime enables caching. Nanbeige's released config sets `use_cache: false`, so this is a conditional calculation rather than measured default memory use.
|
| 45 |
+
|
| 46 |
[Read the architecture explainer](https://huggingface.co/blog/alphabell/nanbeige-qwen-layer-map) for pinned sources and a matched runtime test that could measure how context length affects each model.
|
| 47 |
|
| 48 |
## Five measured experiments
|
architecture/comparison.css
CHANGED
|
@@ -24,7 +24,7 @@ h1 em{font-style:normal;color:var(--green)}
|
|
| 24 |
.scope{display:flex;align-items:flex-start;gap:10px;max-width:830px;padding:14px 17px;background:#edf3ec;border:1px solid #d8e6d9;border-radius:9px;color:#4b6658;font-size:12px;line-height:1.5}
|
| 25 |
.scope strong{white-space:nowrap;color:var(--green)}
|
| 26 |
.scope-dot{flex:none;margin-top:5px;width:7px;height:7px;border-radius:50%;background:var(--green)}
|
| 27 |
-
section.overview,section.layers,section.source{border-top:1px solid var(--line);padding-top:38px;margin-top:18px}
|
| 28 |
.section-heading{margin-bottom:24px}
|
| 29 |
.section-heading .eyebrow{margin-bottom:8px}
|
| 30 |
.section-heading h2{font-size:clamp(27px,3.2vw,37px);letter-spacing:-.045em;margin-bottom:10px}
|
|
@@ -49,6 +49,25 @@ section.overview,section.layers,section.source{border-top:1px solid var(--line);
|
|
| 49 |
.spec-table thead th{color:var(--muted);font-size:10px;letter-spacing:.1em;text-transform:uppercase}
|
| 50 |
.spec-table tbody th{font-weight:550;width:42%}
|
| 51 |
.spec-table tbody td{font-variant-numeric:tabular-nums;font-weight:650}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
.legend{display:flex;gap:24px;flex-wrap:wrap;font-size:12px;font-weight:650;margin-bottom:16px}
|
| 53 |
.legend span{display:inline-flex;align-items:center;gap:8px}
|
| 54 |
.swatch{width:12px;height:12px;border-radius:3px;display:inline-block}
|
|
@@ -90,7 +109,7 @@ footer a{color:var(--green);font-weight:650;text-decoration:none}
|
|
| 90 |
.scope{display:grid;grid-template-columns:8px 1fr;gap:3px 10px}
|
| 91 |
.scope span:last-child{grid-column:2}
|
| 92 |
.scope-dot{margin-top:5px}
|
| 93 |
-
.overview-grid,.map-pair{grid-template-columns:1fr}
|
| 94 |
.model-card{min-height:220px}
|
| 95 |
.inspect dl{grid-template-columns:repeat(2,minmax(0,1fr))}
|
| 96 |
}
|
|
@@ -113,6 +132,10 @@ footer a{color:var(--green);font-weight:650;text-decoration:none}
|
|
| 113 |
.inspect dl{gap:7px}
|
| 114 |
.inspect dl>div{padding:10px}
|
| 115 |
.inspect dd{font-size:13px}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
footer{display:block;padding-left:16px;padding-right:16px}
|
| 117 |
footer a{display:block;margin-top:8px}
|
| 118 |
}
|
|
|
|
| 24 |
.scope{display:flex;align-items:flex-start;gap:10px;max-width:830px;padding:14px 17px;background:#edf3ec;border:1px solid #d8e6d9;border-radius:9px;color:#4b6658;font-size:12px;line-height:1.5}
|
| 25 |
.scope strong{white-space:nowrap;color:var(--green)}
|
| 26 |
.scope-dot{flex:none;margin-top:5px;width:7px;height:7px;border-radius:50%;background:var(--green)}
|
| 27 |
+
section.overview,section.kv,section.layers,section.source{border-top:1px solid var(--line);padding-top:38px;margin-top:18px}
|
| 28 |
.section-heading{margin-bottom:24px}
|
| 29 |
.section-heading .eyebrow{margin-bottom:8px}
|
| 30 |
.section-heading h2{font-size:clamp(27px,3.2vw,37px);letter-spacing:-.045em;margin-bottom:10px}
|
|
|
|
| 49 |
.spec-table thead th{color:var(--muted);font-size:10px;letter-spacing:.1em;text-transform:uppercase}
|
| 50 |
.spec-table tbody th{font-weight:550;width:42%}
|
| 51 |
.spec-table tbody td{font-variant-numeric:tabular-nums;font-weight:650}
|
| 52 |
+
.kv-panel{background:#fff;border:1px solid var(--line);border-radius:12px;padding:24px}
|
| 53 |
+
.kv-control-heading{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:14px}
|
| 54 |
+
.kv-control-heading label{font-weight:700;font-size:14px}
|
| 55 |
+
.kv-control-heading output{font-weight:750;font-size:18px;font-variant-numeric:tabular-nums;color:var(--green)}
|
| 56 |
+
.kv-control input{display:block;width:100%;accent-color:var(--green);cursor:pointer}
|
| 57 |
+
.kv-endpoints{display:flex;justify-content:space-between;color:var(--muted);font-size:11px;margin-top:6px}
|
| 58 |
+
.kv-share{font-size:11px;color:var(--muted);margin:8px 0 0;text-align:right}
|
| 59 |
+
.kv-results{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:28px}
|
| 60 |
+
.kv-result{min-width:0;border:1px solid var(--line);border-top:4px solid var(--green);border-radius:10px;background:#fafcf9;padding:19px 21px;display:flex;flex-direction:column;gap:4px}
|
| 61 |
+
.kv-result.qwen-kv{border-top-color:var(--blue);background:#f9fcfd}
|
| 62 |
+
.kv-result p{font-size:12px;font-weight:700;color:var(--muted);margin:0 0 5px}
|
| 63 |
+
.kv-result strong{font-size:clamp(31px,4vw,45px);letter-spacing:-.055em;line-height:1.1;font-variant-numeric:tabular-nums}
|
| 64 |
+
.kv-result span{font-size:15px;font-weight:650;font-variant-numeric:tabular-nums}
|
| 65 |
+
.kv-result small{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums;margin-top:7px}
|
| 66 |
+
.kv-ratio{font-size:13px;font-weight:750;color:var(--green);margin:19px 0 0}
|
| 67 |
+
.kv-formula{font-size:12px;font-weight:600;color:#435e53;margin:13px 0 0;line-height:1.5}
|
| 68 |
+
.kv-scope{font-size:12px;line-height:1.65;color:#526a5f;margin:12px 0 0;max-width:880px}
|
| 69 |
+
.kv-scope code{background:#e9efe7;padding:2px 4px;border-radius:3px}
|
| 70 |
+
.kv-scope a{color:var(--green);font-weight:700}
|
| 71 |
.legend{display:flex;gap:24px;flex-wrap:wrap;font-size:12px;font-weight:650;margin-bottom:16px}
|
| 72 |
.legend span{display:inline-flex;align-items:center;gap:8px}
|
| 73 |
.swatch{width:12px;height:12px;border-radius:3px;display:inline-block}
|
|
|
|
| 109 |
.scope{display:grid;grid-template-columns:8px 1fr;gap:3px 10px}
|
| 110 |
.scope span:last-child{grid-column:2}
|
| 111 |
.scope-dot{margin-top:5px}
|
| 112 |
+
.overview-grid,.map-pair,.kv-results{grid-template-columns:1fr}
|
| 113 |
.model-card{min-height:220px}
|
| 114 |
.inspect dl{grid-template-columns:repeat(2,minmax(0,1fr))}
|
| 115 |
}
|
|
|
|
| 132 |
.inspect dl{gap:7px}
|
| 133 |
.inspect dl>div{padding:10px}
|
| 134 |
.inspect dd{font-size:13px}
|
| 135 |
+
.kv-panel{padding:16px}
|
| 136 |
+
.kv-control-heading{display:block}
|
| 137 |
+
.kv-control-heading output{display:block;margin-top:4px}
|
| 138 |
+
.kv-result{padding:16px}
|
| 139 |
footer{display:block;padding-left:16px;padding-right:16px}
|
| 140 |
footer a{display:block;margin-top:8px}
|
| 141 |
}
|
architecture/comparison.js
CHANGED
|
@@ -9,6 +9,7 @@ const fmt = value => new Intl.NumberFormat('en-US').format(value);
|
|
| 9 |
const byId = id => document.getElementById(id);
|
| 10 |
const configs = {};
|
| 11 |
let selected = { model: 'qwen', layer: 3 };
|
|
|
|
| 12 |
|
| 13 |
function attentionType(model, layer) {
|
| 14 |
if (model === 'nanbeige') return 'full';
|
|
@@ -35,7 +36,7 @@ function renderSpecs() {
|
|
| 35 |
byId('qwen-layer-count').textContent = `${linear} + ${full}`;
|
| 36 |
const rows = [
|
| 37 |
['Decoder layers', fmt(n.num_hidden_layers), fmt(q.num_hidden_layers)],
|
| 38 |
-
['Attention pattern',
|
| 39 |
['Hidden width', fmt(n.hidden_size), fmt(q.hidden_size)],
|
| 40 |
['Dense feed-forward width', fmt(n.intermediate_size), fmt(q.intermediate_size)],
|
| 41 |
['Full-attention query heads', fmt(n.num_attention_heads), fmt(q.num_attention_heads)],
|
|
@@ -46,6 +47,42 @@ function renderSpecs() {
|
|
| 46 |
byId('spec-body').replaceChildren(...rows.map(row => specRow(...row)));
|
| 47 |
}
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
function fact(label, value) {
|
| 50 |
const group = document.createElement('div');
|
| 51 |
const dt = document.createElement('dt');
|
|
@@ -110,12 +147,14 @@ async function init() {
|
|
| 110 |
for (const [model, config] of entries) configs[model] = config;
|
| 111 |
if (configs.nanbeige.num_hidden_layers !== 52 || configs.qwen.layer_types.length !== configs.qwen.num_hidden_layers) throw new Error('Unexpected decoder configuration');
|
| 112 |
renderSpecs();
|
|
|
|
| 113 |
renderMap('nanbeige');
|
| 114 |
renderMap('qwen');
|
| 115 |
renderSelection();
|
| 116 |
} catch (error) {
|
| 117 |
byId('inspect-title').textContent = 'The comparison could not open';
|
| 118 |
byId('inspect-description').textContent = 'Reload this page to try the model configurations again.';
|
|
|
|
| 119 |
console.error('Architecture comparison failed', error);
|
| 120 |
}
|
| 121 |
}
|
|
|
|
| 9 |
const byId = id => document.getElementById(id);
|
| 10 |
const configs = {};
|
| 11 |
let selected = { model: 'qwen', layer: 3 };
|
| 12 |
+
let kvLengths = [];
|
| 13 |
|
| 14 |
function attentionType(model, layer) {
|
| 15 |
if (model === 'nanbeige') return 'full';
|
|
|
|
| 36 |
byId('qwen-layer-count').textContent = `${linear} + ${full}`;
|
| 37 |
const rows = [
|
| 38 |
['Decoder layers', fmt(n.num_hidden_layers), fmt(q.num_hidden_layers)],
|
| 39 |
+
['Attention pattern', `${n.num_hidden_layers} full`, `${linear} recurrent linear / ${full} full`],
|
| 40 |
['Hidden width', fmt(n.hidden_size), fmt(q.hidden_size)],
|
| 41 |
['Dense feed-forward width', fmt(n.intermediate_size), fmt(q.intermediate_size)],
|
| 42 |
['Full-attention query heads', fmt(n.num_attention_heads), fmt(q.num_attention_heads)],
|
|
|
|
| 47 |
byId('spec-body').replaceChildren(...rows.map(row => specRow(...row)));
|
| 48 |
}
|
| 49 |
|
| 50 |
+
function renderKv() {
|
| 51 |
+
const control = byId('kv-step');
|
| 52 |
+
const tokens = kvLengths[Number(control.value)];
|
| 53 |
+
control.setAttribute('aria-valuetext', `${fmt(tokens)} tokens`);
|
| 54 |
+
byId('kv-context-label').textContent = `${fmt(tokens)} tokens`;
|
| 55 |
+
const values = {};
|
| 56 |
+
for (const [model, prefix] of [['nanbeige', 'nan'], ['qwen', 'qwen']]) {
|
| 57 |
+
const bytes = window.kvCache.fullAttentionKvBytes(configs[model], tokens);
|
| 58 |
+
const perToken = window.kvCache.fullAttentionKvBytes(configs[model], 1);
|
| 59 |
+
values[model] = bytes;
|
| 60 |
+
byId(`kv-${prefix}-gb`).textContent = `${(bytes / 1e9).toFixed(2)} GB`;
|
| 61 |
+
byId(`kv-${prefix}-gib`).textContent = `${(bytes / 2 ** 30).toFixed(2)} GiB`;
|
| 62 |
+
byId(`kv-${prefix}-per-token`).textContent = `${fmt(perToken / 1024)} KiB per token`;
|
| 63 |
+
}
|
| 64 |
+
byId('kv-ratio').textContent = `${(values.nanbeige / values.qwen).toFixed(2)}× Nanbeige-to-Qwen ratio for this calculated component`;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
function initKv() {
|
| 68 |
+
if (!window.kvCache || configs.nanbeige.use_cache !== false) throw new Error('Unexpected KV source configuration');
|
| 69 |
+
const max = Math.min(configs.nanbeige.max_position_embeddings, configs.qwen.max_position_embeddings);
|
| 70 |
+
kvLengths = [2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144].filter(tokens => tokens <= max);
|
| 71 |
+
if (!kvLengths.length) throw new Error('No common context length');
|
| 72 |
+
const control = byId('kv-step');
|
| 73 |
+
control.max = String(kvLengths.length - 1);
|
| 74 |
+
const requested = Number(new URLSearchParams(window.location.search).get('tokens'));
|
| 75 |
+
const requestedIndex = kvLengths.indexOf(requested);
|
| 76 |
+
control.value = String(requestedIndex >= 0 ? requestedIndex : Math.min(4, kvLengths.length - 1));
|
| 77 |
+
control.addEventListener('input', () => {
|
| 78 |
+
renderKv();
|
| 79 |
+
const url = new URL(window.location.href);
|
| 80 |
+
url.searchParams.set('tokens', String(kvLengths[Number(control.value)]));
|
| 81 |
+
window.history.replaceState(null, '', url);
|
| 82 |
+
});
|
| 83 |
+
renderKv();
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
function fact(label, value) {
|
| 87 |
const group = document.createElement('div');
|
| 88 |
const dt = document.createElement('dt');
|
|
|
|
| 147 |
for (const [model, config] of entries) configs[model] = config;
|
| 148 |
if (configs.nanbeige.num_hidden_layers !== 52 || configs.qwen.layer_types.length !== configs.qwen.num_hidden_layers) throw new Error('Unexpected decoder configuration');
|
| 149 |
renderSpecs();
|
| 150 |
+
initKv();
|
| 151 |
renderMap('nanbeige');
|
| 152 |
renderMap('qwen');
|
| 153 |
renderSelection();
|
| 154 |
} catch (error) {
|
| 155 |
byId('inspect-title').textContent = 'The comparison could not open';
|
| 156 |
byId('inspect-description').textContent = 'Reload this page to try the model configurations again.';
|
| 157 |
+
byId('kv-context-label').textContent = 'Calculation unavailable';
|
| 158 |
console.error('Architecture comparison failed', error);
|
| 159 |
}
|
| 160 |
}
|
architecture/index.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
<title>Nanbeige and Qwen architecture | alphaBell</title>
|
| 9 |
<link rel="icon" href="../favicon.svg">
|
| 10 |
<link rel="stylesheet" href="comparison.css">
|
|
|
|
| 11 |
<script src="comparison.js" defer></script>
|
| 12 |
</head>
|
| 13 |
<body>
|
|
@@ -17,7 +18,7 @@
|
|
| 17 |
</header>
|
| 18 |
<main>
|
| 19 |
<section class="intro" aria-labelledby="page-title">
|
| 20 |
-
<p class="eyebrow">Architecture comparison <span>01 /
|
| 21 |
<h1 id="page-title">Two dense decoders.<br><em>Different paths through a token.</em></h1>
|
| 22 |
<p class="lead">Nanbeige repeats full attention in 52 layers. Qwen alternates recurrent linear attention with full attention across 64 text-decoder layers. Explore the published configuration, one layer at a time.</p>
|
| 23 |
<div class="scope"><span class="scope-dot" aria-hidden="true"></span><strong>Configuration view</strong><span>Layer types and dimensions come from pinned model configs. Nanbeige weights, activations and routing were not measured here.</span></div>
|
|
@@ -40,8 +41,22 @@
|
|
| 40 |
<div class="spec-wrap"><table class="spec-table"><caption>Published decoder configuration</caption><thead><tr><th scope="col">Configuration</th><th scope="col">Nanbeige</th><th scope="col">Qwen text decoder</th></tr></thead><tbody id="spec-body"></tbody></table></div>
|
| 41 |
</section>
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
<section class="layers" aria-labelledby="layers-title">
|
| 44 |
-
<div class="section-heading"><p class="eyebrow">
|
| 45 |
<div class="legend" aria-label="Layer colors"><span><i class="swatch full"></i>Full attention</span><span><i class="swatch linear"></i>Recurrent linear attention</span></div>
|
| 46 |
<div class="map-pair">
|
| 47 |
<div class="map-card"><div class="map-title"><h3>Nanbeige</h3><span>52 layers</span></div><div class="layer-grid" id="nanbeige-grid" role="group" aria-label="Nanbeige layers"></div></div>
|
|
|
|
| 8 |
<title>Nanbeige and Qwen architecture | alphaBell</title>
|
| 9 |
<link rel="icon" href="../favicon.svg">
|
| 10 |
<link rel="stylesheet" href="comparison.css">
|
| 11 |
+
<script src="kv-cache.js" defer></script>
|
| 12 |
<script src="comparison.js" defer></script>
|
| 13 |
</head>
|
| 14 |
<body>
|
|
|
|
| 18 |
</header>
|
| 19 |
<main>
|
| 20 |
<section class="intro" aria-labelledby="page-title">
|
| 21 |
+
<p class="eyebrow">Architecture comparison <span>01 / 03</span></p>
|
| 22 |
<h1 id="page-title">Two dense decoders.<br><em>Different paths through a token.</em></h1>
|
| 23 |
<p class="lead">Nanbeige repeats full attention in 52 layers. Qwen alternates recurrent linear attention with full attention across 64 text-decoder layers. Explore the published configuration, one layer at a time.</p>
|
| 24 |
<div class="scope"><span class="scope-dot" aria-hidden="true"></span><strong>Configuration view</strong><span>Layer types and dimensions come from pinned model configs. Nanbeige weights, activations and routing were not measured here.</span></div>
|
|
|
|
| 41 |
<div class="spec-wrap"><table class="spec-table"><caption>Published decoder configuration</caption><thead><tr><th scope="col">Configuration</th><th scope="col">Nanbeige</th><th scope="col">Qwen text decoder</th></tr></thead><tbody id="spec-body"></tbody></table></div>
|
| 42 |
</section>
|
| 43 |
|
| 44 |
+
<section class="kv" aria-labelledby="kv-title">
|
| 45 |
+
<div class="section-heading"><p class="eyebrow">02 / Cache lens</p><h2 id="kv-title">How full-attention K/V would scale</h2><p>Choose a context length to calculate raw f16 key and value tensor storage for one sequence if an inference runtime enables caching.</p></div>
|
| 46 |
+
<div class="kv-panel">
|
| 47 |
+
<div class="kv-control"><div class="kv-control-heading"><label for="kv-step">Context length</label><output id="kv-context-label" for="kv-step" aria-live="polite">32,768 tokens</output></div><input id="kv-step" type="range" min="0" max="7" step="1" value="4" aria-describedby="kv-scope"><div class="kv-endpoints"><span>2K tokens</span><span>256K tokens</span></div><p class="kv-share">Copy the page URL to share this context.</p></div>
|
| 48 |
+
<div class="kv-results">
|
| 49 |
+
<article class="kv-result nanbeige-kv"><p>Nanbeige4.3-33B</p><strong id="kv-nan-gb" aria-live="polite"></strong><span id="kv-nan-gib"></span><small id="kv-nan-per-token"></small></article>
|
| 50 |
+
<article class="kv-result qwen-kv"><p>Qwen3.8-27B text decoder</p><strong id="kv-qwen-gb" aria-live="polite"></strong><span id="kv-qwen-gib"></span><small id="kv-qwen-per-token"></small></article>
|
| 51 |
+
</div>
|
| 52 |
+
<p class="kv-ratio" id="kv-ratio"></p>
|
| 53 |
+
<p class="kv-formula">Formula: tokens × full-attention layers × 2 (K and V) × KV heads × head dimension × 2 bytes.</p>
|
| 54 |
+
<p class="kv-scope" id="kv-scope">Calculation scope: one sequence, f16 K and V for full-attention layers. Nanbeige's released model and <a href="https://huggingface.co/Nanbeige/Nanbeige4.3-33B/blob/c1093c501cc6460427c0ac634db7223d0450c0ee/generation_config.json" target="_blank" rel="noopener noreferrer">generation config</a> set <code>use_cache: false</code>, so its number applies when a runtime enables caching. Qwen's recurrent layers maintain separate state; weights and runtime overhead are separate.</p>
|
| 55 |
+
</div>
|
| 56 |
+
</section>
|
| 57 |
+
|
| 58 |
<section class="layers" aria-labelledby="layers-title">
|
| 59 |
+
<div class="section-heading"><p class="eyebrow">03 / Layer map</p><h2 id="layers-title">Follow the stack</h2><p>Each square is one configured decoder layer. Select a layer to inspect its attention and feed-forward dimensions.</p></div>
|
| 60 |
<div class="legend" aria-label="Layer colors"><span><i class="swatch full"></i>Full attention</span><span><i class="swatch linear"></i>Recurrent linear attention</span></div>
|
| 61 |
<div class="map-pair">
|
| 62 |
<div class="map-card"><div class="map-title"><h3>Nanbeige</h3><span>52 layers</span></div><div class="layer-grid" id="nanbeige-grid" role="group" aria-label="Nanbeige layers"></div></div>
|
architecture/kv-cache.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use strict';
|
| 2 |
+
|
| 3 |
+
function fullAttentionLayers(config) {
|
| 4 |
+
if (Array.isArray(config.layer_types)) {
|
| 5 |
+
return config.layer_types.filter(type => type === 'full_attention').length;
|
| 6 |
+
}
|
| 7 |
+
return config.num_hidden_layers;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
function fullAttentionKvBytes(config, tokens, bytesPerValue = 2) {
|
| 11 |
+
const layers = fullAttentionLayers(config);
|
| 12 |
+
for (const value of [layers, config.num_key_value_heads, config.head_dim, tokens, bytesPerValue]) {
|
| 13 |
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError('Invalid KV calculation input');
|
| 14 |
+
}
|
| 15 |
+
const bytes = tokens * layers * 2 * config.num_key_value_heads * config.head_dim * bytesPerValue;
|
| 16 |
+
if (!Number.isSafeInteger(bytes)) throw new RangeError('KV calculation exceeds safe integer range');
|
| 17 |
+
return bytes;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
const kvCache = { fullAttentionLayers, fullAttentionKvBytes };
|
| 21 |
+
if (typeof module !== 'undefined' && module.exports) module.exports = kvCache;
|
| 22 |
+
else window.kvCache = kvCache;
|
tests/architecture.test.cjs
CHANGED
|
@@ -4,6 +4,7 @@ const assert = require('node:assert/strict');
|
|
| 4 |
const fs = require('node:fs');
|
| 5 |
const path = require('node:path');
|
| 6 |
const crypto = require('node:crypto');
|
|
|
|
| 7 |
const root = path.resolve(__dirname, '..');
|
| 8 |
const read = file => fs.readFileSync(path.join(root, file), 'utf8');
|
| 9 |
const n = JSON.parse(read('models/nanbeige.json'));
|
|
@@ -19,9 +20,22 @@ test('the pinned Nanbeige configuration supports the displayed comparison', () =
|
|
| 19 |
assert.equal(n.num_key_value_heads, 8);
|
| 20 |
assert.equal(n.head_dim, 128);
|
| 21 |
assert.equal(n.max_position_embeddings, 524288);
|
|
|
|
| 22 |
assert.equal(n.num_experts, undefined);
|
| 23 |
});
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
test('the pinned Qwen text decoder has three linear layers for each full layer', () => {
|
| 26 |
assert.equal(q.model_type, 'qwen3_5_text');
|
| 27 |
assert.equal(q.layer_types.length, q.num_hidden_layers);
|
|
@@ -38,14 +52,17 @@ test('the architecture route is linked and ships its local assets', () => {
|
|
| 38 |
const home = read('index.html');
|
| 39 |
const html = read('architecture/index.html');
|
| 40 |
assert.match(home, /href="architecture\/index\.html"/);
|
| 41 |
-
for (const file of ['architecture/comparison.css', 'architecture/comparison.js', 'models/nanbeige.json', 'models/qwen.json']) {
|
| 42 |
assert.ok(fs.statSync(path.join(root, file)).size > 0, file);
|
| 43 |
}
|
| 44 |
assert.match(html, /Nanbeige.*?config/s);
|
| 45 |
assert.match(html, /c1093c501cc6460427c0ac634db7223d0450c0ee/);
|
| 46 |
assert.match(html, /1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/);
|
| 47 |
assert.match(html, /Nanbeige weights, activations and routing were not measured here/);
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
| 49 |
assert.doesNotMatch(text, /\u2014|—|—|—/i);
|
| 50 |
}
|
| 51 |
});
|
|
|
|
| 4 |
const fs = require('node:fs');
|
| 5 |
const path = require('node:path');
|
| 6 |
const crypto = require('node:crypto');
|
| 7 |
+
const { fullAttentionLayers, fullAttentionKvBytes } = require('../architecture/kv-cache.js');
|
| 8 |
const root = path.resolve(__dirname, '..');
|
| 9 |
const read = file => fs.readFileSync(path.join(root, file), 'utf8');
|
| 10 |
const n = JSON.parse(read('models/nanbeige.json'));
|
|
|
|
| 20 |
assert.equal(n.num_key_value_heads, 8);
|
| 21 |
assert.equal(n.head_dim, 128);
|
| 22 |
assert.equal(n.max_position_embeddings, 524288);
|
| 23 |
+
assert.equal(n.use_cache, false);
|
| 24 |
assert.equal(n.num_experts, undefined);
|
| 25 |
});
|
| 26 |
|
| 27 |
+
test('conditional f16 full-attention KV sizes use the pinned layer and head dimensions', () => {
|
| 28 |
+
assert.equal(fullAttentionLayers(n), 52);
|
| 29 |
+
assert.equal(fullAttentionLayers(q), 16);
|
| 30 |
+
assert.equal(fullAttentionKvBytes(n, 1), 212992);
|
| 31 |
+
assert.equal(fullAttentionKvBytes(q, 1), 65536);
|
| 32 |
+
assert.equal(fullAttentionKvBytes(n, 32768), 6979321856);
|
| 33 |
+
assert.equal(fullAttentionKvBytes(q, 32768), 2147483648);
|
| 34 |
+
assert.equal(fullAttentionKvBytes(n, 262144), 55834574848);
|
| 35 |
+
assert.equal(fullAttentionKvBytes(q, 262144), 17179869184);
|
| 36 |
+
assert.throws(() => fullAttentionKvBytes(n, 0), RangeError);
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
test('the pinned Qwen text decoder has three linear layers for each full layer', () => {
|
| 40 |
assert.equal(q.model_type, 'qwen3_5_text');
|
| 41 |
assert.equal(q.layer_types.length, q.num_hidden_layers);
|
|
|
|
| 52 |
const home = read('index.html');
|
| 53 |
const html = read('architecture/index.html');
|
| 54 |
assert.match(home, /href="architecture\/index\.html"/);
|
| 55 |
+
for (const file of ['architecture/comparison.css', 'architecture/comparison.js', 'architecture/kv-cache.js', 'models/nanbeige.json', 'models/qwen.json']) {
|
| 56 |
assert.ok(fs.statSync(path.join(root, file)).size > 0, file);
|
| 57 |
}
|
| 58 |
assert.match(html, /Nanbeige.*?config/s);
|
| 59 |
assert.match(html, /c1093c501cc6460427c0ac634db7223d0450c0ee/);
|
| 60 |
assert.match(html, /1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/);
|
| 61 |
assert.match(html, /Nanbeige weights, activations and routing were not measured here/);
|
| 62 |
+
assert.match(html, /use_cache: false/);
|
| 63 |
+
assert.match(html, /generation_config\.json/);
|
| 64 |
+
assert.match(html, /if an inference runtime enables caching/);
|
| 65 |
+
for (const text of [html, read('architecture/comparison.js'), read('architecture/comparison.css'), read('architecture/kv-cache.js')]) {
|
| 66 |
assert.doesNotMatch(text, /\u2014|—|—|—/i);
|
| 67 |
}
|
| 68 |
});
|