Download index.html from abhimittal/quantization-error-propagation-viewer: direct link, hf CLI and curl.
- Browser
- Download file 8.52 kB
-
https://huggingface.co/spaces/abhimittal/quantization-error-propagation-viewer/resolve/main/index.html
- Command line
-
hf download hf://spaces/abhimittal/quantization-error-propagation-viewer/index.html
-
curl -L -o index.html https://huggingface.co/spaces/abhimittal/quantization-error-propagation-viewer/resolve/main/index.html
8.52 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Quantization Error Propagation Viewer</title> | |
| <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔬</text></svg>"> | |
| <script src="plotly.min.js"></script> | |
| <style> | |
| :root { --fg: #1a1a2e; --muted: #555; --card: #fff; --bg: #f4f4fa; } | |
| body { font-family: system-ui, sans-serif; margin: 0; background: var(--bg); color: var(--fg); } | |
| header { background: linear-gradient(90deg, #3f3f8f, #7b4fb5); color: #fff; padding: 1.2rem 2rem; } | |
| header h1 { margin: 0 0 .3rem; font-size: 1.5rem; } | |
| header p { margin: 0; opacity: .9; max-width: 60rem; } | |
| main { max-width: 1200px; margin: 0 auto; padding: 1rem 1.5rem 3rem; } | |
| .controls { display: flex; gap: 1.5rem; flex-wrap: wrap; margin: 1rem 0; align-items: center; } | |
| .controls label { font-weight: 600; margin-right: .4rem; } | |
| select { padding: .35rem .5rem; border-radius: 6px; border: 1px solid #bbb; font-size: 1rem; } | |
| .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } | |
| @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } } | |
| .card { background: var(--card); border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,.08); padding: .5rem; } | |
| table { border-collapse: collapse; margin: 1rem 0; width: 100%; background: var(--card); border-radius: 10px; overflow: hidden; } | |
| th, td { padding: .5rem .8rem; text-align: left; border-bottom: 1px solid #eee; } | |
| th { background: #ecebf7; } | |
| .pedagogy { background: var(--card); border-radius: 10px; padding: 1rem 1.5rem; margin-top: 1.5rem; line-height: 1.55; } | |
| .pedagogy h3 { margin-top: 1.2rem; } | |
| .note { color: var(--muted); font-size: .9rem; } | |
| code { background: #eee; padding: .1rem .3rem; border-radius: 4px; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>🔬 Quantization Error Propagation Viewer</h1> | |
| <p>How INT8/INT4 error accumulates layer by layer through a real model on real | |
| activations — and the outlier structure that motivates SmoothQuant & AWQ. | |
| Precomputed static demo; the repo includes a fully interactive Gradio app | |
| (custom prompts, any Linear inspectable) you can run locally.</p> | |
| </header> | |
| <main> | |
| <div class="controls"> | |
| <span><label for="model">Model</label><select id="model"></select></span> | |
| <span><label for="alpha">α (migration strength)</label><select id="alpha"></select></span> | |
| <span><label for="module">Calibration profile of</label><select id="module"></select></span> | |
| </div> | |
| <p class="note" id="prompt"></p> | |
| <div class="grid"> | |
| <div class="card"><div id="err"></div></div> | |
| <div class="card"><div id="heat"></div></div> | |
| <div class="card"><div id="cos"></div></div> | |
| <div class="card"><div id="kurt"></div></div> | |
| </div> | |
| <table id="summary"></table> | |
| <div class="card"><div id="profile"></div></div> | |
| <div class="pedagogy" id="pedagogy"> | |
| <h2>📚 What am I looking at?</h2> | |
| <h3>Error propagation</h3> | |
| <p>Each quantized forward pass is compared with the FP32 reference at every | |
| transformer layer. Errors <em>compound</em>: a perturbation in layer 1 is | |
| amplified by every subsequent matmul, LayerNorm and attention mixing step — | |
| so the curves trend upward, and INT4 (16 levels) sits far above INT8 (256).</p> | |
| <h3>Where outliers live</h3> | |
| <p>The heatmap shows per-channel max |activation| after each layer. A few | |
| <em>fixed channels</em> carry values 10–100× larger than the rest (bright | |
| vertical stripes). They emerge in early-middle layers and persist — they | |
| ride the residual stream. With per-tensor activation quantization, one | |
| outlier channel sets the scale for <em>all</em> channels, crushing the rest | |
| into a few integer levels. That's why <b>W8A8 naive</b> is so much worse | |
| than <b>INT8 weight-only</b>.</p> | |
| <h3>Why SmoothQuant targets what it targets</h3> | |
| <p>Outliers live in <em>activations</em>, in <em>specific channels</em>; | |
| weights are flat and easy. SmoothQuant migrates difficulty: divide | |
| activations by <code>s_j = |X_j|max^α / |W_j|max^(1−α)</code> and multiply | |
| the matching weight columns by <code>s_j</code> — a mathematical no-op that | |
| makes both tensors "medium hard" instead of one impossible + one trivial. | |
| Compare <b>W8A8 naive</b> vs <b>W8A8 + SmoothQuant</b>: same bits, very | |
| different error. Try α = 0.3 / 0.5 / 0.7 above.</p> | |
| <h3>Why AWQ targets what it targets</h3> | |
| <p>For weight-only INT4, the weight columns that multiply <em>large | |
| activations</em> (the same outlier channels!) contribute most of the output | |
| error. AWQ scales those salient columns up before rounding — shrinking their | |
| relative rounding error — and folds the inverse scale into the activation | |
| path. Compare <b>INT4 g128</b> vs <b>INT4 g128 + AWQ-style</b> at α = 0.3. | |
| Push α too high and the boost itself inflates the group scales and error | |
| grows again — precisely why real AWQ <em>searches</em> for the best | |
| per-layer scale instead of fixing α. (INT4 here is group-wise, g = 128, as | |
| deployed in practice.)</p> | |
| <h3>Kurtosis</h3> | |
| <p>Excess kurtosis of hidden states per layer (0 = Gaussian). Values in the | |
| thousands are the statistical fingerprint of extreme outlier channels.</p> | |
| <p class="note">All quantization is simulated ("fake quant"): round-to-nearest | |
| onto a symmetric integer grid in float32 — exactly the error a real integer | |
| kernel introduces. Source: | |
| <a href="https://github.com/aabhimittal/quantization-error-propagation-viewer">GitHub</a>.</p> | |
| </div> | |
| </main> | |
| <script> | |
| const LAYOUT = { template: "plotly_white", margin: {l:55,r:15,t:45,b:45}, height: 380 }; | |
| let DATA; | |
| function runFor(model, alpha) { | |
| return DATA.runs.find(r => r.model === model && r.alpha === +alpha); | |
| } | |
| function fillSelect(el, values, keep) { | |
| const prev = keep ? el.value : null; | |
| el.innerHTML = values.map(v => `<option>${v}</option>`).join(""); | |
| if (prev && values.includes(prev)) el.value = prev; | |
| } | |
| function render() { | |
| const run = runFor(model.value, alpha.value); | |
| fillSelect(module, Object.keys(run.profiles), true); | |
| const layers = Array.from({length: run.n_layers}, (_, i) => i + 1); | |
| const lines = key => run.schemes.map(s => ({x: layers, y: s[key], mode: "lines+markers", name: s.label})); | |
| Plotly.react("err", lines("rel_err"), {...LAYOUT, | |
| title: "Relative error of hidden states, layer by layer", | |
| xaxis: {title: "Layer"}, yaxis: {title: "‖h_q − h_ref‖ / ‖h_ref‖", type: "log"}}); | |
| Plotly.react("cos", lines("cos_sim"), {...LAYOUT, | |
| title: "Cosine similarity to reference", | |
| xaxis: {title: "Layer"}, yaxis: {title: "cos(h_q, h_ref)"}}); | |
| Plotly.react("heat", [{z: run.hidden_amax.map(row => row.map(v => Math.log10(Math.max(v, 1e-6)))), | |
| type: "heatmap", colorscale: "Inferno", colorbar: {title: "log10 |h|max"}}], | |
| {...LAYOUT, title: "Where outliers live: per-channel |activation|max", | |
| xaxis: {title: "Hidden channel"}, yaxis: {title: "Layer"}}); | |
| Plotly.react("kurt", [{x: layers, y: run.kurtosis, mode: "lines+markers"}], | |
| {...LAYOUT, title: "Excess kurtosis of hidden states (0 = Gaussian)", | |
| xaxis: {title: "Layer"}, yaxis: {title: "Excess kurtosis"}}); | |
| renderProfile(run); | |
| summary.innerHTML = | |
| "<tr><th>Scheme</th><th>Final-layer rel. error</th><th>Top-1 agreement</th><th>KL(ref‖quant)</th></tr>" + | |
| run.schemes.map(s => | |
| `<tr><td>${s.label}</td><td>${s.rel_err.at(-1).toFixed(4)}</td>` + | |
| `<td>${(s.top1 * 100).toFixed(1)}%</td><td>${s.kl.toFixed(4)}</td></tr>`).join(""); | |
| } | |
| function renderProfile(run) { | |
| run = run || runFor(model.value, alpha.value); | |
| const y = run.profiles[module.value]; | |
| Plotly.react("profile", [{y, mode: "lines", line: {width: 1}}], | |
| {...LAYOUT, height: 340, | |
| title: `Input-channel |x|max into ${module.value} — the profile SmoothQuant/AWQ calibrate on`, | |
| xaxis: {title: "Input channel"}, yaxis: {title: "|x|max", type: "log"}}); | |
| } | |
| fetch("data.json").then(r => r.json()).then(d => { | |
| DATA = d; | |
| document.getElementById("prompt").textContent = `Prompt: “${d.prompt}”`; | |
| fillSelect(model, [...new Set(d.runs.map(r => r.model))]); | |
| fillSelect(alpha, [...new Set(d.runs.map(r => r.alpha))].sort()); | |
| alpha.value = "0.5"; | |
| model.onchange = render; alpha.onchange = render; | |
| module.onchange = () => renderProfile(); | |
| render(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |