firobeid's picture
Upload index.html
eb18fb8 verified
<style>
.pe-wrap { font-family: var(--font-sans); padding: 1rem 0; }
.pe-title { font-size: 15px; font-weight: 500; color: var(--color-text-primary); margin-bottom: 4px; }
.pe-sub { font-size: 12px; color: var(--color-text-secondary); margin-bottom: 16px; }
.pe-canvas-wrap { position: relative; width: 100%; }
.pe-axis-label { font-size: 12px; color: var(--color-text-secondary); text-align: center; }
.pe-y-label { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 12px; color: var(--color-text-secondary); display: flex; align-items: center; justify-content: center; min-width: 20px; }
.pe-row { display: flex; align-items: stretch; gap: 8px; }
.pe-legend { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 11px; color: var(--color-text-secondary); }
.pe-legend-bar { flex: 1; height: 12px; border-radius: 3px; }
.annotation-box { background: var(--color-background-secondary); border: 0.5px solid var(--color-border-tertiary); border-radius: 8px; padding: 10px 14px; margin-top: 14px; font-size: 12px; color: var(--color-text-secondary); line-height: 1.6; }
.ann-row { display: flex; gap: 16px; }
.ann-col { flex: 1; }
.ann-head { font-weight: 500; font-size: 12px; color: var(--color-text-primary); margin-bottom: 3px; }
.highlight-col { border: 2px solid #EF9F27; border-radius: 3px; position: absolute; pointer-events: none; }
.highlight-row { border: 2px solid #1D9E75; border-radius: 3px; position: absolute; pointer-events: none; }
.pe-controls { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; align-items: center; }
.pe-btn { background: var(--color-background-secondary); border: 0.5px solid var(--color-border-secondary); border-radius: 6px; padding: 4px 12px; font-size: 12px; cursor: pointer; color: var(--color-text-primary); }
.pe-btn.active { background: var(--color-background-info); color: var(--color-text-info); border-color: var(--color-border-info); }
.tooltip { position: absolute; background: var(--color-background-primary); border: 0.5px solid var(--color-border-secondary); border-radius: 6px; padding: 6px 10px; font-size: 11px; color: var(--color-text-primary); pointer-events: none; opacity: 0; transition: opacity 0.15s; white-space: nowrap; z-index: 10; }
</style>
<div class="pe-wrap">
<div class="pe-title">Positional Encoding Matrix</div>
<div class="pe-sub">Each cell = PE(pos, i). Hover to inspect. Click a column (dimension) or row (position) to highlight it.</div>
<div class="pe-controls">
<span style="font-size:12px;color:var(--color-text-secondary)">Hover mode:</span>
<button class="pe-btn active" id="btn-cell" onclick="setMode('cell')">Cell value</button>
<button class="pe-btn" id="btn-col" onclick="setMode('col')">Column (dimension)</button>
<button class="pe-btn" id="btn-row" onclick="setMode('row')">Row (position)</button>
</div>
<div class="pe-row">
<div class="pe-y-label" id="ylabel">Sequence position <em>(pos ↓)</em></div>
<div style="flex:1;position:relative;">
<canvas id="pe-canvas" style="width:100%;display:block;cursor:crosshair;"></canvas>
<div class="tooltip" id="tt"></div>
</div>
</div>
<div style="margin-left:28px">
<div class="pe-axis-label">Embedding dimension <em>(i β†’)</em></div>
<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--color-text-secondary);margin-top:2px;margin-left:2px;margin-right:2px">
<span>i=0 (fast)</span><span>i=128</span><span>i=256 (slow)</span>
</div>
</div>
<div class="pe-legend" style="margin-left:28px">
<span>βˆ’1</span>
<canvas id="legend-bar" width="300" height="12" style="flex:1;border-radius:3px;"></canvas>
<span>+1</span>
</div>
<div class="annotation-box" id="ann-box">
<div class="ann-row">
<div class="ann-col">
<div class="ann-head">⚑ Fast dimensions (small <em>i</em>, left)</div>
The sine wave completes many full cycles across just a few positions. Adjacent words look very different here β€” the model uses this to detect <strong>neighbors</strong>.
</div>
<div class="ann-col">
<div class="ann-head">🐒 Slow dimensions (large <em>i</em>, right)</div>
The wavelength is enormous (up to 10,000Β·2Ο€). Values barely change between consecutive words, but differ noticeably over hundreds of positions β€” telling the model about <strong>long-range structure</strong>.
</div>
</div>
</div>
</div>
<script>
const ROWS = 50;
const COLS = 256;
const D_MODEL = 512;
function pe(pos, i) {
const denom = Math.pow(10000, (2 * Math.floor(i / 2)) / D_MODEL);
return (i % 2 === 0) ? Math.sin(pos / denom) : Math.cos(pos / denom);
}
const matrix = [];
for (let r = 0; r < ROWS; r++) {
matrix[r] = [];
for (let c = 0; c < COLS; c++) {
matrix[r][c] = pe(r, c);
}
}
function valToColor(v, alpha) {
const t = (v + 1) / 2;
const isDark = matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
if (t > 0.5) {
const s = (t - 0.5) * 2;
const r = Math.round(30 + s * 200), g = Math.round(40 + s * 100), b = Math.round(80 + s * 80);
return `rgba(${r},${g},${b},${alpha})`;
} else {
const s = (0.5 - t) * 2;
const r = Math.round(30 + s * 180), g = Math.round(40 + s * 40), b = Math.round(80 + s * 120);
return `rgba(${r},${g},${b},${alpha})`;
}
} else {
if (t > 0.5) {
const s = (t - 0.5) * 2;
const r = Math.round(248 - s * 130), g = Math.round(248 - s * 30), b = Math.round(248 - s * 200);
return `rgba(${r},${g},${b},${alpha})`;
} else {
const s = (0.5 - t) * 2;
const r = Math.round(248 - s * 50), g = Math.round(248 - s * 140), b = Math.round(248 - s * 20);
return `rgba(${r},${g},${b},${alpha})`;
}
}
}
const canvas = document.getElementById('pe-canvas');
const ctx = canvas.getContext('2d');
let cellW, cellH, dpr;
function drawMatrix(highlightCol = -1, highlightRow = -1) {
const w = canvas.offsetWidth;
const h = Math.round(w * (ROWS / COLS) * 1.4);
dpr = window.devicePixelRatio || 1;
canvas.width = w * dpr;
canvas.height = h * dpr;
canvas.style.height = h + 'px';
ctx.scale(dpr, dpr);
cellW = w / COLS;
cellH = h / ROWS;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const v = matrix[r][c];
const isHL = (highlightCol >= 0 && c === highlightCol) || (highlightRow >= 0 && r === highlightRow);
ctx.fillStyle = valToColor(v, isHL ? 1 : (highlightCol >= 0 || highlightRow >= 0 ? 0.35 : 1));
ctx.fillRect(c * cellW, r * cellH, cellW + 0.5, cellH + 0.5);
}
}
if (highlightCol >= 0) {
ctx.strokeStyle = '#EF9F27';
ctx.lineWidth = 2;
ctx.strokeRect(highlightCol * cellW + 1, 1, cellW - 1, ROWS * cellH - 2);
}
if (highlightRow >= 0) {
ctx.strokeStyle = '#1D9E75';
ctx.lineWidth = 2;
ctx.strokeRect(1, highlightRow * cellH + 1, COLS * cellW - 2, cellH - 1);
}
}
function drawLegend() {
const lb = document.getElementById('legend-bar');
const lctx = lb.getContext('2d');
const w = lb.offsetWidth || 300;
lb.width = w * (window.devicePixelRatio || 1);
lb.style.width = '100%';
for (let x = 0; x < w; x++) {
const v = (x / w) * 2 - 1;
lctx.fillStyle = valToColor(v, 1);
lctx.fillRect(x * (window.devicePixelRatio || 1), 0, (window.devicePixelRatio || 1), 12);
}
}
let mode = 'cell';
function setMode(m) {
mode = m;
['cell','col','row'].forEach(k => document.getElementById('btn-'+k).classList.toggle('active', k === m));
drawMatrix();
}
canvas.addEventListener('mousemove', function(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const col = Math.floor(x / (rect.width / COLS));
const row = Math.floor(y / (rect.height / ROWS));
if (col < 0 || col >= COLS || row < 0 || row >= ROWS) return;
const tt = document.getElementById('tt');
const v = matrix[row][col].toFixed(4);
if (mode === 'cell') {
drawMatrix();
tt.innerHTML = `<b>pos=${row}, i=${col}</b><br>value = ${v}<br>${col % 2 === 0 ? 'sin' : 'cos'} wave`;
} else if (mode === 'col') {
drawMatrix(-1, -1);
drawMatrix(col, -1);
const speed = col < 20 ? '⚑ fast' : col < 100 ? '⚑ moderate' : '🐒 slow';
tt.innerHTML = `<b>Dimension i=${col}</b> (${speed})<br>wavelength β‰ˆ ${(2 * Math.PI * Math.pow(10000, col / D_MODEL)).toFixed(0)}`;
} else {
drawMatrix(-1, row);
tt.innerHTML = `<b>Position pos=${row}</b><br>Word at index ${row} in the sequence`;
}
tt.style.left = Math.min(x + 10, rect.width - 180) + 'px';
tt.style.top = Math.max(y - 36, 0) + 'px';
tt.style.opacity = '1';
});
canvas.addEventListener('mouseleave', function() {
document.getElementById('tt').style.opacity = '0';
drawMatrix();
});
canvas.addEventListener('click', function(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const col = Math.floor(x / (rect.width / COLS));
const row = Math.floor(y / (rect.height / ROWS));
if (mode === 'col') {
const speed = col < 20 ? 'fast (high frequency)' : col < 100 ? 'moderate frequency' : 'slow (low frequency)';
const ann = document.getElementById('ann-box');
ann.innerHTML = `<div class="ann-row"><div class="ann-col"><div class="ann-head" style="color:#BA7517">Dimension i=${col} β€” ${speed}</div>
Looking down this column (vertical), the value at each row changes ${col < 30 ? '<strong>rapidly</strong> β€” adjacent words look very different. The model can distinguish neighbors.' : col > 150 ? '<strong>barely at all</strong> between neighbors β€” but significantly over hundreds of positions. Good for long-range context.' : 'at a moderate rate.'}<br><br>Wavelength β‰ˆ ${(2 * Math.PI * Math.pow(10000, col / D_MODEL)).toFixed(0)} radians.</div></div>`;
} else if (mode === 'row') {
const ann = document.getElementById('ann-box');
ann.innerHTML = `<div class="ann-row"><div class="ann-col"><div class="ann-head" style="color:#0F6E56">Position pos=${row} β€” the lookup table row for word ${row}</div>
Reading across this row (horizontal), you see how the 512-dimensional encoding vector for <em>any word at position ${row}</em> looks: left side oscillates fast, right side is nearly flat. When the Transformer sees a word here, it adds this entire row to the word's embedding.</div></div>`;
}
});
setTimeout(() => {
drawMatrix();
drawLegend();
}, 50);
window.addEventListener('resize', () => { drawMatrix(); drawLegend(); });
</script>