| | <div class="d3-parameter-comparison"></div> |
| |
|
| | <style> |
| | .d3-parameter-comparison { |
| | position: relative !important; |
| | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
| | line-height: 1.5; |
| | color: var(--text-color); |
| | padding-bottom: 0; |
| | } |
| | |
| | .d3-parameter-comparison .model-selector { |
| | margin-bottom: 32px; |
| | } |
| | |
| | .d3-parameter-comparison .section-title { |
| | font-size: 1.1em; |
| | font-weight: 700; |
| | color: var(--text-color); |
| | margin: 0 0 12px 0; |
| | } |
| | |
| | .d3-parameter-comparison .button-group { |
| | display: flex; |
| | gap: 0; |
| | width: 100%; |
| | background: var(--page-bg); |
| | border: 0.25px solid var(--primary-color); |
| | border-radius: 8px; |
| | padding: 0; |
| | margin-bottom: 24px; |
| | } |
| | |
| | .d3-parameter-comparison .actions-group { |
| | display: flex; |
| | gap: 8px; |
| | width: 100%; |
| | } |
| | |
| | .d3-parameter-comparison .actions-group .button { |
| | flex: 1; |
| | margin: 0 !important; |
| | border-radius: var(--button-radius) !important; |
| | border: 1px solid var(--primary-color) !important; |
| | display: flex !important; |
| | align-items: center !important; |
| | justify-content: center !important; |
| | text-align: center !important; |
| | gap: 8px !important; |
| | } |
| | |
| | .d3-parameter-comparison .btn-icon { |
| | flex-shrink: 0; |
| | opacity: 0.8; |
| | transition: opacity 0.2s ease; |
| | } |
| | |
| | .d3-parameter-comparison .actions-group .button:hover .btn-icon { |
| | opacity: 1; |
| | } |
| | |
| | |
| | .d3-parameter-comparison .button-group { |
| | display: flex; |
| | gap: 0; |
| | width: 100%; |
| | background: var(--page-bg); |
| | border: 0.25px solid var(--primary-color); |
| | border-radius: 8px; |
| | padding: 0; |
| | margin-bottom: 24px; |
| | } |
| | |
| | .d3-parameter-comparison .button-group .button { |
| | flex: 1; |
| | margin: 0 !important; |
| | border-radius: 0 !important; |
| | border: none !important; |
| | padding: calc(var(--button-padding-y) * 2) var(--button-padding-x) !important; |
| | } |
| | |
| | .d3-parameter-comparison .button-group .button:first-child { |
| | border-radius: 8px 0 0 8px !important; |
| | } |
| | |
| | .d3-parameter-comparison .button-group .button:last-child { |
| | border-radius: 0 8px 8px 0 !important; |
| | } |
| | |
| | .d3-parameter-comparison .button-group .button:not(:first-child) { |
| | border-left: 1px solid var(--primary-color) !important; |
| | } |
| | |
| | .d3-parameter-comparison .button-group .button.active { |
| | background: var(--primary-color) !important; |
| | color: var(--page-bg) !important; |
| | font-weight: 900 !important; |
| | } |
| | |
| | .d3-parameter-comparison .button-group .button.active:first-child { |
| | border-left: none !important; |
| | } |
| | |
| | |
| | .d3-parameter-comparison .model-info { |
| | margin: 10px 0; |
| | } |
| | |
| | .d3-parameter-comparison .model-size-card { |
| | background: var(--page-bg); |
| | border: 1px solid var(--border-color); |
| | border-radius: 8px; |
| | padding: 20px; |
| | margin: 16px 0 20px 0; |
| | text-align: center; |
| | width: 100%; |
| | } |
| | |
| | .d3-parameter-comparison .model-size-label { |
| | font-size: 0.8em; |
| | font-weight: 500; |
| | color: var(--muted-color); |
| | margin: 0 0 4px 0; |
| | } |
| | |
| | .d3-parameter-comparison .model-size { |
| | font-size: 1.8em; |
| | font-weight: bold; |
| | color: var(--primary-color); |
| | margin: 0 0 4px 0; |
| | } |
| | |
| | .d3-parameter-comparison .model-specs { |
| | display: grid; |
| | grid-template-columns: repeat(5, 1fr); |
| | gap: 12px; |
| | margin-bottom: 20px; |
| | } |
| | |
| | .d3-parameter-comparison .spec-card { |
| | background: var(--page-bg); |
| | border: 1px solid var(--border-color); |
| | border-radius: 8px; |
| | padding: 12px 8px; |
| | text-align: center; |
| | position: relative; |
| | overflow: hidden; |
| | } |
| | |
| | .d3-parameter-comparison .spec-label { |
| | font-size: 0.65em; |
| | color: var(--muted-color); |
| | font-weight: 600; |
| | text-transform: uppercase; |
| | letter-spacing: 0.3px; |
| | margin-bottom: 6px; |
| | line-height: 1.1; |
| | } |
| | |
| | .d3-parameter-comparison .spec-value { |
| | font-weight: 700; |
| | color: var(--primary-color); |
| | font-size: 1.0em; |
| | line-height: 1.1; |
| | word-break: break-all; |
| | } |
| | |
| | |
| | .d3-parameter-comparison .spec-card[data-type="number"] .spec-value { |
| | font-size: 1.1em; |
| | font-variant-numeric: tabular-nums; |
| | } |
| | |
| | .d3-parameter-comparison .spec-card[data-type="ratio"] .spec-value { |
| | font-size: 0.95em; |
| | font-variant-numeric: tabular-nums; |
| | } |
| | |
| | .d3-parameter-comparison .spec-card[data-type="text"] .spec-value { |
| | font-size: 0.9em; |
| | text-transform: capitalize; |
| | } |
| | |
| | .d3-parameter-comparison .chart-container { |
| | margin: 20px 0 0 0; |
| | } |
| | |
| | .d3-parameter-comparison .chart-card { |
| | background: var(--page-bg); |
| | border: 1px solid var(--border-color); |
| | border-radius: 10px; |
| | padding: 8px; |
| | } |
| | |
| | .d3-parameter-comparison .legend { |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | gap: 6px; |
| | margin: 8px 0 16px 0; |
| | font-size: 12px; |
| | color: var(--text-color); |
| | } |
| | |
| | .d3-parameter-comparison .legend .legend-title { |
| | font-size: 12px; |
| | font-weight: 700; |
| | color: var(--text-color); |
| | } |
| | |
| | .d3-parameter-comparison .legend .items { |
| | display: flex; |
| | flex-wrap: wrap; |
| | gap: 8px 14px; |
| | } |
| | |
| | .d3-parameter-comparison .legend .item { |
| | display: inline-flex; |
| | align-items: center; |
| | gap: 6px; |
| | white-space: nowrap; |
| | cursor: pointer; |
| | } |
| | |
| | .d3-parameter-comparison .legend .swatch { |
| | width: 14px; |
| | height: 14px; |
| | border-radius: 3px; |
| | border: 1px solid var(--border-color); |
| | } |
| | |
| | |
| | .d3-parameter-comparison .slice-label { |
| | font-size: 11px; |
| | font-weight: 700; |
| | fill: var(--text-color); |
| | paint-order: stroke; |
| | stroke: var(--transparent-page-contrast); |
| | stroke-width: 3px; |
| | } |
| | |
| | |
| | .d3-parameter-comparison.hovering .legend .item.ghost { |
| | opacity: .35; |
| | } |
| | |
| | .d3-parameter-comparison .slice { |
| | transition: opacity .15s ease; |
| | } |
| | |
| | .d3-parameter-comparison.hovering .slice.ghost { |
| | opacity: .25; |
| | } |
| | |
| | |
| | .d3-parameter-comparison .d3-tooltip { |
| | position: absolute; |
| | top: 0; |
| | left: 0; |
| | transform: translate(-9999px, -9999px); |
| | pointer-events: none; |
| | padding: 8px 10px; |
| | border-radius: 8px; |
| | font-size: 12px; |
| | line-height: 1.35; |
| | border: 1px solid var(--border-color); |
| | background: var(--surface-bg); |
| | color: var(--text-color); |
| | box-shadow: 0 4px 24px rgba(0, 0, 0, .18); |
| | opacity: 0; |
| | transition: opacity .12s ease; |
| | z-index: var(--z-elevated); |
| | backdrop-filter: saturate(1.12) blur(8px); |
| | } |
| | |
| | .d3-parameter-comparison .d3-tooltip__inner { |
| | display: flex; |
| | flex-direction: column; |
| | gap: 6px; |
| | min-width: 220px; |
| | text-align: left; |
| | } |
| | |
| | .d3-parameter-comparison .d3-tooltip__inner>div:first-child { |
| | font-weight: 800; |
| | letter-spacing: 0.1px; |
| | margin-bottom: 0; |
| | } |
| | |
| | .d3-parameter-comparison .d3-tooltip__inner>div:nth-child(2) { |
| | font-size: 11px; |
| | color: var(--muted-color); |
| | display: block; |
| | margin-top: -4px; |
| | margin-bottom: 2px; |
| | letter-spacing: 0.1px; |
| | } |
| | |
| | .d3-parameter-comparison .d3-tooltip__inner>div:nth-child(n+3) { |
| | padding-top: 6px; |
| | border-top: 1px solid var(--border-color); |
| | } |
| | |
| | .d3-parameter-comparison .d3-tooltip .swatch { |
| | width: 12px; |
| | height: 12px; |
| | border-radius: 3px; |
| | border: 1px solid var(--border-color); |
| | display: inline-block; |
| | margin-right: 6px; |
| | } |
| | |
| | |
| | |
| | @media (max-width: 768px) { |
| | .d3-parameter-comparison .model-specs { |
| | grid-template-columns: repeat(2, 1fr); |
| | gap: 12px; |
| | } |
| | |
| | .d3-parameter-comparison .button-group { |
| | flex-direction: column; |
| | gap: 4px; |
| | } |
| | |
| | .d3-parameter-comparison .actions-group { |
| | flex-direction: column; |
| | gap: 4px; |
| | } |
| | |
| | .d3-parameter-comparison .button-group .button, |
| | .d3-parameter-comparison .actions-group .button { |
| | padding: 8px 12px; |
| | } |
| | } |
| | |
| | @media (max-width: 480px) { |
| | .d3-parameter-comparison .model-specs { |
| | grid-template-columns: 1fr; |
| | } |
| | |
| | .d3-parameter-comparison .section-title { |
| | font-size: 1em; |
| | } |
| | } |
| | </style> |
| |
|
| | <script> |
| | (() => { |
| | const ensureD3 = (cb) => { |
| | if (window.d3 && typeof window.d3.select === 'function') return cb(); |
| | let s = document.getElementById('d3-cdn-script'); |
| | if (!s) { s = document.createElement('script'); s.id = 'd3-cdn-script'; s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; document.head.appendChild(s); } |
| | const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); }; |
| | s.addEventListener('load', onReady, { once: true }); if (window.d3) onReady(); |
| | }; |
| | |
| | const bootstrap = () => { |
| | const scriptEl = document.currentScript; |
| | let container = scriptEl ? scriptEl.previousElementSibling : null; |
| | if (!(container && container.classList && container.classList.contains('d3-parameter-comparison'))) { |
| | const cs = Array.from(document.querySelectorAll('.d3-parameter-comparison')).filter(el => !(el.dataset && el.dataset.mounted === 'true')); |
| | container = cs[cs.length - 1] || null; |
| | } |
| | if (!container) return; |
| | if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; } |
| | |
| | container.style.position = container.style.position || 'relative'; |
| | |
| | |
| | |
| | const models = { |
| | 'llama3.2-1b': { |
| | name: 'Llama 3.2 1B', |
| | configUrl: 'https://huggingface.co/meta-llama/Llama-3.2-1B/blob/main/config.json', |
| | vocab_size: 128256, |
| | hidden_size: 2048, |
| | num_hidden_layers: 16, |
| | num_attention_heads: 32, |
| | num_key_value_heads: 8, |
| | intermediate_size: 8192, |
| | tie_word_embeddings: true |
| | }, |
| | 'smollm3-3b': { |
| | name: 'SmolLM3 3B', |
| | configUrl: 'https://huggingface.co/HuggingFaceTB/SmolLM3-3B/blob/main/config.json', |
| | vocab_size: 128256, |
| | hidden_size: 2048, |
| | num_hidden_layers: 36, |
| | num_attention_heads: 16, |
| | num_key_value_heads: 4, |
| | intermediate_size: 11008, |
| | tie_word_embeddings: true |
| | }, |
| | 'llama3.1-8b': { |
| | name: 'Llama 3.1 8B', |
| | configUrl: 'https://huggingface.co/meta-llama/Llama-3.1-8B/blob/main/config.json', |
| | vocab_size: 128256, |
| | hidden_size: 4096, |
| | num_hidden_layers: 32, |
| | num_attention_heads: 32, |
| | num_key_value_heads: 8, |
| | intermediate_size: 14336, |
| | tie_word_embeddings: false |
| | }, |
| | 'llama3.1-70b': { |
| | name: 'Llama 3.1 70B', |
| | configUrl: 'https://huggingface.co/meta-llama/Llama-3.1-70B/blob/main/config.json', |
| | vocab_size: 128256, |
| | hidden_size: 8192, |
| | num_hidden_layers: 80, |
| | num_attention_heads: 64, |
| | num_key_value_heads: 8, |
| | intermediate_size: 28672, |
| | tie_word_embeddings: false |
| | } |
| | }; |
| | |
| | function calculateParameters(config) { |
| | const { vocab_size, hidden_size, num_hidden_layers, num_attention_heads, num_key_value_heads, intermediate_size, tie_word_embeddings } = config; |
| | |
| | |
| | const embeddings = vocab_size * hidden_size * (tie_word_embeddings ? 1 : 2); |
| | |
| | |
| | const attention = num_hidden_layers * hidden_size * hidden_size * (2 + 2 * num_key_value_heads / num_attention_heads); |
| | |
| | |
| | const ffn = num_hidden_layers * hidden_size * intermediate_size * 3; |
| | |
| | |
| | const layernorm = num_hidden_layers * hidden_size * 2 + hidden_size; |
| | |
| | const total = embeddings + attention + ffn + layernorm; |
| | |
| | return { embeddings, attention, ffn, layernorm, total }; |
| | } |
| | |
| | function formatNumber(num) { |
| | if (num >= 1000000000) return `${(num / 1000000000).toFixed(2)} <span style="opacity: 0.6;">B</span>`; |
| | if (num >= 1000000) return `${(num / 1000000).toFixed(0)} <span style="opacity: 0.6;">M</span>`; |
| | if (num >= 1000) return `${(num / 1000).toFixed(0)} <span style="opacity: 0.6;">K</span>`; |
| | return num.toString(); |
| | } |
| | |
| | function formatPercent(part, total) { |
| | return `${((part / total) * 100).toFixed(1)} <span style="opacity: 0.6;">%</span>`; |
| | } |
| | |
| | function getColors() { |
| | try { |
| | if (window.ColorPalettes && typeof window.ColorPalettes.getColors === 'function') { |
| | return window.ColorPalettes.getColors('categorical', 4); |
| | } |
| | } catch (_) { } |
| | return ['#E458B5', '#F3A4D3', '#55D09D', '#00B971']; |
| | } |
| | |
| | |
| | container.innerHTML = ` |
| | <div class="model-selector"> |
| | <h3 class="section-title">Choose a Model</h3> |
| | <div class="button-group" id="modelButtonGroup"> |
| | <button class="button button--ghost active" data-model="llama3.2-1b">Llama 3.2 1B</button> |
| | <button class="button button--ghost" data-model="smollm3-3b">SmolLM3 3B</button> |
| | <button class="button button--ghost" data-model="llama3.1-8b">Llama 3.1 8B</button> |
| | <button class="button button--ghost" data-model="llama3.1-70b">Llama 3.1 70B</button> |
| | </div> |
| | <div class="actions-group"> |
| | <button class="button button--ghost" id="exportBtn"> |
| | <svg class="btn-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| | <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> |
| | <polyline points="7,10 12,15 17,10"/> |
| | <line x1="12" y1="15" x2="12" y2="3"/> |
| | </svg> |
| | <span class="btn-text">Export JSON</span> |
| | </button> |
| | <a href="#" class="button button--ghost" id="configLink" target="_blank"> |
| | <svg class="btn-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| | <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/> |
| | <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/> |
| | </svg> |
| | <span class="btn-text">View Original</span> |
| | </a> |
| | </div> |
| | </div> |
| | |
| | <div class="model-info"> |
| | <div class="model-size-card"> |
| | <div class="model-size" id="modelSize">1.24B</div> |
| | <div class="model-size-label">Parameters</div> |
| | </div> |
| | <div class="model-specs"> |
| | <div class="spec-card" data-type="number"> |
| | <div class="spec-label">Layers</div> |
| | <div class="spec-value" id="specLayers">16</div> |
| | </div> |
| | <div class="spec-card" data-type="number"> |
| | <div class="spec-label">Hidden Size</div> |
| | <div class="spec-value" id="specHidden">2048</div> |
| | </div> |
| | <div class="spec-card" data-type="ratio"> |
| | <div class="spec-label">Heads (Q/KV)</div> |
| | <div class="spec-value" id="specHeads">32/8</div> |
| | </div> |
| | <div class="spec-card" data-type="number"> |
| | <div class="spec-label">Intermediate</div> |
| | <div class="spec-value" id="specIntermediate">8192</div> |
| | </div> |
| | <div class="spec-card" data-type="text"> |
| | <div class="spec-label">Embeddings</div> |
| | <div class="spec-value" id="specTied">Tied</div> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="chart-container"> |
| | <div class="chart-card"> |
| | <div class="chart-svg"></div> |
| | <div class="legend"> |
| | <div class="legend-title">Legend</div> |
| | <div class="items"></div> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| | |
| | |
| | let tip = container.querySelector('.d3-tooltip'); let tipInner; |
| | if (!tip) { tip = document.createElement('div'); tip.className = 'd3-tooltip'; tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tip.appendChild(tipInner); container.appendChild(tip); } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; } |
| | |
| | const chartCard = container.querySelector('.chart-card'); |
| | const chartSvg = container.querySelector('.chart-svg'); |
| | const legend = container.querySelector('.legend'); |
| | const items = legend.querySelector('.items'); |
| | |
| | const svg = d3.select(chartSvg).append('svg').attr('width', '100%').style('display', 'block'); |
| | const gRoot = svg.append('g'); |
| | |
| | let width = 400, height = 240; const DONUT_INNER_RATIO = 0.6; |
| | function updateSize() { |
| | width = chartSvg.clientWidth || 400; |
| | height = Math.min(width, 240); |
| | svg.attr('width', width).attr('height', height); |
| | gRoot.attr('transform', `translate(${width / 2},${height / 2})`); |
| | return { inner: Math.min(width, height) * 0.4 }; |
| | } |
| | |
| | function makeLegend(categories, colorOf, pieData, rawValues, total) { |
| | items.innerHTML = ''; |
| | categories.forEach(name => { |
| | const el = document.createElement('span'); |
| | el.className = 'item'; |
| | el.dataset.category = name; |
| | const sw = document.createElement('span'); |
| | sw.className = 'swatch'; |
| | sw.style.background = colorOf(name); |
| | const txt = document.createElement('span'); |
| | txt.textContent = name; |
| | el.appendChild(sw); |
| | el.appendChild(txt); |
| | |
| | |
| | el.addEventListener('mouseenter', (ev) => { |
| | container.classList.add('hovering'); |
| | gRoot.selectAll('path.slice').classed('ghost', s => (s && s.data && s.data.category) !== name); |
| | items.querySelectorAll('.item').forEach(it => it.classList.toggle('ghost', it.dataset.category !== name)); |
| | |
| | |
| | const sliceData = pieData.find(d => d.data.category === name); |
| | if (sliceData) { |
| | const realValue = rawValues.find(r => r.category === name)?.value || sliceData.data.value; |
| | const realPct = (realValue / total) * 100; |
| | const colorSw = colorOf(name); |
| | const selectedModel = getSelectedModel(); |
| | const modelName = models[selectedModel]?.name || selectedModel; |
| | |
| | tipInner.innerHTML = `<div style="display:flex;align-items:center;gap:8px;white-space:nowrap;"><span class="swatch" style="background:${colorSw}"></span><strong>${name}</strong></div>` + |
| | `<div style="font-size:11px;color:var(--muted-color);margin-top:-4px;">${modelName}</div>` + |
| | `<div style="padding-top:6px;border-top:1px solid var(--border-color);display:flex;justify-content:space-between;align-items:center;">` + |
| | `<span><strong>Parameters</strong></span>` + |
| | `<span style="font-weight:600;">${realValue.toLocaleString()}</span>` + |
| | `</div>` + |
| | `<div style="display:flex;justify-content:space-between;align-items:center;">` + |
| | `<span><strong>Percentage</strong></span>` + |
| | `<span style="font-weight:600;">${realPct.toFixed(1)}%</span>` + |
| | `</div>`; |
| | tip.style.opacity = '1'; |
| | } |
| | }); |
| | |
| | el.addEventListener('mouseleave', () => { |
| | tip.style.opacity = '0'; |
| | tip.style.transform = 'translate(-9999px, -9999px)'; |
| | container.classList.remove('hovering'); |
| | gRoot.selectAll('path.slice').classed('ghost', false); |
| | items.querySelectorAll('.item').forEach(it => it.classList.remove('ghost')); |
| | }); |
| | |
| | items.appendChild(el); |
| | }); |
| | } |
| | |
| | function render(data) { |
| | const { inner } = updateSize(); |
| | const categories = ['Embeddings', 'Attention', 'Feed Forward', 'Layer Norms']; |
| | const colors = getColors(); |
| | const color = d3.scaleOrdinal().domain(categories).range(colors); |
| | const colorOf = (c) => color(c || 'Unknown'); |
| | |
| | const rawValues = [ |
| | { category: 'Embeddings', value: data.embeddings }, |
| | { category: 'Attention', value: data.attention }, |
| | { category: 'Feed Forward', value: data.ffn }, |
| | { category: 'Layer Norms', value: data.layernorm } |
| | ]; |
| | |
| | const total = rawValues.reduce((sum, d) => sum + d.value, 0); |
| | const minVisiblePercent = 0.5; |
| | const minVisibleValue = total * (minVisiblePercent / 100); |
| | |
| | const values = rawValues.map(d => ({ |
| | category: d.category, |
| | value: Math.max(d.value, minVisibleValue) |
| | })); |
| | |
| | const sum = d3.sum(values, d => d.value) || 1; |
| | |
| | const radius = Math.max(60, Math.min(inner, 120)); |
| | const innerR = Math.round(radius * DONUT_INNER_RATIO); |
| | const pie = d3.pie().sort(null).value(d => d.value).padAngle(0.02); |
| | const arc = d3.arc().innerRadius(innerR).outerRadius(radius).cornerRadius(3); |
| | const arcLabel = d3.arc().innerRadius((innerR + radius) / 2).outerRadius((innerR + radius) / 2); |
| | |
| | const pieData = pie(values); |
| | |
| | |
| | makeLegend(categories, colorOf, pieData, rawValues, total); |
| | |
| | const slices = gRoot.selectAll('path.slice').data(pieData, d => d.data.category); |
| | |
| | slices.enter().append('path').attr('class', 'slice') |
| | .attr('fill', d => colorOf(d.data.category)) |
| | .attr('stroke', getComputedStyle(document.documentElement).getPropertyValue('--page-bg').trim() || '#ffffff') |
| | .attr('stroke-width', 1) |
| | .attr('data-category', d => d.data.category) |
| | .on('mouseenter', (ev, d) => { |
| | |
| | const realValue = rawValues.find(r => r.category === d.data.category)?.value || d.data.value; |
| | const realPct = (realValue / total) * 100; |
| | container.classList.add('hovering'); |
| | gRoot.selectAll('path.slice').classed('ghost', s => (s && s.data && s.data.category) !== d.data.category); |
| | try { const items = legend.querySelectorAll('.item'); items.forEach(it => it.classList.toggle('ghost', it.dataset.category !== d.data.category)); } catch (_) { } |
| | const colorSw = colorOf(d.data.category); |
| | const selectedModel = getSelectedModel(); |
| | const modelName = models[selectedModel]?.name || selectedModel; |
| | |
| | tipInner.innerHTML = `<div style="display:flex;align-items:center;gap:8px;white-space:nowrap;"><span class="swatch" style="background:${colorSw}"></span><strong>${d.data.category}</strong></div>` + |
| | `<div style="font-size:11px;color:var(--muted-color);margin-top:-4px;">${modelName}</div>` + |
| | `<div style="padding-top:6px;border-top:1px solid var(--border-color);display:flex;justify-content:space-between;align-items:center;">` + |
| | `<span><strong>Parameters</strong></span>` + |
| | `<span style="font-weight:600;">${realValue.toLocaleString()}</span>` + |
| | `</div>` + |
| | `<div style="display:flex;justify-content:space-between;align-items:center;">` + |
| | `<span><strong>Percentage</strong></span>` + |
| | `<span style="font-weight:600;">${realPct.toFixed(1)}%</span>` + |
| | `</div>`; |
| | tip.style.opacity = '1'; |
| | }) |
| | .on('mousemove', (ev) => { const [mx, my] = d3.pointer(ev, container); tip.style.transform = `translate(${Math.round(mx + 12)}px, ${Math.round(my + 12)}px)`; }) |
| | .on('mouseleave', () => { |
| | tip.style.opacity = '0'; tip.style.transform = 'translate(-9999px, -9999px)'; |
| | container.classList.remove('hovering'); |
| | gRoot.selectAll('path.slice').classed('ghost', false); |
| | try { const items = legend.querySelectorAll('.item'); items.forEach(it => it.classList.remove('ghost')); } catch (_) { } |
| | }) |
| | .merge(slices) |
| | .transition() |
| | .duration(750) |
| | .attrTween('d', function (d) { |
| | const interpolate = d3.interpolate(this._current || { startAngle: 0, endAngle: 0 }, d); |
| | this._current = interpolate(0); |
| | return function (t) { |
| | return arc(interpolate(t)); |
| | }; |
| | }); |
| | |
| | slices.exit().remove(); |
| | |
| | const labels = gRoot.selectAll('text.slice-label').data(pieData, d => d.data.category); |
| | labels.enter().append('text').attr('class', 'slice-label').attr('text-anchor', 'middle') |
| | .merge(labels) |
| | .transition() |
| | .duration(750) |
| | .attrTween('transform', function (d) { |
| | const interpolate = d3.interpolate(this._current || { startAngle: 0, endAngle: 0 }, d); |
| | this._current = interpolate(0); |
| | return function (t) { |
| | return `translate(${arcLabel.centroid(interpolate(t))})`; |
| | }; |
| | }) |
| | .attr('font-size', d => { |
| | const percentage = (d.data.value / sum) * 100; |
| | if (percentage >= 5) return '11px'; |
| | if (percentage >= 2) return '9px'; |
| | return '7px'; |
| | }) |
| | .text(d => { |
| | const realValue = rawValues.find(r => r.category === d.data.category)?.value || d.data.value; |
| | const realPct = (realValue / total) * 100; |
| | return realPct >= 1 ? `${realPct.toFixed(1)}%` : ''; |
| | }); |
| | labels.exit().remove(); |
| | } |
| | |
| | function getSelectedModel() { |
| | const activeBtn = container.querySelector('.button-group .button.active'); |
| | return activeBtn ? activeBtn.dataset.model : 'llama3.2-1b'; |
| | } |
| | |
| | function updateDisplay() { |
| | const selectedModel = getSelectedModel(); |
| | const config = models[selectedModel]; |
| | const params = calculateParameters(config); |
| | |
| | container.querySelector('#modelSize').innerHTML = formatNumber(params.total); |
| | container.querySelector('#specLayers').textContent = config.num_hidden_layers; |
| | container.querySelector('#specHidden').textContent = config.hidden_size; |
| | container.querySelector('#specHeads').textContent = `${config.num_attention_heads}/${config.num_key_value_heads}`; |
| | container.querySelector('#specIntermediate').textContent = config.intermediate_size; |
| | container.querySelector('#specTied').textContent = config.tie_word_embeddings ? 'Tied' : 'Separate'; |
| | |
| | |
| | const configLink = container.querySelector('#configLink'); |
| | configLink.href = config.configUrl; |
| | |
| | render(params); |
| | } |
| | |
| | function exportToJSON() { |
| | const selectedModel = getSelectedModel(); |
| | const config = models[selectedModel]; |
| | |
| | |
| | const jsonConfig = { |
| | vocab_size: config.vocab_size, |
| | hidden_size: config.hidden_size, |
| | num_hidden_layers: config.num_hidden_layers, |
| | num_attention_heads: config.num_attention_heads, |
| | num_key_value_heads: config.num_key_value_heads, |
| | intermediate_size: config.intermediate_size, |
| | tie_word_embeddings: config.tie_word_embeddings |
| | }; |
| | |
| | |
| | const jsonString = JSON.stringify(jsonConfig, null, 2); |
| | |
| | |
| | const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8;' }); |
| | const link = document.createElement('a'); |
| | const url = URL.createObjectURL(blob); |
| | link.setAttribute('href', url); |
| | link.setAttribute('download', `${config.name.replace(/\s+/g, '_')}_config.json`); |
| | link.style.visibility = 'hidden'; |
| | document.body.appendChild(link); |
| | link.click(); |
| | document.body.removeChild(link); |
| | } |
| | |
| | |
| | const buttonGroup = container.querySelector('#modelButtonGroup'); |
| | buttonGroup.addEventListener('click', (e) => { |
| | if (e.target.classList.contains('button')) { |
| | |
| | buttonGroup.querySelectorAll('.button').forEach(btn => btn.classList.remove('active')); |
| | |
| | e.target.classList.add('active'); |
| | updateDisplay(); |
| | } |
| | }); |
| | container.querySelector('#exportBtn').addEventListener('click', exportToJSON); |
| | |
| | |
| | updateDisplay(); |
| | |
| | |
| | if (window.ResizeObserver) { |
| | const ro = new ResizeObserver(() => { |
| | const selectedModel = getSelectedModel(); |
| | const config = models[selectedModel]; |
| | const params = calculateParameters(config); |
| | render(params); |
| | }); |
| | ro.observe(container); |
| | } else { |
| | window.addEventListener('resize', () => { |
| | const selectedModel = getSelectedModel(); |
| | const config = models[selectedModel]; |
| | const params = calculateParameters(config); |
| | render(params); |
| | }); |
| | } |
| | }; |
| | |
| | if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); } else { ensureD3(bootstrap); } |
| | })(); |
| | </script> |