Download index.html from ecs-lab/SIMPLE: direct link, hf CLI and curl.
- Browser
- Download file 19.5 kB
-
https://huggingface.co/spaces/ecs-lab/SIMPLE/resolve/main/index.html
- Command line
-
hf download hf://spaces/ecs-lab/SIMPLE/index.html
-
curl -L -o index.html https://huggingface.co/spaces/ecs-lab/SIMPLE/resolve/main/index.html
19.5 kB
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>SIMPLE Law — Energy-per-Token Demo</title> | |
| <script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.js"></script> | |
| <style> | |
| /* Fluid base size: every rem below scales with the viewport width | |
| (14px floor on phones, 18px cap on large screens) */ | |
| html { | |
| font-size: clamp(14px, 0.75rem + 0.35vw, 18px); | |
| } | |
| /* Content column: grows with the screen but keeps empty space on both | |
| sides (at least 6% of the width per side, and never wider than 1280px) */ | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; | |
| color: #1d1d1b; | |
| background: white; | |
| max-width: 1280px; | |
| margin: 0 auto; | |
| padding: 24px clamp(16px, 6vw, 120px); | |
| overflow-wrap: break-word; | |
| } | |
| :root { | |
| --accent: #1a9850; | |
| --accent-soft: #e8f5ec; | |
| --border: #e2e2e0; | |
| --muted: #6b6b68; | |
| --surface: #fafaf9; | |
| } | |
| .card { | |
| background: white; | |
| border: 1px solid white; | |
| border-radius: 14px; | |
| padding: clamp(18px, 2.2vw, 32px) clamp(20px, 3vw, 44px); | |
| margin-top: 24px; | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); | |
| } | |
| .cardHeader { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| } | |
| .cardHeader h3 { | |
| margin: 0; | |
| font-size: 1.0625rem; | |
| } | |
| .status { | |
| font-size: 0.8125rem; | |
| padding: 4px 12px; | |
| border-radius: 999px; | |
| border: 1px solid transparent; | |
| overflow-wrap: anywhere; | |
| } | |
| .status:empty { | |
| display: none; | |
| } | |
| .status.ok { | |
| background: var(--accent-soft); | |
| color: #146c3a; | |
| border-color: #bfe3cb; | |
| } | |
| .status.loading, | |
| .status.custom { | |
| background: #f1f1ef; | |
| color: var(--muted); | |
| border-color: var(--border); | |
| } | |
| .status.error { | |
| background: #fdecea; | |
| color: #a52a1f; | |
| border-color: #f5c6c0; | |
| } | |
| .field { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| min-width: 0; | |
| } | |
| .field label, | |
| .sliderHeader label { | |
| font-size: 0.8125rem; | |
| font-weight: 600; | |
| color: #333; | |
| } | |
| .hint { | |
| font-weight: 400; | |
| color: var(--muted); | |
| margin-left: 4px; | |
| } | |
| .card select, | |
| .card input[type="text"], | |
| .card input[type="number"] { | |
| font: inherit; | |
| font-size: 0.875rem; | |
| height: 38px; | |
| padding: 0 12px; | |
| border: 1px solid #cfcfcc; | |
| border-radius: 8px; | |
| background: white; | |
| width: 100%; | |
| box-sizing: border-box; | |
| transition: border-color 0.15s, box-shadow 0.15s; | |
| } | |
| .card select:focus, | |
| .card input[type="text"]:focus, | |
| .card input[type="number"]:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px rgba(26, 152, 80, 0.15); | |
| } | |
| .sources { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr) auto minmax(0, 1.4fr); | |
| gap: 16px; | |
| align-items: end; | |
| margin-top: 18px; | |
| } | |
| .divider { | |
| color: var(--muted); | |
| font-size: 0.8125rem; | |
| height: 38px; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .inputGroup { | |
| display: flex; | |
| } | |
| .inputGroup input[type="text"] { | |
| border-radius: 8px 0 0 8px; | |
| } | |
| .inputGroup button { | |
| font: inherit; | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| height: 38px; | |
| padding: 0 18px; | |
| border: 1px solid var(--accent); | |
| border-radius: 0 8px 8px 0; | |
| background: var(--accent); | |
| color: white; | |
| cursor: pointer; | |
| margin-left: -1px; | |
| } | |
| .inputGroup button:hover { | |
| background: #137a40; | |
| } | |
| .sectionLabel { | |
| margin: 22px 0 10px; | |
| font-size: 0.75rem; | |
| letter-spacing: 0.06em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| } | |
| .archGrid { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 16px; | |
| } | |
| #archSummary { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| margin-top: 12px; | |
| } | |
| .chip { | |
| font-size: 0.75rem; | |
| padding: 3px 10px; | |
| border-radius: 999px; | |
| background: white; | |
| border: 1px solid var(--border); | |
| color: #444; | |
| } | |
| @media (max-width: 700px) { | |
| .sources { | |
| grid-template-columns: 1fr; | |
| } | |
| .divider { | |
| height: auto; | |
| } | |
| .archGrid { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| } | |
| } | |
| /* Input-token slider */ | |
| #ninWrapper { | |
| width: 100%; | |
| margin-bottom: 22px; | |
| padding-bottom: 18px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .sliderHeader { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .badge { | |
| font-size: 0.8125rem; | |
| font-weight: 600; | |
| font-variant-numeric: tabular-nums; | |
| padding: 2px 10px; | |
| border-radius: 6px; | |
| background: var(--accent-soft); | |
| color: #146c3a; | |
| } | |
| #ninSlider { | |
| width: 100%; | |
| display: block; | |
| margin-top: 10px; | |
| accent-color: var(--accent); | |
| } | |
| #ninLabels { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 0.75rem; | |
| color: var(--muted); | |
| margin-top: 3px; | |
| } | |
| /* Plots side by side; the component plot is the narrower one */ | |
| #plots { | |
| display: flex; | |
| gap: 20px; | |
| flex-wrap: wrap; | |
| margin-top: 20px; | |
| } | |
| #plotPanel { | |
| flex: 2 1 380px; | |
| min-width: 0; | |
| } | |
| #contourPanel { | |
| flex: 3 1 520px; | |
| min-width: 0; | |
| } | |
| /* ---------- Header ---------- */ | |
| .hero { | |
| margin: 8px 0 24px; | |
| } | |
| .eyebrow { | |
| font-size: 0.75rem; | |
| font-weight: 700; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--accent); | |
| } | |
| .hero h1 { | |
| font-size: 1.875rem; | |
| line-height: 1.2; | |
| margin: 6px 0 8px; | |
| letter-spacing: -0.01em; | |
| } | |
| .lede { | |
| margin: 0; | |
| color: var(--muted); | |
| font-size: 0.9375rem; | |
| line-height: 1.5; | |
| } | |
| /* ---------- Formula + thetas ---------- */ | |
| :root { | |
| --c-prefill: #55A868; | |
| --c-kvcache: #E2C044; | |
| --c-nexttok: #DD8452; | |
| } | |
| .modelGrid { | |
| display: grid; | |
| /* Theta card as wide as its one-line rows; the formula takes the rest */ | |
| grid-template-columns: minmax(0, 1fr) auto; | |
| gap: 20px; | |
| align-items: stretch; | |
| } | |
| .modelGrid .card { | |
| margin-top: 0; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .card h3 { | |
| margin: 0 0 16px; | |
| font-size: 1.0625rem; | |
| } | |
| /* Always a single line: everything inside is sized in em, and | |
| fitFormula() in app.js lowers font-size when the line would overflow */ | |
| .formula { | |
| /* Centered vertically in the card, but only as tall as its content, | |
| so the term boxes do not stretch to the card height */ | |
| margin: auto 0; | |
| display: flex; | |
| align-items: center; | |
| flex-wrap: nowrap; | |
| gap: 0.55em; | |
| font-size: 1.125rem; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| } | |
| .formula > * { | |
| flex-shrink: 0; | |
| } | |
| .plus { | |
| font-size: 1.11em; | |
| color: var(--muted); | |
| } | |
| .term { | |
| align-self: stretch; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 0.33em; | |
| padding: 0.55em 0.78em 0.45em; | |
| border-radius: 0.55em; | |
| border: 1.5px solid var(--c); | |
| background-color: color-mix(in srgb, var(--c) 16%, white); | |
| } | |
| /* Same colors and hatches as the stacked plot (x, /, -) */ | |
| .prefill { --c: var(--c-prefill); } | |
| .kvcache { --c: var(--c-kvcache); } | |
| .nexttok { --c: var(--c-nexttok); } | |
| .term.prefill { | |
| background-image: | |
| repeating-linear-gradient(45deg, rgba(0,0,0,0.05) 0 1px, transparent 1px 9px), | |
| repeating-linear-gradient(-45deg, rgba(0,0,0,0.05) 0 1px, transparent 1px 9px); | |
| } | |
| .term.kvcache { | |
| background-image: | |
| repeating-linear-gradient(-45deg, rgba(0,0,0,0.06) 0 1px, transparent 1px 9px); | |
| } | |
| .term.nexttok { | |
| background-image: | |
| repeating-linear-gradient(0deg, rgba(0,0,0,0.06) 0 1px, transparent 1px 9px); | |
| } | |
| .termLabel { | |
| font-size: 0.61em; | |
| font-weight: 600; | |
| letter-spacing: 0.02em; | |
| color: color-mix(in srgb, var(--c) 55%, black); | |
| text-align: center; | |
| } | |
| .symbols { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px 20px; | |
| margin: 18px 0 0; | |
| padding-top: 14px; | |
| border-top: 1px solid var(--border); | |
| font-size: 0.8125rem; | |
| color: var(--muted); | |
| } | |
| .symbols span .katex { | |
| color: #1d1d1b; | |
| } | |
| #thetaTable { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| #thetaTable td { | |
| padding: 9px 6px; | |
| border-bottom: 1px solid var(--border); | |
| white-space: nowrap; | |
| } | |
| #thetaTable tr:last-child td { | |
| border-bottom: none; | |
| } | |
| #thetaTable td:nth-child(2) { | |
| font-size: 0.9375rem; | |
| } | |
| #thetaTable .termName { | |
| font-size: 0.75rem; | |
| color: color-mix(in srgb, var(--c) 55%, black); | |
| text-align: right; | |
| padding-left: 16px; | |
| } | |
| .dot { | |
| display: inline-block; | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 3px; | |
| background: var(--c); | |
| margin-right: 8px; | |
| vertical-align: middle; | |
| } | |
| .note { | |
| margin: 12px 0 0; | |
| font-size: 0.75rem; | |
| color: var(--muted); | |
| } | |
| /* ---------- About ---------- */ | |
| .about { | |
| margin: 32px 0 12px; | |
| } | |
| .about p { | |
| line-height: 1.6; | |
| } | |
| .about a { | |
| color: #146c3a; | |
| } | |
| .authors { | |
| list-style: none; | |
| padding: 0; | |
| margin: 0; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px 28px; | |
| font-size: 0.9375rem; | |
| font-weight: 600; | |
| } | |
| .authors sup { | |
| font-weight: 400; | |
| color: var(--muted); | |
| margin-left: 1px; | |
| } | |
| .affiliations { | |
| list-style: none; | |
| padding: 0; | |
| margin: 10px 0 0; | |
| font-size: 0.8125rem; | |
| line-height: 1.6; | |
| color: var(--muted); | |
| } | |
| .bibtex { | |
| position: relative; | |
| } | |
| .bibtex pre { | |
| margin: 0; | |
| padding: 14px 16px; | |
| padding-right: 80px; | |
| background: white; | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| font-size: 0.8125rem; | |
| line-height: 1.5; | |
| overflow-x: auto; | |
| } | |
| .copyBtn { | |
| position: absolute; | |
| top: 8px; | |
| right: 8px; | |
| font: inherit; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| padding: 4px 12px; | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| background: var(--surface); | |
| color: #333; | |
| cursor: pointer; | |
| } | |
| .copyBtn:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| } | |
| /* Model shown under each plot title, kept in sync by setStatus() */ | |
| #plots h4 { | |
| margin-bottom: 4px; | |
| } | |
| .plotModel { | |
| font-size: 0.8125rem; | |
| color: var(--muted); | |
| } | |
| .plotModel b { | |
| color: #1d1d1b; | |
| } | |
| @media (max-width: 900px) { | |
| .modelGrid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header class="hero"> | |
| <div class="eyebrow">Interactive demo</div> | |
| <h1>The SIMPLE Law for Energy Cost of LLM Inference</h1> | |
| <p class="lede"> | |
| An architecture-aware analytical model of the energy spent on each | |
| generated token, fitted on an NVIDIA H100 with TensorRT-LLM. | |
| </p> | |
| </header> | |
| <div class="modelGrid"> | |
| <section class="card"> | |
| <h3>SIMPLE Law</h3> | |
| <div class="formula"> | |
| <span data-tex="E_{\mathrm{tok}}(\tau_{\mathrm{in}}, \tau_{\mathrm{out}}) ="></span> | |
| <div class="term prefill"> | |
| <span data-tex="\theta_1\, d^2 L\, \dfrac{\tau_{\mathrm{in}}}{\tau_{\mathrm{out}}}"></span> | |
| <span class="termLabel">Prefill Amortization</span> | |
| </div> | |
| <span class="plus">+</span> | |
| <div class="term kvcache"> | |
| <span data-tex="\theta_2\, d L\, R \left(\tau_{\mathrm{in}} + \dfrac{\tau_{\mathrm{out}}}{2}\right)"></span> | |
| <span class="termLabel">Decode Accum. KV-cache</span> | |
| </div> | |
| <span class="plus">+</span> | |
| <div class="term nexttok"> | |
| <span data-tex="\theta_0 + \theta_3\, d L"></span> | |
| <span class="termLabel">Decode Accum. Next-token</span> | |
| </div> | |
| </div> | |
| <div class="symbols"> | |
| <span><span data-tex="d"></span> hidden size</span> | |
| <span><span data-tex="L"></span> layers</span> | |
| <span><span data-tex="R = n_{kv} / n_q"></span> KV-head ratio</span> | |
| <span><span data-tex="\tau_{\mathrm{in}}, \tau_{\mathrm{out}}"></span> input / output tokens</span> | |
| </div> | |
| </section> | |
| <section class="card"> | |
| <h3>Fitted parameters</h3> | |
| <table id="thetaTable"> | |
| <tbody id="thetaBody"></tbody> | |
| </table> | |
| <p class="note">E<sub>tok</sub> in J/token.</p> | |
| </section> | |
| </div> | |
| <div id="plots"> | |
| <div id="plotPanel"> | |
| <h4>Component breakdown at the selected τ<sub>in</sub></h4> | |
| <div class="plotModel"></div> | |
| <div id="plot"></div> | |
| </div> | |
| <div id="contourPanel"> | |
| <h4>E<sub>tok</sub> over input and output tokens</h4> | |
| <div class="plotModel"></div> | |
| <div id="contour"></div> | |
| </div> | |
| </div> | |
| <p> | |
| The contour map shows the total energy per token for the current architecture; | |
| the dashed line marks the selected τ<sub>in</sub>. | |
| </p> | |
| <section class="card" id="controls"> | |
| <div id="ninWrapper"> | |
| <div class="sliderHeader"> | |
| <label for="ninSlider">Input tokens τ<sub>in</sub></label> | |
| <span id="ninValue" class="badge">64</span> | |
| </div> | |
| <input | |
| id="ninSlider" | |
| type="range" | |
| min="4" | |
| max="16" | |
| step="0.01" | |
| value="6" | |
| oninput="updateNin(this.value)" | |
| > | |
| <div id="ninLabels"> | |
| <span>16</span> | |
| <span>65536</span> | |
| </div> | |
| <!-- Actual token count used by app.js --> | |
| <input id="nin" type="hidden" value="64"> | |
| </div> | |
| <div class="cardHeader"> | |
| <h3>Model</h3> | |
| <span id="status" class="status"></span> | |
| </div> | |
| <div class="sources"> | |
| <div class="field"> | |
| <label for="preset">Validated models</label> | |
| <select id="preset" onchange="loadPreset()"></select> | |
| </div> | |
| <div class="divider"><span>or</span></div> | |
| <div class="field"> | |
| <label for="repo">Any public Hugging Face model <span class="hint">dense, < 20B</span></label> | |
| <div class="inputGroup"> | |
| <input id="repo" type="text" placeholder="e.g. deepseek-ai/DeepSeek-R1-Distill-Llama-8B" spellcheck="false"> | |
| <button onclick="loadModel()">Load</button> | |
| </div> | |
| </div> | |
| </div> | |
| <h4 class="sectionLabel">Architecture</h4> | |
| <div class="archGrid"> | |
| <div class="field"> | |
| <label for="d">Hidden size <i>d</i></label> | |
| <input id="d" type="number" min="1" value="4096"> | |
| </div> | |
| <div class="field"> | |
| <label for="L">Layers <i>L</i></label> | |
| <input id="L" type="number" min="1" value="32"> | |
| </div> | |
| <div class="field"> | |
| <label for="nq">Query heads <i>n</i><sub>q</sub></label> | |
| <input id="nq" type="number" min="1" value="32"> | |
| </div> | |
| <div class="field"> | |
| <label for="nkv">KV heads <i>n</i><sub>kv</sub></label> | |
| <input id="nkv" type="number" min="1" value="8"> | |
| </div> | |
| </div> | |
| <div id="archSummary"></div> | |
| </section> | |
| <script> | |
| const MIN_TOKENS = 16; | |
| const MAX_TOKENS = 65536; | |
| function updateNin(exponent) { | |
| const value = Math.round(Math.pow(2, parseFloat(exponent))); | |
| const clamped = Math.min(MAX_TOKENS, Math.max(MIN_TOKENS, value)); | |
| document.getElementById('nin').value = clamped; | |
| document.getElementById('ninValue').textContent = clamped; | |
| draw(); | |
| } | |
| </script> | |
| <section class="card about"> | |
| <h3>About the SIMPLE Law</h3> | |
| <p> | |
| The <b>SIMPLE Law</b> is an architecture-aware analytical model that predicts | |
| the <b>energy consumed per generated token</b> (E<sub>tok</sub>) during LLM | |
| inference from a model's architecture (<i>d</i>, <i>L</i>, <i>n</i><sub>q</sub>, | |
| <i>n</i><sub>kv</sub>) and its input and output token counts | |
| (τ<sub>in</sub>, τ<sub>out</sub>). | |
| This demo plots the SIMPLE Law's prediction and the share of each of its terms. | |
| Select one of the models validated in the paper, or load the architecture of any | |
| <b>dense decoder-only Transformer</b> (up to approximately 20B parameters) from | |
| Hugging Face. | |
| </p> | |
| <p> | |
| The SIMPLE Law was fitted using | |
| <a href="https://zenodo.org/records/18714476" target="_blank">this dataset</a>. | |
| </p> | |
| <p> | |
| For the complete methodology, experimental validation, and citation | |
| information, see the paper: | |
| <a href="https://arxiv.org/abs/2611.03330" target="_blank"> | |
| The SIMPLE Law for Energy Cost of LLM Inference</a>. | |
| </p> | |
| <h4 class="sectionLabel">Authors</h4> | |
| <ul class="authors"> | |
| <li>Hiari Pizzini Cavagna<sup>a</sup></li> | |
| <li>Andrea Proia<sup>a</sup></li> | |
| <li>Daniele Cesarini<sup>b</sup></li> | |
| <li>Zeynep Kiziltan<sup>a</sup></li> | |
| <li>Andrea Bartolini<sup>a</sup></li> | |
| </ul> | |
| <ul class="affiliations"> | |
| <li><sup>a</sup> University of Bologna, Bologna, Italy</li> | |
| <li><sup>b</sup> CINECA, Casalecchio di Reno, Italy</li> | |
| </ul> | |
| <h4 class="sectionLabel">Citation</h4> | |
| <!-- TODO: fill in venue, year, pages and DOI once published --> | |
| <div class="bibtex"> | |
| <button class="copyBtn" onclick="copyBibtex(this)">Copy</button> | |
| <pre id="bibtex">@misc{cavagna2026simplelawenergycost, | |
| title={The SIMPLE Law for Energy Cost of LLM Inference}, | |
| author={Hiari Pizzini Cavagna and Andrea Proia and Daniele Cesarini and Zeynep Kiziltan and Andrea Bartolini}, | |
| year={2026}, | |
| eprint={2611.03330}, | |
| archivePrefix={arXiv}, | |
| primaryClass={cs.AI}, | |
| url={https://arxiv.org/abs/2611.03330}, | |
| }</pre> | |
| </div> | |
| <script> | |
| function copyBibtex(button) { | |
| navigator.clipboard.writeText(document.getElementById('bibtex').textContent); | |
| button.textContent = 'Copied'; | |
| setTimeout(() => button.textContent = 'Copy', 1500); | |
| } | |
| </script> | |
| <p class="note"> | |
| Web demo developed with the assistance of Claude (Anthropic), an AI assistant. | |
| </p> | |
| </section> | |
| <script src="app.js"></script> | |
| </body> | |
| </html> | |