Jellyfish042's picture
improvements
48754a8
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);