> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensormesh.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Cache Sizing

> Paste a chronological trace of token-id requests and simulate the cache hit rate you would get at any L1/L2 capacity, in tokens and bytes.

export const CacheSizingCalculator = () => {
  const DEFAULT_TRACE = JSON.stringify([
    [1, 2, 3, 4, 5, 6, 7, 8, 101, 102],
    [1, 2, 3, 4, 5, 6, 7, 8, 201, 202, 203],
    [1, 2, 3, 4, 5, 6, 7, 8, 101, 102],
    [1, 2, 3, 4, 5, 6, 7, 8, 301],
    [50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65],
    [1, 2, 3, 4, 5, 6, 7, 8, 101, 102],
    [1, 2, 3, 4, 5, 6, 7, 8, 201, 202, 203],
    [70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81],
    [1, 2, 3, 4, 5, 6, 7, 8, 401, 402],
  ]);

  const [traceText, setTraceText] = useState(DEFAULT_TRACE);
  const [l1Tokens, setL1Tokens] = useState(12);
  const [l2Tokens, setL2Tokens] = useState(18);
  const [bytesPerToken, setBytesPerToken] = useState("131072");

  function hashA(prev, s) {
    let h = prev;
    for (let i = 0; i < s.length; i++) h = (h * 131 + s.charCodeAt(i)) % 2147483647;
    return h;
  }
  function hashB(prev, s) {
    let h = prev;
    for (let i = 0; i < s.length; i++) h = (h * 137 + s.charCodeAt(i)) % 2147483629;
    return h;
  }

  const analysis = useMemo(() => {
    const t = traceText.trim();
    if (!t) return { empty: true };
    let requests;
    try {
      requests = JSON.parse(t);
    } catch (e) {
      return { error: String(e.message || e) };
    }
    if (!Array.isArray(requests)) return { error: "Trace must be a JSON array of token-id arrays." };

    const reqTok = [];
    for (const r of requests) {
      const tok = Array.isArray(r) ? r : r && Array.isArray(r.tokens) ? r.tokens : null;
      if (!tok) return { error: "Each request must be an array of token ids, e.g. [1, 2, 3]." };
      reqTok.push(tok);
    }
    if (!reqTok.length) return { empty: true };

    const CAP = 200000;
    let n = 0;
    let truncated = false;
    const used = [];
    for (const tk of reqTok) {
      used.push(tk);
      n = n + tk.length;
      if (n >= CAP) {
        truncated = true;
        break;
      }
    }
    const N = Math.min(n, CAP);
    if (N === 0) return { empty: true };

    const bit = new Array(N + 1).fill(0);
    function add(i, v) {
      for (; i <= N; i = i + (i & (-i))) bit[i] = bit[i] + v;
    }
    function pre(i) {
      let s = 0;
      for (; i > 0; i = i - (i & (-i))) s = s + bit[i];
      return s;
    }

    const lastSeen = new Map();
    const required = [];
    let cold = 0;
    let step = 0;
    for (const tk of used) {
      let a = 16777619;
      let b = 2166136261 % 2000000011;
      for (let j = 0; j < tk.length; j++) {
        const enc = String(tk[j]) + ",";
        a = hashA(a, enc);
        b = hashB(b, enc);
        step = step + 1;
        if (step > N) break;
        const key = a + "_" + b;
        const prev = lastSeen.get(key);
        if (prev === undefined) {
          cold = cold + 1;
        } else {
          required.push(pre(step - 1) - pre(prev) + 1);
          add(prev, -1);
        }
        add(step, 1);
        lastSeen.set(key, step);
      }
      if (step > N) break;
    }
    required.sort((x, y) => x - y);
    const working = lastSeen.size;
    const maxD = required.length ? required[required.length - 1] : working;
    return {
      requests: reqTok.length,
      total: step,
      cold: cold,
      required: required,
      working: working,
      maxD: maxD,
      truncated: truncated,
    };
  }, [traceText]);

  const pct = (n, d) => (d ? ((100 * n) / d).toFixed(1) : "0.0");
  const fmtTok = (x) => {
    const v = Math.round(x);
    if (v >= 1000000) return (v / 1000000).toFixed(1) + "M";
    if (v >= 1000) return (v / 1000).toFixed(1) + "K";
    return String(v);
  };
  const fmtBytes = (n) => {
    if (n >= 1073741824) return (n / 1073741824).toFixed(2) + " GB";
    if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";
    if (n >= 1024) return (n / 1024).toFixed(1) + " KB";
    return Math.round(n) + " B";
  };
  const bpt = Number(bytesPerToken) || 0;
  const bytesSuffix = (tokens) => (bpt > 0 ? " · " + fmtBytes(tokens * bpt) : "");
  const countLE = (arr, c) => {
    let lo = 0;
    let hi = arr.length;
    while (lo < hi) {
      const mid = Math.floor((lo + hi) / 2);
      if (arr[mid] <= c) lo = mid + 1;
      else hi = mid;
    }
    return lo;
  };

  const ready = analysis.required && analysis.total > 0;

  const splits = ready
    ? (() => {
        const l1 = countLE(analysis.required, l1Tokens);
        const l2 = countLE(analysis.required, l1Tokens + l2Tokens) - l1;
        const miss = analysis.required.length - l1 - l2 + analysis.cold;
        return { l1: l1, l2: l2, miss: miss, total: analysis.total };
      })()
    : null;

  const maxCap = ready ? Math.max(analysis.maxD, l1Tokens + l2Tokens, 8) * 1.1 : 1;
  const curvePts = ready
    ? (() => {
        const N = 120;
        const pts = [];
        for (let i = 0; i <= N; i++) {
          const cap = (maxCap * i) / N;
          pts.push({ cap: cap, hit: countLE(analysis.required, cap) / analysis.total });
        }
        return pts;
      })()
    : [];

  const sliderMax = Math.max(8, Math.ceil(maxCap));
  const sStep = Math.max(1, Math.round(sliderMax / 200));

  const W = 640;
  const H = 240;
  const PL = 48;
  const PR = 16;
  const PT = 14;
  const PB = 34;
  const xAt = (cap) => PL + (cap / maxCap) * (W - PL - PR);
  const yAt = (h) => PT + (1 - h) * (H - PT - PB);
  const l1x = xAt(l1Tokens);
  const l2x = xAt(l1Tokens + l2Tokens);
  const linePts = curvePts.map((p) => xAt(p.cap).toFixed(1) + "," + yAt(p.hit).toFixed(1)).join(" ");
  const hitAt = (c) => (ready ? countLE(analysis.required, c) / analysis.total : 0);

  const box = { border: "1px solid #e5e7eb", borderRadius: 12, padding: 16 };
  const label = { fontSize: 13, fontWeight: 600, display: "block", marginBottom: 4 };
  const ctrl = { width: "100%", padding: "6px 8px", borderRadius: 8, border: "1px solid #d1d5db", fontSize: 14, boxSizing: "border-box" };
  const colors = { l1: "#10b981", l2: "#f59e0b", miss: "#9ca3af" };

  return (
    <div style={{ ...box, fontSize: 14 }}>
      <div style={{ marginBottom: 12 }}>
        <span style={label}>Trace — ordered list of token-id requests</span>
        <textarea
          style={{ ...ctrl, fontFamily: "ui-monospace, monospace", fontSize: 12.5, minHeight: 150, resize: "vertical" }}
          value={traceText}
          onChange={(e) => setTraceText(e.target.value)}
          spellCheck={false}
        />
        <div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>
          JSON array, chronological. Each request is an array of token ids, e.g.{" "}
          <code>[[1,2,3,4],[1,2,3,9]]</code>. Requests sharing a leading prefix share cached KV.
        </div>
      </div>

      <div style={{ display: "flex", alignItems: "flex-end", gap: 12, flexWrap: "wrap", marginBottom: 12 }}>
        <div style={{ flex: "0 0 200px" }}>
          <span style={label}>Bytes per token</span>
          <input type="number" min={0} placeholder="e.g. 131072" style={ctrl} value={bytesPerToken} onChange={(e) => setBytesPerToken(e.target.value)} />
        </div>
        <div style={{ fontSize: 12, color: "#6b7280", paddingBottom: 8 }}>
          Get it for your model from the{" "}
          <a href="https://docs.lmcache.ai/getting_started/kv_cache_calculator.html" target="_blank" rel="noreferrer">
            LMCache KV Cache Size Calculator
          </a>
          . Set it to also see capacities in bytes.
        </div>
      </div>

      {analysis.error && <div style={{ color: "#dc2626", fontSize: 13 }}>Trace error: {analysis.error}</div>}
      {analysis.truncated && (
        <div style={{ color: "#b45309", fontSize: 12, marginBottom: 8 }}>Trace truncated to the first 200,000 tokens.</div>
      )}

      {ready && splits && (
        <>
          <div style={{ display: "flex", gap: 16, flexWrap: "wrap", margin: "4px 0 14px", fontSize: 13, color: "#374151" }}>
            <span>Requests: <strong>{analysis.requests}</strong></span>
            <span>Total tokens: <strong>{fmtTok(analysis.total)}</strong></span>
            <span>Working set: <strong>{fmtTok(analysis.working)} tok{bytesSuffix(analysis.working)}</strong></span>
            <span>Cold misses: <strong>{fmtTok(analysis.cold)}</strong></span>
          </div>

          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 14 }}>
            <div>
              <span style={label}>L1 (memory): {fmtTok(l1Tokens)} tok{bytesSuffix(l1Tokens)} ({pct(l1Tokens, analysis.working)}%)</span>
              <input type="range" min={0} max={sliderMax} step={sStep} value={l1Tokens} style={{ width: "100%" }}
                onChange={(e) => setL1Tokens(Number(e.target.value))} />
            </div>
            <div>
              <span style={label}>L2 (disk/remote): {fmtTok(l2Tokens)} tok{bytesSuffix(l2Tokens)} ({pct(l2Tokens, analysis.working)}%)</span>
              <input type="range" min={0} max={sliderMax} step={sStep} value={l2Tokens} style={{ width: "100%" }}
                onChange={(e) => setL2Tokens(Number(e.target.value))} />
            </div>
          </div>

          <div style={{ display: "flex", height: 40, borderRadius: 8, overflow: "hidden", border: "1px solid #e5e7eb", marginBottom: 8 }}>
            {[
              { k: "l1", v: splits.l1, c: colors.l1, t: "L1" },
              { k: "l2", v: splits.l2, c: colors.l2, t: "L2" },
              { k: "miss", v: splits.miss, c: colors.miss, t: "Miss" },
            ].map((s) => {
              const w = (100 * s.v) / splits.total;
              return (
                <div key={s.k} title={s.t + ": " + s.v} style={{ width: w + "%", background: s.c, color: "white", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 12, fontWeight: 600, whiteSpace: "nowrap" }}>
                  {w > 8 ? s.t + " " + pct(s.v, splits.total) + "%" : ""}
                </div>
              );
            })}
          </div>
          <div style={{ display: "flex", gap: 16, fontSize: 13, marginBottom: 16, flexWrap: "wrap" }}>
            <span style={{ color: colors.l1 }}>● L1 hit {pct(splits.l1, splits.total)}%</span>
            <span style={{ color: colors.l2 }}>● L2 hit {pct(splits.l2, splits.total)}%</span>
            <span style={{ color: "#6b7280" }}>● Miss {pct(splits.miss, splits.total)}%</span>
            <span style={{ marginLeft: "auto", fontWeight: 600 }}>Hit rate {pct(splits.l1 + splits.l2, splits.total)}%</span>
          </div>

          <svg viewBox={"0 0 " + W + " " + H} style={{ width: "100%", height: "auto" }} role="img" aria-label="Hit rate versus cache capacity">
            <rect x={PL} y={PT} width={l1x - PL} height={H - PT - PB} fill={colors.l1} opacity="0.08" />
            <rect x={l1x} y={PT} width={l2x - l1x} height={H - PT - PB} fill={colors.l2} opacity="0.10" />
            {[0, 0.25, 0.5, 0.75, 1].map((g) => (
              <g key={g}>
                <line x1={PL} y1={yAt(g)} x2={W - PR} y2={yAt(g)} stroke="#e5e7eb" strokeWidth="1" />
                <text x={PL - 6} y={yAt(g) + 3} textAnchor="end" fontSize="10" fill="#9ca3af">{Math.round(g * 100)}%</text>
              </g>
            ))}
            <polyline points={linePts} fill="none" stroke="#2563eb" strokeWidth="2.5" />
            <line x1={l1x} y1={PT} x2={l1x} y2={H - PB} stroke={colors.l1} strokeWidth="1.5" strokeDasharray="4 3" />
            <line x1={l2x} y1={PT} x2={l2x} y2={H - PB} stroke={colors.l2} strokeWidth="1.5" strokeDasharray="4 3" />
            <circle cx={l1x} cy={yAt(hitAt(l1Tokens))} r="4" fill={colors.l1} />
            <circle cx={l2x} cy={yAt(hitAt(l1Tokens + l2Tokens))} r="4" fill={colors.l2} />
            <text x={l1x} y={H - PB + 14} textAnchor="middle" fontSize="10" fill={colors.l1}>L1</text>
            <text x={l2x} y={H - PB + 14} textAnchor="middle" fontSize="10" fill={colors.l2}>L1+L2</text>
            <text x={(PL + W - PR) / 2} y={H - 4} textAnchor="middle" fontSize="11" fill="#6b7280">cache capacity (tokens) — full scale {fmtTok(maxCap)} tok{bytesSuffix(maxCap)}</text>
          </svg>
        </>
      )}
    </div>
  );
};

<CacheSizingCalculator />
