<!-- FlashAttention demo: compare HBM accesses in standard vs. flash attention -->
<div class="controls">
<label>Sequence length N = <strong id="nval">8</strong>
<input type="range" id="nslider" min="4" max="16" step="4" value="8">
</label>
<label>Tile size T = <strong id="tval">4</strong>
<input type="range" id="tslider" min="2" max="8" step="2" value="4">
</label>
</div>
<div class="mode-btns">
<button id="btn-standard" class="active" type="button">Standard Attention</button>
<button id="btn-flash" type="button">FlashAttention</button>
</div>
<canvas id="canvas" width="480" height="280"></canvas>
<div id="stats" class="stats"></div>
<div class="action-btns">
<button id="btn-run" type="button">Run animation</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<p class="caption" id="caption">Press ’Run animation’ to see each memory access step.</p>
/* Styles for the FlashAttention IO-aware tiling demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; gap: 1.4rem; flex-wrap: wrap; margin-bottom: .6rem; }
label { font-size: .85rem; color: #444; display: flex; align-items: center; gap: .5rem; }
input[type=range] { width: 90px; }
.mode-btns { display: flex; gap: .5rem; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button.active { background: #e63946; border-color: #c0303c; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
background: #f8fafc; max-width: 100%; }
.stats { font-size: .85rem; margin: .4rem 0; min-height: 1.3em; color: #1d3557; font-weight: 600; }
.action-btns { display: flex; gap: .5rem; margin-top: .4rem; }
.caption { font-size: .82rem; color: #555; margin: .5rem 0 0; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// FlashAttention IO-aware tiling visualizer
// Shows how standard attention makes O(N^2) HBM round-trips while FlashAttention tiles to SRAM.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Demo state: sequence length N, tile size T, current animation mode
let N = 8, T = 4, mode = 'standard';
let animFrame = null, animStep = 0, animSteps = [];
const COLORS = {
q: '#4a90d9', // query matrix color
k: '#e67e22', // key matrix color
v: '#27ae60', // value matrix color
s: '#9b59b6', // score matrix color
hbm_write: '#e63946', // HBM write highlight
hbm_read: '#f4a261', // HBM read highlight
sram: '#2ecc71', // SRAM tile highlight
out: '#1d3557', // output matrix color
tile: 'rgba(230,57,70,0.18)', // active tile overlay
bg: '#f8fafc',
border: '#cdd9e3',
text: '#222',
};
function getN(){ return parseInt(document.getElementById('nslider').value); }
function getT(){ return parseInt(document.getElementById('tslider').value); }
// Build the list of memory-access steps for a given mode
function buildSteps(n, t, m){
const steps = [];
const tClamped = Math.min(t, n);
if(m === 'standard'){
// Step 1: read Q, K, V from HBM
steps.push({ type: 'hbm_read', label: 'read_qkv', rows: n });
// Step 2: write N×N score matrix S to HBM
steps.push({ type: 'hbm_write', label: 'write_s', rows: n });
// Step 3: read S back from HBM for softmax
steps.push({ type: 'hbm_read', label: 'read_s', rows: n });
// Step 4: write softmax(S) = P to HBM
steps.push({ type: 'hbm_write', label: 'write_p', rows: n });
// Step 5: read P back from HBM
steps.push({ type: 'hbm_read', label: 'read_p', rows: n });
// Step 6: write output O to HBM
steps.push({ type: 'hbm_write', label: 'write_o', rows: n });
} else {
// FlashAttention: loop over tiles, each tile stays in SRAM
const numTiles = Math.ceil(n / tClamped);
for(let i = 0; i < numTiles; i++){
// Load one tile of Q/K/V into SRAM
steps.push({ type: 'tile_sram', label: 'load_tile', tile: i, numTiles, rows: tClamped });
// Compute partial softmax + accumulate O in SRAM
steps.push({ type: 'tile_compute', label: 'compute_tile', tile: i, numTiles, rows: tClamped });
// Write only the O chunk back to HBM
steps.push({ type: 'tile_write_o', label: 'write_tile_o', tile: i, numTiles, rows: tClamped });
}
}
return steps;
}
// Count total HBM element reads + writes across all steps
function countHBM(steps){
let reads = 0, writes = 0;
for(const s of steps){
if(s.type === 'hbm_read') reads += s.rows * s.rows;
if(s.type === 'hbm_write') writes += s.rows * s.rows;
if(s.type === 'tile_sram') reads += s.rows * s.rows;
if(s.type === 'tile_write_o') writes += s.rows * s.rows;
}
return { reads, writes };
}
// Render the canvas: HBM grid on left, SRAM grid on right
function draw(step){
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const n = N, t = Math.min(T, n);
const cellSize = Math.min(Math.floor((W * 0.42) / n), 24);
const gridW = cellSize * n, gridH = cellSize * n;
const leftX = 16, topY = Math.floor((H - gridH) / 2);
const rightX = W - gridW - 16;
// Clear canvas background
ctx.fillStyle = COLORS.bg;
ctx.fillRect(0, 0, W, H);
// Draw HBM / SRAM section labels
ctx.font = 'bold 11px system-ui';
ctx.fillStyle = COLORS.text;
ctx.textAlign = 'center';
ctx.fillText('HBM ' + step.hbmLabel, leftX + gridW / 2, topY - 10);
ctx.fillText('SRAM', rightX + gridW / 2, topY - 10);
// Draw the HBM matrix grid with highlights
for(let r = 0; r < n; r++){
for(let c = 0; c < n; c++){
const x = leftX + c * cellSize, y = topY + r * cellSize;
let fill = '#e8eef3';
if(step.hbmHighlight){
if(step.hbmHighlight === 'all') fill = COLORS.hbm_read;
else if(step.hbmHighlight === 'write_s') fill = COLORS.hbm_write;
else if(step.hbmHighlight === 'write_p') fill = COLORS.s;
else if(step.hbmHighlight === 'write_o') fill = COLORS.out;
}
ctx.fillStyle = fill;
ctx.fillRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
ctx.strokeStyle = COLORS.border;
ctx.strokeRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
}
}
// Draw the SRAM tile grid with active-tile highlight
for(let r = 0; r < n; r++){
for(let c = 0; c < n; c++){
const x = rightX + c * cellSize, y = topY + r * cellSize;
let fill = '#e8eef3';
if(step.sramHighlight){
const inTile = step.tileRow !== undefined
? (r >= step.tileRow && r < step.tileRow + t)
: false;
if(inTile) fill = COLORS.sram;
}
ctx.fillStyle = fill;
ctx.fillRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
ctx.strokeStyle = COLORS.border;
ctx.strokeRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
}
}
// Draw a directional arrow between HBM and SRAM
if(step.arrowDir){
const ay = topY + gridH / 2;
const ax1 = step.arrowDir === 'right' ? leftX + gridW + 4 : rightX - 4;
const ax2 = step.arrowDir === 'right' ? rightX - 4 : leftX + gridW + 4;
ctx.strokeStyle = step.arrowDir === 'right' ? COLORS.sram : COLORS.hbm_read;
ctx.lineWidth = 2.2;
ctx.beginPath();
ctx.moveTo(ax1, ay);
ctx.lineTo(ax2, ay);
const dir = step.arrowDir === 'right' ? 1 : -1;
ctx.lineTo(ax2 - dir * 8, ay - 5);
ctx.moveTo(ax2, ay);
ctx.lineTo(ax2 - dir * 8, ay + 5);
ctx.stroke();
ctx.lineWidth = 1;
}
}
// Map an animation step to its visual parameters
function stepToVis(s, n, t){
if(s.type === 'hbm_read')
return { hbmLabel: '(S, read)', hbmHighlight: 'all', sramHighlight: false, arrowDir: 'right' };
if(s.type === 'hbm_write' && s.label === 'write_s')
return { hbmLabel: '(writing S)', hbmHighlight: 'write_s', sramHighlight: false, arrowDir: 'left' };
if(s.type === 'hbm_write' && s.label === 'write_p')
return { hbmLabel: '(writing P=softmax(S))', hbmHighlight: 'write_p', sramHighlight: false, arrowDir: 'left' };
if(s.type === 'hbm_write' && s.label === 'write_o')
return { hbmLabel: '(writing O)', hbmHighlight: 'write_o', sramHighlight: false, arrowDir: 'left' };
if(s.type === 'tile_sram')
return { hbmLabel: '(idle)', hbmHighlight: null, sramHighlight: true, tileRow: s.tile * t, arrowDir: 'right' };
if(s.type === 'tile_compute')
return { hbmLabel: '(idle)', hbmHighlight: null, sramHighlight: true, tileRow: s.tile * t, arrowDir: null };
if(s.type === 'tile_write_o')
return { hbmLabel: '(write O chunk)', hbmHighlight: 'write_o', sramHighlight: false, tileRow: s.tile * t, arrowDir: 'left' };
return { hbmLabel: '', hbmHighlight: null, sramHighlight: false, arrowDir: null };
}
const STEP_LABELS = {
read_qkv: 'Read Q, K, V from HBM',
write_s: 'Write S = QKᵀ to HBM',
read_s: 'Read S from HBM',
write_p: 'Write P = softmax(S) to HBM',
read_p: 'Read P from HBM',
write_o: 'Write O = PV to HBM',
load_tile: 'Load tile into SRAM',
compute_tile:'Compute tile in SRAM (online softmax)',
write_tile_o:'Write O chunk to HBM',
};
function updateStats(){
const n = getN(), t = getT();
const std = countHBM(buildSteps(n, t, 'standard'));
const fl = countHBM(buildSteps(n, t, 'flash'));
const stdTotal = std.reads + std.writes;
const flTotal = fl.reads + fl.writes;
const ratio = (stdTotal / flTotal).toFixed(1);
document.getElementById('stats').textContent =
(mode === 'standard'
? fmt('Standard attention: %stdTotal% HBM element accesses (reads + writes)', { stdTotal: stdTotal })
: fmt('FlashAttention: %flTotal% HBM element accesses — %ratio%× fewer than standard', { flTotal: flTotal, ratio: ratio }));
}
function reset(){
if(animFrame){ cancelAnimationFrame(animFrame); animFrame = null; }
animStep = 0; animSteps = [];
N = getN(); T = getT();
const vis = stepToVis({ type: 'hbm_read', label: 'read_qkv', rows: N }, N, Math.min(T,N));
draw({ ...vis, hbmHighlight: null, arrowDir: null, hbmLabel: '' });
document.getElementById('caption').textContent = 'Press ’Run animation’ to see each memory access step.';
updateStats();
}
function runAnimation(){
if(animFrame){ cancelAnimationFrame(animFrame); animFrame = null; }
N = getN(); T = getT();
animSteps = buildSteps(N, Math.min(T, N), mode);
animStep = 0;
function tick(){
if(animStep >= animSteps.length){ animFrame = null; return; }
const s = animSteps[animStep];
const vis = stepToVis(s, N, Math.min(T, N));
draw(vis);
const lbl = STEP_LABELS[s.label] || s.label;
document.getElementById('caption').textContent =
(mode === 'standard' ? 'Standard attention step:' : 'FlashAttention step:') + ' ' + lbl;
updateStats();
animStep++;
animFrame = setTimeout(tick, 650);
}
tick();
}
document.getElementById('nslider').addEventListener('input', function(){
document.getElementById('nval').textContent = this.value; reset();
});
document.getElementById('tslider').addEventListener('input', function(){
document.getElementById('tval').textContent = this.value; reset();
});
document.getElementById('btn-standard').addEventListener('click', function(){
mode = 'standard';
document.getElementById('btn-standard').classList.add('active');
document.getElementById('btn-flash').classList.remove('active');
reset();
});
document.getElementById('btn-flash').addEventListener('click', function(){
mode = 'flash';
document.getElementById('btn-flash').classList.add('active');
document.getElementById('btn-standard').classList.remove('active');
reset();
});
document.getElementById('btn-run').addEventListener('click', runAnimation);
document.getElementById('btn-reset').addEventListener('click', reset);
reset();
<!-- FlashAttention demo: compare HBM accesses in standard vs. flash attention -->
<div class="controls">
<label>Sequence length N = <strong id="nval">8</strong>
<input type="range" id="nslider" min="4" max="16" step="4" value="8">
</label>
<label>Tile size T = <strong id="tval">4</strong>
<input type="range" id="tslider" min="2" max="8" step="2" value="4">
</label>
</div>
<div class="mode-btns">
<button id="btn-standard" class="active" type="button">Standard Attention</button>
<button id="btn-flash" type="button">FlashAttention</button>
</div>
<canvas id="canvas" width="480" height="280"></canvas>
<div id="stats" class="stats"></div>
<div class="action-btns">
<button id="btn-run" type="button">Run animation</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<p class="caption" id="caption">Press ’Run animation’ to see each memory access step.</p>
/* Styles for the FlashAttention IO-aware tiling demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; gap: 1.4rem; flex-wrap: wrap; margin-bottom: .6rem; }
label { font-size: .85rem; color: #444; display: flex; align-items: center; gap: .5rem; }
input[type=range] { width: 90px; }
.mode-btns { display: flex; gap: .5rem; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button.active { background: #e63946; border-color: #c0303c; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
background: #f8fafc; max-width: 100%; }
.stats { font-size: .85rem; margin: .4rem 0; min-height: 1.3em; color: #1d3557; font-weight: 600; }
.action-btns { display: flex; gap: .5rem; margin-top: .4rem; }
.caption { font-size: .82rem; color: #555; margin: .5rem 0 0; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// FlashAttention IO-aware tiling visualizer
// Shows how standard attention makes O(N^2) HBM round-trips while FlashAttention tiles to SRAM.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Demo state: sequence length N, tile size T, current animation mode
let N = 8, T = 4, mode = 'standard';
let animFrame = null, animStep = 0, animSteps = [];
const COLORS = {
q: '#4a90d9', // query matrix color
k: '#e67e22', // key matrix color
v: '#27ae60', // value matrix color
s: '#9b59b6', // score matrix color
hbm_write: '#e63946', // HBM write highlight
hbm_read: '#f4a261', // HBM read highlight
sram: '#2ecc71', // SRAM tile highlight
out: '#1d3557', // output matrix color
tile: 'rgba(230,57,70,0.18)', // active tile overlay
bg: '#f8fafc',
border: '#cdd9e3',
text: '#222',
};
function getN(){ return parseInt(document.getElementById('nslider').value); }
function getT(){ return parseInt(document.getElementById('tslider').value); }
// Build the list of memory-access steps for a given mode
function buildSteps(n, t, m){
const steps = [];
const tClamped = Math.min(t, n);
if(m === 'standard'){
// Step 1: read Q, K, V from HBM
steps.push({ type: 'hbm_read', label: 'read_qkv', rows: n });
// Step 2: write N×N score matrix S to HBM
steps.push({ type: 'hbm_write', label: 'write_s', rows: n });
// Step 3: read S back from HBM for softmax
steps.push({ type: 'hbm_read', label: 'read_s', rows: n });
// Step 4: write softmax(S) = P to HBM
steps.push({ type: 'hbm_write', label: 'write_p', rows: n });
// Step 5: read P back from HBM
steps.push({ type: 'hbm_read', label: 'read_p', rows: n });
// Step 6: write output O to HBM
steps.push({ type: 'hbm_write', label: 'write_o', rows: n });
} else {
// FlashAttention: loop over tiles, each tile stays in SRAM
const numTiles = Math.ceil(n / tClamped);
for(let i = 0; i < numTiles; i++){
// Load one tile of Q/K/V into SRAM
steps.push({ type: 'tile_sram', label: 'load_tile', tile: i, numTiles, rows: tClamped });
// Compute partial softmax + accumulate O in SRAM
steps.push({ type: 'tile_compute', label: 'compute_tile', tile: i, numTiles, rows: tClamped });
// Write only the O chunk back to HBM
steps.push({ type: 'tile_write_o', label: 'write_tile_o', tile: i, numTiles, rows: tClamped });
}
}
return steps;
}
// Count total HBM element reads + writes across all steps
function countHBM(steps){
let reads = 0, writes = 0;
for(const s of steps){
if(s.type === 'hbm_read') reads += s.rows * s.rows;
if(s.type === 'hbm_write') writes += s.rows * s.rows;
if(s.type === 'tile_sram') reads += s.rows * s.rows;
if(s.type === 'tile_write_o') writes += s.rows * s.rows;
}
return { reads, writes };
}
// Render the canvas: HBM grid on left, SRAM grid on right
function draw(step){
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const n = N, t = Math.min(T, n);
const cellSize = Math.min(Math.floor((W * 0.42) / n), 24);
const gridW = cellSize * n, gridH = cellSize * n;
const leftX = 16, topY = Math.floor((H - gridH) / 2);
const rightX = W - gridW - 16;
// Clear canvas background
ctx.fillStyle = COLORS.bg;
ctx.fillRect(0, 0, W, H);
// Draw HBM / SRAM section labels
ctx.font = 'bold 11px system-ui';
ctx.fillStyle = COLORS.text;
ctx.textAlign = 'center';
ctx.fillText('HBM ' + step.hbmLabel, leftX + gridW / 2, topY - 10);
ctx.fillText('SRAM', rightX + gridW / 2, topY - 10);
// Draw the HBM matrix grid with highlights
for(let r = 0; r < n; r++){
for(let c = 0; c < n; c++){
const x = leftX + c * cellSize, y = topY + r * cellSize;
let fill = '#e8eef3';
if(step.hbmHighlight){
if(step.hbmHighlight === 'all') fill = COLORS.hbm_read;
else if(step.hbmHighlight === 'write_s') fill = COLORS.hbm_write;
else if(step.hbmHighlight === 'write_p') fill = COLORS.s;
else if(step.hbmHighlight === 'write_o') fill = COLORS.out;
}
ctx.fillStyle = fill;
ctx.fillRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
ctx.strokeStyle = COLORS.border;
ctx.strokeRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
}
}
// Draw the SRAM tile grid with active-tile highlight
for(let r = 0; r < n; r++){
for(let c = 0; c < n; c++){
const x = rightX + c * cellSize, y = topY + r * cellSize;
let fill = '#e8eef3';
if(step.sramHighlight){
const inTile = step.tileRow !== undefined
? (r >= step.tileRow && r < step.tileRow + t)
: false;
if(inTile) fill = COLORS.sram;
}
ctx.fillStyle = fill;
ctx.fillRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
ctx.strokeStyle = COLORS.border;
ctx.strokeRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
}
}
// Draw a directional arrow between HBM and SRAM
if(step.arrowDir){
const ay = topY + gridH / 2;
const ax1 = step.arrowDir === 'right' ? leftX + gridW + 4 : rightX - 4;
const ax2 = step.arrowDir === 'right' ? rightX - 4 : leftX + gridW + 4;
ctx.strokeStyle = step.arrowDir === 'right' ? COLORS.sram : COLORS.hbm_read;
ctx.lineWidth = 2.2;
ctx.beginPath();
ctx.moveTo(ax1, ay);
ctx.lineTo(ax2, ay);
const dir = step.arrowDir === 'right' ? 1 : -1;
ctx.lineTo(ax2 - dir * 8, ay - 5);
ctx.moveTo(ax2, ay);
ctx.lineTo(ax2 - dir * 8, ay + 5);
ctx.stroke();
ctx.lineWidth = 1;
}
}
// Map an animation step to its visual parameters
function stepToVis(s, n, t){
if(s.type === 'hbm_read')
return { hbmLabel: '(S, read)', hbmHighlight: 'all', sramHighlight: false, arrowDir: 'right' };
if(s.type === 'hbm_write' && s.label === 'write_s')
return { hbmLabel: '(writing S)', hbmHighlight: 'write_s', sramHighlight: false, arrowDir: 'left' };
if(s.type === 'hbm_write' && s.label === 'write_p')
return { hbmLabel: '(writing P=softmax(S))', hbmHighlight: 'write_p', sramHighlight: false, arrowDir: 'left' };
if(s.type === 'hbm_write' && s.label === 'write_o')
return { hbmLabel: '(writing O)', hbmHighlight: 'write_o', sramHighlight: false, arrowDir: 'left' };
if(s.type === 'tile_sram')
return { hbmLabel: '(idle)', hbmHighlight: null, sramHighlight: true, tileRow: s.tile * t, arrowDir: 'right' };
if(s.type === 'tile_compute')
return { hbmLabel: '(idle)', hbmHighlight: null, sramHighlight: true, tileRow: s.tile * t, arrowDir: null };
if(s.type === 'tile_write_o')
return { hbmLabel: '(write O chunk)', hbmHighlight: 'write_o', sramHighlight: false, tileRow: s.tile * t, arrowDir: 'left' };
return { hbmLabel: '', hbmHighlight: null, sramHighlight: false, arrowDir: null };
}
const STEP_LABELS = {
read_qkv: 'Read Q, K, V from HBM',
write_s: 'Write S = QKᵀ to HBM',
read_s: 'Read S from HBM',
write_p: 'Write P = softmax(S) to HBM',
read_p: 'Read P from HBM',
write_o: 'Write O = PV to HBM',
load_tile: 'Load tile into SRAM',
compute_tile:'Compute tile in SRAM (online softmax)',
write_tile_o:'Write O chunk to HBM',
};
function updateStats(){
const n = getN(), t = getT();
const std = countHBM(buildSteps(n, t, 'standard'));
const fl = countHBM(buildSteps(n, t, 'flash'));
const stdTotal = std.reads + std.writes;
const flTotal = fl.reads + fl.writes;
const ratio = (stdTotal / flTotal).toFixed(1);
document.getElementById('stats').textContent =
(mode === 'standard'
? fmt('Standard attention: %stdTotal% HBM element accesses (reads + writes)', { stdTotal: stdTotal })
: fmt('FlashAttention: %flTotal% HBM element accesses — %ratio%× fewer than standard', { flTotal: flTotal, ratio: ratio }));
}
function reset(){
if(animFrame){ cancelAnimationFrame(animFrame); animFrame = null; }
animStep = 0; animSteps = [];
N = getN(); T = getT();
const vis = stepToVis({ type: 'hbm_read', label: 'read_qkv', rows: N }, N, Math.min(T,N));
draw({ ...vis, hbmHighlight: null, arrowDir: null, hbmLabel: '' });
document.getElementById('caption').textContent = 'Press ’Run animation’ to see each memory access step.';
updateStats();
}
function runAnimation(){
if(animFrame){ cancelAnimationFrame(animFrame); animFrame = null; }
N = getN(); T = getT();
animSteps = buildSteps(N, Math.min(T, N), mode);
animStep = 0;
function tick(){
if(animStep >= animSteps.length){ animFrame = null; return; }
const s = animSteps[animStep];
const vis = stepToVis(s, N, Math.min(T, N));
draw(vis);
const lbl = STEP_LABELS[s.label] || s.label;
document.getElementById('caption').textContent =
(mode === 'standard' ? 'Standard attention step:' : 'FlashAttention step:') + ' ' + lbl;
updateStats();
animStep++;
animFrame = setTimeout(tick, 650);
}
tick();
}
document.getElementById('nslider').addEventListener('input', function(){
document.getElementById('nval').textContent = this.value; reset();
});
document.getElementById('tslider').addEventListener('input', function(){
document.getElementById('tval').textContent = this.value; reset();
});
document.getElementById('btn-standard').addEventListener('click', function(){
mode = 'standard';
document.getElementById('btn-standard').classList.add('active');
document.getElementById('btn-flash').classList.remove('active');
reset();
});
document.getElementById('btn-flash').addEventListener('click', function(){
mode = 'flash';
document.getElementById('btn-flash').classList.add('active');
document.getElementById('btn-standard').classList.remove('active');
reset();
});
document.getElementById('btn-run').addEventListener('click', runAnimation);
document.getElementById('btn-reset').addEventListener('click', reset);
reset();
Comments
Loading comments...