const svgOverlay = document.getElementById('svg-overlay'); const content = document.querySelector('.content'); const renderModelEl = document.getElementById('render-model'); let renderModel = {}; let renderTokens = []; try { renderModel = JSON.parse(renderModelEl ? renderModelEl.textContent : '{}') || {}; renderTokens = Array.isArray(renderModel.tokens) ? renderModel.tokens : []; } catch (e) { console.warn('Failed to parse render model JSON:', e); renderModel = {}; renderTokens = []; } function escapeControlChars(text) { if (!text) return text; let out = ''; for (let i = 0; i < text.length; i++) { const ch = text[i]; const code = text.charCodeAt(i); if (ch === '\\') { out += '\\\\'; } else if (ch === '\n') { out += '\\n'; } else if (ch === '\r') { out += '\\r'; } else if (ch === '\t') { out += '\\t'; } else if (code < 32 || code === 127) { out += '\\x' + code.toString(16).padStart(2, '0'); } else { out += ch; } } return out; } if (content) { while (content.firstChild) { content.removeChild(content.firstChild); } const wordCounts = {}; renderTokens.forEach((token) => { if (token && token.is_word && token.word_key) { wordCounts[token.word_key] = (wordCounts[token.word_key] || 0) + 1; } }); renderTokens.forEach((token, idx) => { const span = document.createElement('span'); span.className = 'token'; span.dataset.tokenIdx = String(idx); span.dataset.tunedDelta = (token && typeof token.tuned_delta === 'number') ? String(token.tuned_delta) : '0'; span.dataset.rawDelta = (token && typeof token.raw_delta === 'number') ? String(token.raw_delta) : ''; const kind = (token && token.display && token.display.kind) ? token.display.kind : 'normal'; const text = (token && token.display && typeof token.display.text === 'string') ? token.display.text : ''; const hasVisible = (() => { if (!text) return false; for (let i = 0; i < text.length; i++) { const code = text.charCodeAt(i); if (code >= 32 && code !== 127) { return true; } } return false; })(); const mainKind = (kind === 'control' && hasVisible) ? 'normal' : kind; if (text.includes('\n') || text.includes('\r')) { span.dataset.hasLinebreak = '1'; } if (mainKind === 'control') { span.classList.add('token-kind-control'); span.textContent = text; } else if (mainKind === 'raw') { span.classList.add('token-kind-raw'); span.textContent = text; } else { span.textContent = text; } if (token && token.is_word && token.word_key && wordCounts[token.word_key] > 1) { span.classList.add('word'); span.dataset.word = token.word_key; if (token.word_id !== undefined && token.word_id !== null) { span.dataset.wordId = String(token.word_id); } } content.appendChild(span); }); } const words = document.querySelectorAll('.word'); const wordGroups = {}; words.forEach(word => { const wordText = word.getAttribute('data-word'); if (!wordGroups[wordText]) { wordGroups[wordText] = []; } wordGroups[wordText].push(word); }); function clearLines() { while (svgOverlay.firstChild) { svgOverlay.removeChild(svgOverlay.firstChild); } words.forEach(w => w.classList.remove('highlighted')); } function pickRectByY(rects, targetY) { if (!rects || rects.length === 0) return null; let best = rects[0]; let bestDist = Infinity; rects.forEach(r => { const cy = r.top + r.height / 2; const dist = Math.abs(cy - targetY); if (dist < bestDist) { best = r; bestDist = dist; } }); return best; } function getAnchorRect(element, targetY) { const rects = Array.from(element.getClientRects()); if (rects.length === 0) return element.getBoundingClientRect(); if (rects.length === 1) return rects[0]; const picked = pickRectByY(rects, targetY); return picked || rects[0]; } function drawLines(hoveredWord, evt) { clearLines(); const wordText = hoveredWord.getAttribute('data-word'); const wordId = parseInt(hoveredWord.getAttribute('data-word-id')); const sameWords = wordGroups[wordText] || []; const previousWords = sameWords.filter(w => { const id = parseInt(w.getAttribute('data-word-id')); return id < wordId; }); if (previousWords.length === 0) return; sameWords.forEach(w => w.classList.add('highlighted')); const targetY = evt ? evt.clientY : (hoveredWord.getBoundingClientRect().top + hoveredWord.getBoundingClientRect().height / 2); const hoveredRect = getAnchorRect(hoveredWord, targetY); const hoveredX = hoveredRect.left + hoveredRect.width / 2; const hoveredY = hoveredRect.top + hoveredRect.height / 2; previousWords.forEach(prevWord => { const prevRect = getAnchorRect(prevWord, hoveredY); const prevX = prevRect.left + prevRect.width / 2; const prevY = prevRect.top + prevRect.height / 2; const midX = (hoveredX + prevX) / 2; const midY = Math.min(hoveredY, prevY) - 30; const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('class', 'link-line'); path.setAttribute('d', `M ${prevX} ${prevY} Q ${midX} ${midY} ${hoveredX} ${hoveredY}`); svgOverlay.appendChild(path); const dot1 = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); dot1.setAttribute('class', 'link-dot'); dot1.setAttribute('cx', prevX); dot1.setAttribute('cy', prevY); dot1.setAttribute('r', 4); svgOverlay.appendChild(dot1); const dot2 = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); dot2.setAttribute('class', 'link-dot'); dot2.setAttribute('cx', hoveredX); dot2.setAttribute('cy', hoveredY); dot2.setAttribute('r', 4); svgOverlay.appendChild(dot2); }); } words.forEach(word => { word.addEventListener('mouseenter', (e) => drawLines(word, e)); word.addEventListener('mouseleave', clearLines); }); window.addEventListener('scroll', clearLines); const tooltip = document.getElementById('tooltip'); const tokenSpans = document.querySelectorAll('.token'); tokenSpans.forEach(token => { token.addEventListener('mouseenter', (e) => { const tokenIdx = parseInt(token.dataset.tokenIdx); const tokenInfo = (!isNaN(tokenIdx) && renderTokens[tokenIdx]) ? renderTokens[tokenIdx] : null; const bytes = (tokenInfo && tokenInfo.bytes_hex) ? tokenInfo.bytes_hex : ''; const compressionA = (tokenInfo && tokenInfo.compression && tokenInfo.compression.rwkv) ? tokenInfo.compression.rwkv : ''; const compressionB = (tokenInfo && tokenInfo.compression && tokenInfo.compression.qwen) ? tokenInfo.compression.qwen : ''; const avgCompressionA = (tokenInfo && tokenInfo.loss && typeof tokenInfo.loss.rwkv === 'number') ? tokenInfo.loss.rwkv.toFixed(2) : ''; const avgCompressionB = (tokenInfo && tokenInfo.loss && typeof tokenInfo.loss.qwen === 'number') ? tokenInfo.loss.qwen.toFixed(2) : ''; const modelA = (tokenInfo && tokenInfo.model_tokens && tokenInfo.model_tokens.rwkv) ? tokenInfo.model_tokens.rwkv : null; const modelB = (tokenInfo && tokenInfo.model_tokens && tokenInfo.model_tokens.qwen) ? tokenInfo.model_tokens.qwen : null; const top5A = (tokenInfo && tokenInfo.topk && tokenInfo.topk.rwkv) ? tokenInfo.topk.rwkv : null; const top5B = (tokenInfo && tokenInfo.topk && tokenInfo.topk.qwen) ? tokenInfo.topk.qwen : null; function hasControlChars(text) { if (!text) return false; for (let i = 0; i < text.length; i++) { const code = text.charCodeAt(i); if (code < 32 || code === 127) { return true; } } return false; } function resolveKind(text, kindHint) { if (kindHint === 'raw' || kindHint === 'control' || kindHint === 'normal') { return kindHint; } if (kindHint === true) { return 'raw'; } if (hasControlChars(text)) { return 'control'; } return 'normal'; } function appendEscapedWithControlColor(container, text) { if (text === undefined || text === null) return; let buffer = ''; const flush = () => { if (buffer) { container.appendChild(document.createTextNode(buffer)); buffer = ''; } }; for (let i = 0; i < text.length; i++) { const ch = text[i]; const code = text.charCodeAt(i); if (ch === '\\') { buffer += '\\\\'; continue; } if (ch === '\n' || ch === '\r' || ch === '\t' || code < 32 || code === 127) { flush(); const span = document.createElement('span'); span.className = 'esc-control'; if (ch === '\n') { span.textContent = '\\n'; } else if (ch === '\r') { span.textContent = '\\r'; } else if (ch === '\t') { span.textContent = '\\t'; } else { span.textContent = '\\x' + code.toString(16).padStart(2, '0'); } container.appendChild(span); continue; } buffer += ch; } flush(); } function appendTokenText(container, text, kindHint) { const display = (text !== undefined && text !== null) ? text : ''; const kind = resolveKind(display, kindHint); while (container.firstChild) { container.removeChild(container.firstChild); } if (kind === 'raw') { const span = document.createElement('span'); span.className = 'esc-raw'; span.textContent = display; container.appendChild(span); return; } if (kind === 'control') { appendEscapedWithControlColor(container, display); return; } container.textContent = display; } function formatTopkColumn(topkData, modelName, titleClass) { const column = document.createElement('div'); column.className = 'topk-column'; const title = document.createElement('div'); title.className = 'topk-title ' + titleClass; title.textContent = modelName; column.appendChild(title); const list = document.createElement('div'); list.className = 'topk-list'; column.appendChild(list); if (!topkData) { list.textContent = 'N/A'; return column; } try { const data = topkData; let actualId = null; let rank = null; let actualProb = null; let topkList = []; if (data.length >= 4) { [actualId, rank, actualProb, topkList] = data; } else { [actualId, rank, topkList] = data; } topkList.forEach((item, idx) => { const tokenId = item[0]; const prob = item[1]; const tokenText = item[2]; const isRaw = item.length > 3 ? item[3] : false; const isHit = tokenId === actualId; const rankClass = isHit ? 'topk-rank hit' : 'topk-rank'; const rawText = (tokenText !== undefined && tokenText !== null) ? tokenText : ''; const displayText = (rawText !== '') ? rawText : ('[' + tokenId + ']'); const row = document.createElement('div'); row.className = 'topk-item'; const rankSpan = document.createElement('span'); rankSpan.className = rankClass; rankSpan.textContent = (idx + 1) + '.'; row.appendChild(rankSpan); const tokenSpan = document.createElement('span'); tokenSpan.className = 'topk-token'; tokenSpan.title = 'ID: ' + tokenId; appendTokenText(tokenSpan, displayText, isRaw); row.appendChild(tokenSpan); const probSpan = document.createElement('span'); probSpan.className = 'topk-prob'; probSpan.textContent = (prob * 100).toFixed(2) + '%'; row.appendChild(probSpan); if (isHit) { const hit = document.createElement('span'); hit.className = 'topk-hit'; hit.textContent = '✓'; row.appendChild(hit); } list.appendChild(row); }); if (rank > 10) { let probSuffix = ''; const probVal = parseFloat(actualProb); if (!isNaN(probVal)) { probSuffix = ' (' + (probVal * 100).toFixed(4) + '%)'; } const miss = document.createElement('div'); miss.className = 'topk-item topk-miss'; miss.textContent = 'Actual rank: ' + rank + probSuffix; list.appendChild(miss); } return column; } catch (e) { console.error('Error in formatTopkColumn for ' + modelName + ':', e); console.error('topkData:', topkData); list.textContent = 'Error: ' + e.message; return column; } } function formatTokenChips(modelData, label, labelClass) { const block = document.createElement('div'); block.className = 'token-block'; const labelSpan = document.createElement('span'); labelSpan.className = 'label ' + labelClass; labelSpan.textContent = label + ':'; block.appendChild(labelSpan); const chips = document.createElement('div'); chips.className = 'token-chips'; block.appendChild(chips); if (!modelData) { const na = document.createElement('span'); na.className = 'topk-token token-chip'; na.textContent = 'N/A'; chips.appendChild(na); return block; } try { const tokenList = modelData; tokenList.forEach((item) => { const tokenId = item[0]; const tokenText = item[1]; const kindHint = item.length > 2 ? item[2] : false; const probVal = item.length > 3 ? item[3] : null; const displayText = (tokenText !== undefined && tokenText !== null) ? tokenText : ''; const group = document.createElement('span'); group.className = 'token-chip-group'; group.title = 'ID: ' + tokenId; const idSpan = document.createElement('span'); idSpan.className = 'token-id'; idSpan.textContent = '[' + tokenId + ']'; group.appendChild(idSpan); const chipSpan = document.createElement('span'); chipSpan.className = 'topk-token token-chip'; appendTokenText(chipSpan, displayText, kindHint); group.appendChild(chipSpan); if (probVal !== null && probVal !== undefined) { const probSpan = document.createElement('span'); probSpan.className = 'token-prob'; const numericProb = typeof probVal === 'number' ? probVal : parseFloat(probVal); if (!isNaN(numericProb)) { probSpan.textContent = (numericProb * 100).toFixed(2) + '%'; } else { probSpan.textContent = String(probVal); } group.appendChild(probSpan); } chips.appendChild(group); }); return block; } catch (e) { console.error('Error in formatTokenChips for ' + label + ':', e); console.error('modelData:', modelData); const err = document.createElement('span'); err.className = 'topk-token token-chip'; err.textContent = 'Error: ' + e.message; chips.appendChild(err); return block; } } tooltip.replaceChildren(); const bytesRow = document.createElement('div'); const bytesLabel = document.createElement('span'); bytesLabel.className = 'label'; bytesLabel.textContent = 'Bytes:'; const bytesValue = document.createElement('span'); bytesValue.className = 'bytes'; bytesValue.textContent = bytes || '(empty)'; bytesRow.appendChild(bytesLabel); bytesRow.appendChild(document.createTextNode(' ')); bytesRow.appendChild(bytesValue); tooltip.appendChild(bytesRow); const rwkvRow = document.createElement('div'); const rwkvLabel = document.createElement('span'); rwkvLabel.className = 'label'; rwkvLabel.textContent = 'RWKV Compression Rate:'; const rwkvValue = document.createElement('span'); rwkvValue.className = 'loss-a'; rwkvValue.textContent = compressionA || '(empty)'; if (avgCompressionA) { rwkvValue.textContent += ' (avg: ' + avgCompressionA + '%)'; } rwkvRow.appendChild(rwkvLabel); rwkvRow.appendChild(document.createTextNode(' ')); rwkvRow.appendChild(rwkvValue); tooltip.appendChild(rwkvRow); const qwenRow = document.createElement('div'); const qwenLabel = document.createElement('span'); qwenLabel.className = 'label'; qwenLabel.textContent = 'Qwen Compression Rate:'; const qwenValue = document.createElement('span'); qwenValue.className = 'loss-b'; qwenValue.textContent = compressionB || '(empty)'; if (avgCompressionB) { qwenValue.textContent += ' (avg: ' + avgCompressionB + '%)'; } qwenRow.appendChild(qwenLabel); qwenRow.appendChild(document.createTextNode(' ')); qwenRow.appendChild(qwenValue); tooltip.appendChild(qwenRow); const hr = document.createElement('hr'); hr.style.borderColor = '#555'; hr.style.margin = '6px 0'; tooltip.appendChild(hr); tooltip.appendChild(formatTokenChips(modelA, 'RWKV', 'model-a')); tooltip.appendChild(formatTokenChips(modelB, 'Qwen', 'model-b')); if (top5A || top5B) { const topkSection = document.createElement('div'); topkSection.className = 'topk-section'; const topkContainer = document.createElement('div'); topkContainer.className = 'topk-container'; topkContainer.appendChild(formatTopkColumn(top5A, 'RWKV Top10', 'model-a')); topkContainer.appendChild(formatTopkColumn(top5B, 'Qwen Top10', 'model-b')); topkSection.appendChild(topkContainer); tooltip.appendChild(topkSection); } tooltip.style.display = 'block'; }); token.addEventListener('mousemove', (e) => { const tooltipRect = tooltip.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; let x = e.clientX + 15; let y = e.clientY + 15; if (x + tooltipRect.width > viewportWidth - 10) { x = e.clientX - tooltipRect.width - 15; } if (y + tooltipRect.height > viewportHeight - 10) { y = e.clientY - tooltipRect.height - 15; } if (x < 10) x = 10; if (y < 10) y = 10; tooltip.style.left = x + 'px'; tooltip.style.top = y + 'px'; }); token.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; }); }); const slider = document.getElementById('color-range-slider'); const rangeValue = document.getElementById('color-range-value'); const deltaModeInputs = document.querySelectorAll('input[name="delta-mode"]'); const legendBetter = document.getElementById('legend-better'); const legendEqual = document.getElementById('legend-equal'); const legendWorse = document.getElementById('legend-worse'); let deltaMode = 'relative'; // Collect all tuned_delta values const tokenData = []; const linebreakTokens = []; tokenSpans.forEach((token, idx) => { if (token.dataset.hasLinebreak === '1') { linebreakTokens.push(token); return; } const tunedDelta = parseFloat(token.dataset.tunedDelta); let rawDelta = parseFloat(token.dataset.rawDelta); if (isNaN(rawDelta)) { rawDelta = tunedDelta; } if (!isNaN(tunedDelta)) { tokenData.push({ token, tunedDelta, rawDelta }); } }); function getDeltaValue(item) { return deltaMode === 'absolute' ? item.rawDelta : item.tunedDelta; } function tunedDeltaToColor(tunedDelta, maxAbsDelta, exponent) { // Normalize to [-1, 1] const normalized = Math.max(-1, Math.min(1, tunedDelta / maxAbsDelta)); let r, g, b; if (normalized < 0) { // Green (RWKV better) const intensity = Math.pow(-normalized, exponent); r = Math.round(255 * (1 - intensity * 0.85)); g = 255; b = Math.round(255 * (1 - intensity * 0.85)); } else { // Red (RWKV worse) const intensity = Math.pow(normalized, exponent); r = 255; g = Math.round(255 * (1 - intensity * 0.85)); b = Math.round(255 * (1 - intensity * 0.85)); } return `rgb(${r}, ${g}, ${b})`; } function updateColors(colorRangePercent) { // colorRangePercent: 0-100, represents the proportion of tokens to color const absValues = tokenData.map(item => Math.abs(getDeltaValue(item))); const maxAbsDelta = Math.max(...absValues, 1e-9); const sortedByAbs = [...tokenData].sort((a, b) => Math.abs(getDeltaValue(b)) - Math.abs(getDeltaValue(a))); sortedByAbs.forEach((item, rank) => { item.rank = rank; }); const colorCount = Math.round(tokenData.length * colorRangePercent / 100); // Calculate exponent: 100% -> 0.5, 0% -> 1.0 const exponent = 1 - (colorRangePercent / 100) * 0.5; // Calculate max deviation within the colored range let maxAbsDeltaInRange = 1e-9; tokenData.forEach(item => { if (item.rank < colorCount) { maxAbsDeltaInRange = Math.max(maxAbsDeltaInRange, Math.abs(getDeltaValue(item))); } }); tokenData.forEach(item => { if (item.rank < colorCount) { // Use dynamic normalization based on colored range const deltaValue = getDeltaValue(item); item.token.style.backgroundColor = tunedDeltaToColor(deltaValue, maxAbsDeltaInRange, exponent); } else { // Outside color range, white item.token.style.backgroundColor = 'rgb(255, 255, 255)'; } }); linebreakTokens.forEach(token => { token.style.backgroundColor = 'rgb(255, 255, 255)'; }); } function updateLegendText() { if (!legendBetter || !legendEqual || !legendWorse) return; if (deltaMode === 'absolute') { legendBetter.textContent = 'RWKV better'; legendEqual.textContent = 'Equal'; legendWorse.textContent = 'RWKV worse'; } else { legendBetter.textContent = 'RWKV better than avg delta'; legendEqual.textContent = 'Equal to avg delta'; legendWorse.textContent = 'RWKV worse than avg delta'; } } slider.addEventListener('input', (e) => { const val = parseFloat(e.target.value); rangeValue.textContent = val.toFixed(1) + '%'; updateColors(val); }); deltaModeInputs.forEach(input => { input.addEventListener('change', (e) => { const target = e.target; if (target && target.checked) { deltaMode = target.value === 'absolute' ? 'absolute' : 'relative'; updateLegendText(); updateColors(parseFloat(slider.value)); } }); }); updateLegendText(); // Apply default color range on page load updateColors(10);