Below is a small vector of model weights. Adjust the bit-width slider to quantize them — the bars show the original float values and the rounded integer approximations. The mean absolute error updates live.
<!-- Controls: bit-width slider and outlier toggle -->
<div class="controls">
<label>
<span class="lbl">Bits</span>
<input type="range" id="bits" min="2" max="8" step="2" value="8">
<span id="bits-val" class="badge">8</span>
</label>
<label class="toggle-row">
<span class="lbl">Add outlier</span>
<input type="checkbox" id="outlier" role="switch">
</label>
</div>
<div class="chart" id="chart"></div>
<div class="stats" id="stats"></div>
<button id="resample" type="button">New weights</button>
/* Layout for the quantization demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; flex-wrap: wrap; gap: .6rem 1.4rem; margin-bottom: .8rem; align-items: center; }
label { display: flex; align-items: center; gap: .4rem; font-size: .9rem; }
.lbl { font-weight: 600; }
input[type=range] { width: 120px; accent-color: #1d3557; }
.badge { background: #1d3557; color: #fff; border-radius: 4px; padding: 1px 6px; font-size: .85rem; min-width: 2ch; text-align: center; }
.toggle-row input[type=checkbox] { width: 36px; height: 20px; appearance: none; background: #ccc; border-radius: 10px; cursor: pointer; transition: background .2s; position: relative; }
.toggle-row input[type=checkbox]:checked { background: #e63946; }
.toggle-row input[type=checkbox]::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: transform .2s; }
.toggle-row input[type=checkbox]:checked::after { transform: translateX(16px); }
/* Bar chart: blue = original float, red = quantized value */
.chart { display: flex; align-items: flex-end; gap: 3px; height: 160px; border-bottom: 2px solid #ccc; padding-bottom: 2px; overflow-x: auto; }
.bar-group { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.bar-wrap { display: flex; gap: 2px; align-items: flex-end; height: 140px; }
.bar { width: 14px; border-radius: 3px 3px 0 0; min-height: 2px; transition: height .25s, background .25s; }
.bar.orig { background: #457b9d; }
.bar.quant { background: #e63946; opacity: .82; }
.bar-idx { font-size: .65rem; color: #888; margin-top: 1px; }
.stats { font-size: .9rem; margin: .5rem 0; min-height: 1.2em; }
.stats strong { color: #1d3557; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; margin-top: .3rem; }
.outlier-bar { background: #f4a261 !important; }
@media (prefers-color-scheme: dark) {
body { color: #e0e0e0; }
.bar.orig { background: #74b3ce; }
.bar.quant { background: #ff6b6b; }
.stats strong { color: #9fb2c8; }
button { border-color: #74b3ce; background: #74b3ce; color: #111; }
}
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Weight quantization demo
// Shows how rounding precision degrades with fewer bits, and how one outlier forces a wider scale that hurts all weights.
const N_WEIGHTS = 14;
const OUTLIER_IDX = 4; // index of the outlier weight in the vector
// Generate normally distributed weights (mean=0, std=0.5)
function randn() {
let u = 0, v = 0;
while (u === 0) u = Math.random();
while (v === 0) v = Math.random();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
let weights = Array.from({ length: N_WEIGHTS }, () => randn() * 0.5);
// Uniform quantization: map weights to 2^bits evenly spaced levels
function quantize(vals, bits, hasOutlier) {
const levels = Math.pow(2, bits); // number of integer levels in the quantization grid
const arr = hasOutlier
? vals.map((v, i) => i === OUTLIER_IDX ? v * 10 : v)
: [...vals];
const mn = Math.min(...arr), mx = Math.max(...arr);
const scale = (mx - mn) / (levels - 1) || 1; // step size between levels
return arr.map(v => mn + Math.round((v - mn) / scale) * scale);
}
// Re-draw the chart whenever a control changes
function render() {
const bits = parseInt(document.getElementById('bits').value);
const hasOutlier = document.getElementById('outlier').checked;
document.getElementById('bits-val').textContent = bits;
const orig = hasOutlier
? weights.map((v, i) => i === OUTLIER_IDX ? v * 10 : v)
: [...weights];
const quant = quantize(weights, bits, hasOutlier);
// Mean Absolute Error between original and quantized values
const mae = orig.reduce((s, v, i) => s + Math.abs(v - quant[i]), 0) / orig.length;
const maxAbs = Math.max(...orig.map(Math.abs), ...quant.map(Math.abs), 0.01);
const chart = document.getElementById('chart');
chart.innerHTML = '';
orig.forEach((v, i) => {
const group = document.createElement('div');
group.className = 'bar-group';
const wrap = document.createElement('div');
wrap.className = 'bar-wrap';
const bOrig = document.createElement('div');
bOrig.className = 'bar orig' + (hasOutlier && i === OUTLIER_IDX ? ' outlier-bar' : '');
bOrig.style.height = (Math.abs(v) / maxAbs * 130) + 'px';
bOrig.title = fmt('Original: %v%', { v: v.toFixed(3) });
const bQuant = document.createElement('div');
bQuant.className = 'bar quant';
bQuant.style.height = (Math.abs(quant[i]) / maxAbs * 130) + 'px';
bQuant.title = fmt('Quantized: %v%', { v: quant[i].toFixed(3) });
wrap.appendChild(bOrig);
wrap.appendChild(bQuant);
const idx = document.createElement('div');
idx.className = 'bar-idx';
idx.textContent = i;
group.appendChild(wrap);
group.appendChild(idx);
chart.appendChild(group);
});
// Update the stats line below the chart
const stats = document.getElementById('stats');
stats.innerHTML = fmt('<strong>Bits:</strong> %bits% <strong>Mean Abs Error:</strong> %mae% <strong>Levels:</strong> %pow%%v%', { bits: bits, mae: mae.toFixed(4), pow: Math.pow(2, bits), v: hasOutlier ? ` <span style="color:#e07020">⚠ Outlier active — scale forced wide</span>` : '' });
console.log(fmt('Quantization: bits=%bits% outlier=%hasOutlier% mae=%mae%', { bits: bits, hasOutlier: hasOutlier, mae: mae.toFixed(4) }));
}
document.getElementById('bits').addEventListener('input', render);
document.getElementById('outlier').addEventListener('change', render);
document.getElementById('resample').addEventListener('click', () => {
weights = Array.from({ length: N_WEIGHTS }, () => randn() * 0.5);
render();
});
render();
<!-- Controls: bit-width slider and outlier toggle -->
<div class="controls">
<label>
<span class="lbl">Bits</span>
<input type="range" id="bits" min="2" max="8" step="2" value="8">
<span id="bits-val" class="badge">8</span>
</label>
<label class="toggle-row">
<span class="lbl">Add outlier</span>
<input type="checkbox" id="outlier" role="switch">
</label>
</div>
<div class="chart" id="chart"></div>
<div class="stats" id="stats"></div>
<button id="resample" type="button">New weights</button>
/* Layout for the quantization demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; flex-wrap: wrap; gap: .6rem 1.4rem; margin-bottom: .8rem; align-items: center; }
label { display: flex; align-items: center; gap: .4rem; font-size: .9rem; }
.lbl { font-weight: 600; }
input[type=range] { width: 120px; accent-color: #1d3557; }
.badge { background: #1d3557; color: #fff; border-radius: 4px; padding: 1px 6px; font-size: .85rem; min-width: 2ch; text-align: center; }
.toggle-row input[type=checkbox] { width: 36px; height: 20px; appearance: none; background: #ccc; border-radius: 10px; cursor: pointer; transition: background .2s; position: relative; }
.toggle-row input[type=checkbox]:checked { background: #e63946; }
.toggle-row input[type=checkbox]::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: transform .2s; }
.toggle-row input[type=checkbox]:checked::after { transform: translateX(16px); }
/* Bar chart: blue = original float, red = quantized value */
.chart { display: flex; align-items: flex-end; gap: 3px; height: 160px; border-bottom: 2px solid #ccc; padding-bottom: 2px; overflow-x: auto; }
.bar-group { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.bar-wrap { display: flex; gap: 2px; align-items: flex-end; height: 140px; }
.bar { width: 14px; border-radius: 3px 3px 0 0; min-height: 2px; transition: height .25s, background .25s; }
.bar.orig { background: #457b9d; }
.bar.quant { background: #e63946; opacity: .82; }
.bar-idx { font-size: .65rem; color: #888; margin-top: 1px; }
.stats { font-size: .9rem; margin: .5rem 0; min-height: 1.2em; }
.stats strong { color: #1d3557; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; margin-top: .3rem; }
.outlier-bar { background: #f4a261 !important; }
@media (prefers-color-scheme: dark) {
body { color: #e0e0e0; }
.bar.orig { background: #74b3ce; }
.bar.quant { background: #ff6b6b; }
.stats strong { color: #9fb2c8; }
button { border-color: #74b3ce; background: #74b3ce; color: #111; }
}
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Weight quantization demo
// Shows how rounding precision degrades with fewer bits, and how one outlier forces a wider scale that hurts all weights.
const N_WEIGHTS = 14;
const OUTLIER_IDX = 4; // index of the outlier weight in the vector
// Generate normally distributed weights (mean=0, std=0.5)
function randn() {
let u = 0, v = 0;
while (u === 0) u = Math.random();
while (v === 0) v = Math.random();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
let weights = Array.from({ length: N_WEIGHTS }, () => randn() * 0.5);
// Uniform quantization: map weights to 2^bits evenly spaced levels
function quantize(vals, bits, hasOutlier) {
const levels = Math.pow(2, bits); // number of integer levels in the quantization grid
const arr = hasOutlier
? vals.map((v, i) => i === OUTLIER_IDX ? v * 10 : v)
: [...vals];
const mn = Math.min(...arr), mx = Math.max(...arr);
const scale = (mx - mn) / (levels - 1) || 1; // step size between levels
return arr.map(v => mn + Math.round((v - mn) / scale) * scale);
}
// Re-draw the chart whenever a control changes
function render() {
const bits = parseInt(document.getElementById('bits').value);
const hasOutlier = document.getElementById('outlier').checked;
document.getElementById('bits-val').textContent = bits;
const orig = hasOutlier
? weights.map((v, i) => i === OUTLIER_IDX ? v * 10 : v)
: [...weights];
const quant = quantize(weights, bits, hasOutlier);
// Mean Absolute Error between original and quantized values
const mae = orig.reduce((s, v, i) => s + Math.abs(v - quant[i]), 0) / orig.length;
const maxAbs = Math.max(...orig.map(Math.abs), ...quant.map(Math.abs), 0.01);
const chart = document.getElementById('chart');
chart.innerHTML = '';
orig.forEach((v, i) => {
const group = document.createElement('div');
group.className = 'bar-group';
const wrap = document.createElement('div');
wrap.className = 'bar-wrap';
const bOrig = document.createElement('div');
bOrig.className = 'bar orig' + (hasOutlier && i === OUTLIER_IDX ? ' outlier-bar' : '');
bOrig.style.height = (Math.abs(v) / maxAbs * 130) + 'px';
bOrig.title = fmt('Original: %v%', { v: v.toFixed(3) });
const bQuant = document.createElement('div');
bQuant.className = 'bar quant';
bQuant.style.height = (Math.abs(quant[i]) / maxAbs * 130) + 'px';
bQuant.title = fmt('Quantized: %v%', { v: quant[i].toFixed(3) });
wrap.appendChild(bOrig);
wrap.appendChild(bQuant);
const idx = document.createElement('div');
idx.className = 'bar-idx';
idx.textContent = i;
group.appendChild(wrap);
group.appendChild(idx);
chart.appendChild(group);
});
// Update the stats line below the chart
const stats = document.getElementById('stats');
stats.innerHTML = fmt('<strong>Bits:</strong> %bits% <strong>Mean Abs Error:</strong> %mae% <strong>Levels:</strong> %pow%%v%', { bits: bits, mae: mae.toFixed(4), pow: Math.pow(2, bits), v: hasOutlier ? ` <span style="color:#e07020">⚠ Outlier active — scale forced wide</span>` : '' });
console.log(fmt('Quantization: bits=%bits% outlier=%hasOutlier% mae=%mae%', { bits: bits, hasOutlier: hasOutlier, mae: mae.toFixed(4) }));
}
document.getElementById('bits').addEventListener('input', render);
document.getElementById('outlier').addEventListener('change', render);
document.getElementById('resample').addEventListener('click', () => {
weights = Array.from({ length: N_WEIGHTS }, () => randn() * 0.5);
render();
});
render();
Notice how the error stays low at 8-bit but jumps sharply at 4-bit and 2-bit. Now toggle the outlier switch: a single weight jumps to 10× the normal range. Because the quantization grid must now span a much wider range, every ordinary weight gets rounded more coarsely — and the error spikes across the whole vector. This is the outlier problem that LLM.int8() solves by handling outlier channels in float16 and only quantizing the rest.
Comments
Loading comments...