The chart below shows empirical cross-entropy loss versus model size for a family of language models. The x-axis is model parameters (log scale), the y-axis is held-out loss (log scale).
<div class="controls">
<label>Noise: <input type="range" id="noise" min="0" max="0.15" step="0.005" value="0.04"></label>
<span id="noiseVal">0.04</span>
<button id="fitBtn" type="button">Fit power law</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<canvas id="chart" width="480" height="300"></canvas>
<div class="info" id="info">Adjust noise or click Fit power law.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; margin-bottom: .6rem; font-size: .9rem; }
label { display: flex; align-items: center; gap: .4rem; }
input[type=range] { width: 90px; }
button { font: 600 13px system-ui; padding: .38rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
canvas { display: block; border: 1px solid #dde3ea; border-radius: 10px; max-width: 100%; }
.info { margin-top: .5rem; font-size: .92rem; font-weight: 600; min-height: 1.3em; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Neural Scaling Laws demo: plot log(loss) vs log(params) and fit power law.
// Ground truth: L = C * N^(-alpha) with alpha = 0.076, C = 3.1 (Kaplan-style language model law).
// Model sizes sampled log-uniformly from 100K to 10B parameters (5 orders of magnitude).
const ALPHA_TRUE = 0.076; // true power-law exponent (Kaplan et al. GPT-3 regime)
const C_TRUE = 3.1; // loss coefficient (entropy of dataset floor ~2.0, this is above it)
const SIZES_LOG = [-6, -5.5, -5, -4.5, -4, -3.5, -3, -2.5, -2, -1.5, -1]; // log10(N/10^11)
// Param counts: 10^5, 10^5.5, ..., 10^10 (100K to 10B)
const PARAM_M = SIZES_LOG.map(l => Math.pow(10, l + 11)); // params = 10^(l+11)
let noiseLevel = 0.04;
let fitted = false;
let fitAlpha = null, fitC = null;
function truePoints() {
return PARAM_M.map(n => ({ n, loss: C_TRUE * Math.pow(n, -ALPHA_TRUE) }));
}
function noisyPoints() {
const rng = seededRandom(42 + Math.round(noiseLevel * 1000));
return truePoints().map(({ n, loss }) => {
const logLoss = Math.log(loss) + noiseLevel * (rng() - 0.5) * 6;
return { n, loss: Math.exp(logLoss) };
});
}
// Simple seeded PRNG (mulberry32)
function seededRandom(seed) {
let s = seed >>> 0;
return function() {
s += 0x6D2B79F5; let t = Math.imul(s ^ s >>> 15, 1 | s);
t ^= t + Math.imul(t ^ t >>> 7, 61 | t);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// Ordinary least squares in log-log space: log(L) = log(C) - alpha*log(N)
function fitPowerLaw(pts) {
const xs = pts.map(p => Math.log(p.n));
const ys = pts.map(p => Math.log(p.loss));
const n = xs.length;
const mx = xs.reduce((a,b)=>a+b,0)/n;
const my = ys.reduce((a,b)=>a+b,0)/n;
const sxx = xs.reduce((a,x)=>a+(x-mx)**2,0);
const sxy = xs.reduce((a,x,i)=>a+(x-mx)*(ys[i]-my),0);
const slope = sxy/sxx; // this is -alpha
const intercept = my - slope*mx; // this is log(C)
return { alpha: -slope, C: Math.exp(intercept) };
}
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
function draw() {
const pts = noisyPoints();
const W = canvas.width, H = canvas.height;
const pad = { l:54, r:20, t:18, b:40 };
ctx.clearRect(0, 0, W, H);
// Axis ranges in log10
const logNmin = Math.log10(Math.min(...PARAM_M));
const logNmax = Math.log10(Math.max(...PARAM_M));
const allLoss = pts.map(p=>p.loss);
if (fitted && fitAlpha !== null) {
const fitMin = fitC * Math.pow(Math.min(...PARAM_M), -fitAlpha);
const fitMax = fitC * Math.pow(Math.max(...PARAM_M), -fitAlpha);
allLoss.push(fitMin, fitMax);
}
const logLmin = Math.log10(Math.min(...allLoss)) - 0.05;
const logLmax = Math.log10(Math.max(...allLoss)) + 0.05;
function xPx(n) { return pad.l + (Math.log10(n)-logNmin)/(logNmax-logNmin)*(W-pad.l-pad.r); }
function yPx(loss) { return pad.t + (logLmax-Math.log10(loss))/(logLmax-logLmin)*(H-pad.t-pad.b); }
// Grid & axes
ctx.strokeStyle = '#e5eaf0'; ctx.lineWidth = 1;
// horizontal grid
for (let lv = Math.ceil(logLmin*10)/10; lv <= logLmax+0.01; lv += 0.1) {
const y = pad.t + (logLmax-lv)/(logLmax-logLmin)*(H-pad.t-pad.b);
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(W-pad.r, y); ctx.stroke();
}
// vertical grid (decade markers)
for (let lv = Math.ceil(logNmin); lv <= logNmax+0.01; lv += 1) {
const x = xPx(Math.pow(10,lv));
ctx.beginPath(); ctx.moveTo(x, pad.t); ctx.lineTo(x, H-pad.b); ctx.stroke();
}
// Axis lines
ctx.strokeStyle = '#9aa5b0'; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, H-pad.b); ctx.stroke();
ctx.beginPath(); ctx.moveTo(pad.l, H-pad.b); ctx.lineTo(W-pad.r, H-pad.b); ctx.stroke();
// Y axis labels
ctx.fillStyle = '#555'; ctx.font = '11px system-ui'; ctx.textAlign = 'right';
const lossLabels = [5, 4, 3.5, 3, 2.8, 2.6, 2.4, 2.2, 2.0];
lossLabels.forEach(v => {
if (Math.log10(v) >= logLmin && Math.log10(v) <= logLmax) {
const y = yPx(v);
ctx.fillText(v.toFixed(1), pad.l-5, y+4);
}
});
// X axis labels (params in millions/billions)
ctx.textAlign = 'center';
const paramLabels = [
{ n: 1e5, label:'100K' }, { n: 1e6, label:'1M' }, { n: 1e7, label:'10M' },
{ n: 1e8, label:'100M' }, { n: 1e9, label:'1B' }, { n: 1e10, label:'10B' }
];
paramLabels.forEach(({ n, label }) => {
if (Math.log10(n) >= logNmin-0.1 && Math.log10(n) <= logNmax+0.1) {
ctx.fillText(label, xPx(n), H-pad.b+16);
}
});
// Axis titles
ctx.save();
ctx.translate(13, H/2); ctx.rotate(-Math.PI/2);
ctx.font = '11px system-ui'; ctx.fillStyle = '#444'; ctx.textAlign = 'center';
ctx.fillText('Loss (log scale)', 0, 0);
ctx.restore();
ctx.font = '11px system-ui'; ctx.fillStyle = '#444'; ctx.textAlign = 'center';
ctx.fillText('Model parameters (log scale)', pad.l + (W-pad.l-pad.r)/2, H-2);
// Fit line
if (fitted && fitAlpha !== null) {
ctx.beginPath(); ctx.strokeStyle = '#e63946'; ctx.lineWidth = 2;
const x0 = Math.min(...PARAM_M), x1 = Math.max(...PARAM_M);
ctx.moveTo(xPx(x0), yPx(fitC * Math.pow(x0, -fitAlpha)));
ctx.lineTo(xPx(x1), yPx(fitC * Math.pow(x1, -fitAlpha)));
ctx.stroke();
// Slope annotation
ctx.fillStyle = '#e63946'; ctx.font = 'bold 12px system-ui'; ctx.textAlign = 'left';
ctx.fillText(fmt('slope = −%fitAlpha% (−α)', { fitAlpha: fitAlpha.toFixed(3) }), xPx(1e7)+4, yPx(fitC * Math.pow(1e7,-fitAlpha))-8);
}
// Data points
pts.forEach(({ n, loss }) => {
ctx.beginPath();
ctx.arc(xPx(n), yPx(loss), 5, 0, 2*Math.PI);
ctx.fillStyle = '#1d6fa5'; ctx.fill();
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
});
}
function update() { draw(); }
document.getElementById('noise').addEventListener('input', function() {
noiseLevel = parseFloat(this.value);
document.getElementById('noiseVal').textContent = noiseLevel.toFixed(3);
fitted = false; fitAlpha = null;
document.getElementById('info').textContent = 'Adjust noise or click Fit power law.';
draw();
});
document.getElementById('fitBtn').addEventListener('click', function() {
const result = fitPowerLaw(noisyPoints());
fitAlpha = result.alpha; fitC = result.C;
fitted = true;
draw();
document.getElementById('info').textContent =
fmt('Fit: L = %C% × N^(−%alpha%) | true α = %ALPHA_TRUE%', { C: result.C.toFixed(2), alpha: result.alpha.toFixed(3), ALPHA_TRUE: ALPHA_TRUE.toFixed(3) });
});
document.getElementById('resetBtn').addEventListener('click', function() {
fitted = false; fitAlpha = null;
noiseLevel = 0.04;
document.getElementById('noise').value = '0.04';
document.getElementById('noiseVal').textContent = '0.04';
document.getElementById('info').textContent = 'Adjust noise or click Fit power law.';
draw();
});
draw();
<div class="controls">
<label>Noise: <input type="range" id="noise" min="0" max="0.15" step="0.005" value="0.04"></label>
<span id="noiseVal">0.04</span>
<button id="fitBtn" type="button">Fit power law</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<canvas id="chart" width="480" height="300"></canvas>
<div class="info" id="info">Adjust noise or click Fit power law.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; margin-bottom: .6rem; font-size: .9rem; }
label { display: flex; align-items: center; gap: .4rem; }
input[type=range] { width: 90px; }
button { font: 600 13px system-ui; padding: .38rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
canvas { display: block; border: 1px solid #dde3ea; border-radius: 10px; max-width: 100%; }
.info { margin-top: .5rem; font-size: .92rem; font-weight: 600; min-height: 1.3em; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Neural Scaling Laws demo: plot log(loss) vs log(params) and fit power law.
// Ground truth: L = C * N^(-alpha) with alpha = 0.076, C = 3.1 (Kaplan-style language model law).
// Model sizes sampled log-uniformly from 100K to 10B parameters (5 orders of magnitude).
const ALPHA_TRUE = 0.076; // true power-law exponent (Kaplan et al. GPT-3 regime)
const C_TRUE = 3.1; // loss coefficient (entropy of dataset floor ~2.0, this is above it)
const SIZES_LOG = [-6, -5.5, -5, -4.5, -4, -3.5, -3, -2.5, -2, -1.5, -1]; // log10(N/10^11)
// Param counts: 10^5, 10^5.5, ..., 10^10 (100K to 10B)
const PARAM_M = SIZES_LOG.map(l => Math.pow(10, l + 11)); // params = 10^(l+11)
let noiseLevel = 0.04;
let fitted = false;
let fitAlpha = null, fitC = null;
function truePoints() {
return PARAM_M.map(n => ({ n, loss: C_TRUE * Math.pow(n, -ALPHA_TRUE) }));
}
function noisyPoints() {
const rng = seededRandom(42 + Math.round(noiseLevel * 1000));
return truePoints().map(({ n, loss }) => {
const logLoss = Math.log(loss) + noiseLevel * (rng() - 0.5) * 6;
return { n, loss: Math.exp(logLoss) };
});
}
// Simple seeded PRNG (mulberry32)
function seededRandom(seed) {
let s = seed >>> 0;
return function() {
s += 0x6D2B79F5; let t = Math.imul(s ^ s >>> 15, 1 | s);
t ^= t + Math.imul(t ^ t >>> 7, 61 | t);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// Ordinary least squares in log-log space: log(L) = log(C) - alpha*log(N)
function fitPowerLaw(pts) {
const xs = pts.map(p => Math.log(p.n));
const ys = pts.map(p => Math.log(p.loss));
const n = xs.length;
const mx = xs.reduce((a,b)=>a+b,0)/n;
const my = ys.reduce((a,b)=>a+b,0)/n;
const sxx = xs.reduce((a,x)=>a+(x-mx)**2,0);
const sxy = xs.reduce((a,x,i)=>a+(x-mx)*(ys[i]-my),0);
const slope = sxy/sxx; // this is -alpha
const intercept = my - slope*mx; // this is log(C)
return { alpha: -slope, C: Math.exp(intercept) };
}
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
function draw() {
const pts = noisyPoints();
const W = canvas.width, H = canvas.height;
const pad = { l:54, r:20, t:18, b:40 };
ctx.clearRect(0, 0, W, H);
// Axis ranges in log10
const logNmin = Math.log10(Math.min(...PARAM_M));
const logNmax = Math.log10(Math.max(...PARAM_M));
const allLoss = pts.map(p=>p.loss);
if (fitted && fitAlpha !== null) {
const fitMin = fitC * Math.pow(Math.min(...PARAM_M), -fitAlpha);
const fitMax = fitC * Math.pow(Math.max(...PARAM_M), -fitAlpha);
allLoss.push(fitMin, fitMax);
}
const logLmin = Math.log10(Math.min(...allLoss)) - 0.05;
const logLmax = Math.log10(Math.max(...allLoss)) + 0.05;
function xPx(n) { return pad.l + (Math.log10(n)-logNmin)/(logNmax-logNmin)*(W-pad.l-pad.r); }
function yPx(loss) { return pad.t + (logLmax-Math.log10(loss))/(logLmax-logLmin)*(H-pad.t-pad.b); }
// Grid & axes
ctx.strokeStyle = '#e5eaf0'; ctx.lineWidth = 1;
// horizontal grid
for (let lv = Math.ceil(logLmin*10)/10; lv <= logLmax+0.01; lv += 0.1) {
const y = pad.t + (logLmax-lv)/(logLmax-logLmin)*(H-pad.t-pad.b);
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(W-pad.r, y); ctx.stroke();
}
// vertical grid (decade markers)
for (let lv = Math.ceil(logNmin); lv <= logNmax+0.01; lv += 1) {
const x = xPx(Math.pow(10,lv));
ctx.beginPath(); ctx.moveTo(x, pad.t); ctx.lineTo(x, H-pad.b); ctx.stroke();
}
// Axis lines
ctx.strokeStyle = '#9aa5b0'; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, H-pad.b); ctx.stroke();
ctx.beginPath(); ctx.moveTo(pad.l, H-pad.b); ctx.lineTo(W-pad.r, H-pad.b); ctx.stroke();
// Y axis labels
ctx.fillStyle = '#555'; ctx.font = '11px system-ui'; ctx.textAlign = 'right';
const lossLabels = [5, 4, 3.5, 3, 2.8, 2.6, 2.4, 2.2, 2.0];
lossLabels.forEach(v => {
if (Math.log10(v) >= logLmin && Math.log10(v) <= logLmax) {
const y = yPx(v);
ctx.fillText(v.toFixed(1), pad.l-5, y+4);
}
});
// X axis labels (params in millions/billions)
ctx.textAlign = 'center';
const paramLabels = [
{ n: 1e5, label:'100K' }, { n: 1e6, label:'1M' }, { n: 1e7, label:'10M' },
{ n: 1e8, label:'100M' }, { n: 1e9, label:'1B' }, { n: 1e10, label:'10B' }
];
paramLabels.forEach(({ n, label }) => {
if (Math.log10(n) >= logNmin-0.1 && Math.log10(n) <= logNmax+0.1) {
ctx.fillText(label, xPx(n), H-pad.b+16);
}
});
// Axis titles
ctx.save();
ctx.translate(13, H/2); ctx.rotate(-Math.PI/2);
ctx.font = '11px system-ui'; ctx.fillStyle = '#444'; ctx.textAlign = 'center';
ctx.fillText('Loss (log scale)', 0, 0);
ctx.restore();
ctx.font = '11px system-ui'; ctx.fillStyle = '#444'; ctx.textAlign = 'center';
ctx.fillText('Model parameters (log scale)', pad.l + (W-pad.l-pad.r)/2, H-2);
// Fit line
if (fitted && fitAlpha !== null) {
ctx.beginPath(); ctx.strokeStyle = '#e63946'; ctx.lineWidth = 2;
const x0 = Math.min(...PARAM_M), x1 = Math.max(...PARAM_M);
ctx.moveTo(xPx(x0), yPx(fitC * Math.pow(x0, -fitAlpha)));
ctx.lineTo(xPx(x1), yPx(fitC * Math.pow(x1, -fitAlpha)));
ctx.stroke();
// Slope annotation
ctx.fillStyle = '#e63946'; ctx.font = 'bold 12px system-ui'; ctx.textAlign = 'left';
ctx.fillText(fmt('slope = −%fitAlpha% (−α)', { fitAlpha: fitAlpha.toFixed(3) }), xPx(1e7)+4, yPx(fitC * Math.pow(1e7,-fitAlpha))-8);
}
// Data points
pts.forEach(({ n, loss }) => {
ctx.beginPath();
ctx.arc(xPx(n), yPx(loss), 5, 0, 2*Math.PI);
ctx.fillStyle = '#1d6fa5'; ctx.fill();
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
});
}
function update() { draw(); }
document.getElementById('noise').addEventListener('input', function() {
noiseLevel = parseFloat(this.value);
document.getElementById('noiseVal').textContent = noiseLevel.toFixed(3);
fitted = false; fitAlpha = null;
document.getElementById('info').textContent = 'Adjust noise or click Fit power law.';
draw();
});
document.getElementById('fitBtn').addEventListener('click', function() {
const result = fitPowerLaw(noisyPoints());
fitAlpha = result.alpha; fitC = result.C;
fitted = true;
draw();
document.getElementById('info').textContent =
fmt('Fit: L = %C% × N^(−%alpha%) | true α = %ALPHA_TRUE%', { C: result.C.toFixed(2), alpha: result.alpha.toFixed(3), ALPHA_TRUE: ALPHA_TRUE.toFixed(3) });
});
document.getElementById('resetBtn').addEventListener('click', function() {
fitted = false; fitAlpha = null;
noiseLevel = 0.04;
document.getElementById('noise').value = '0.04';
document.getElementById('noiseVal').textContent = '0.04';
document.getElementById('info').textContent = 'Adjust noise or click Fit power law.';
draw();
});
draw();
Comments
Loading comments...