analytical-viz: roofline tab — S_kv knob, regime formulas, good B/L, decomp plot, split table
Bundle of roofline-tab enhancements requested in-thread:
1. **S_kv slider at top of tab** — override the sidebar's S_kv locally
so all four plots update as you drag it. Handy for exploring how
the KV wall arrives without disturbing the rest of the app config.
2. **Good B / Good L KPI cards** — 3 metrics under the main KPI row:
- Good B = 2·B* (Pope's rule of thumb)
- Good L ceiling = L* (compute-friendly context ceiling)
- Utilization @ current S_kv = 1 / (1 + S_kv/L*)
3. **Plot 4 — latency-decomposition per decode step** — five curves
on one axis:
- Compute (dashed, flat)
- Weight fetch (triangles, shrinks 1/B)
- KV fetch (squares, flat — batching doesn't help)
- Memory total (weights + KV, purple)
- Total (compute + memory, black bold)
Makes "which term dominates at this B?" visible at a glance.
4. **Regime formulas table** — one row per cost term (t_com, weight
fetch, KV fetch, bottleneck) × two columns (short-context vs
long-context regime), plus a 'value now' column using the
current S_kv slider.
5. **How to pick B and S_kv — one-paragraph guidance** with the
numeric recommendations plugged in.
6. **Split formula table** — was cramming symbolic + substituted
form into one cell with newlines; now has four proper columns:
Symbol / Formula / With numbers / Meaning / Value.
New pure functions in chip_roofline.py: t_mem_short, t_mem_long,
t_com, good_batch, good_context, utilization_at. All roofline math
stays in the pure module; app.py just calls them and formats.
10 new tests bring the roofline test count to 27 (all 67 tests still
green): t_mem_long(L*) == t_com, doubling B halves t_mem_short,
good_batch scales with sparsity, utilization at 0/L*/2L* returns
1.0/0.5/0.333, monotonic decrease with context.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -10,10 +10,16 @@ from tests.analytical_visualization.chip_roofline import (
|
||||
balance_context,
|
||||
bound_regime,
|
||||
critical_batch,
|
||||
good_batch,
|
||||
good_context,
|
||||
knee_batch,
|
||||
kv_bytes_per_token,
|
||||
per_token_latency_curve,
|
||||
t_com,
|
||||
t_mem_long,
|
||||
t_mem_short,
|
||||
total_active_params,
|
||||
utilization_at,
|
||||
)
|
||||
from tests.analytical_visualization.model_config import (
|
||||
MachineParams, ModelConfig,
|
||||
@@ -193,6 +199,88 @@ def test_kv_bytes_per_token_llama3_8b():
|
||||
assert kv_bpt == 2 * 8 * 128 * 2 * 32
|
||||
|
||||
|
||||
# ── Regime terms + good-batch / good-context recommendations ──────
|
||||
|
||||
|
||||
def test_t_mem_short_shrinks_with_batch():
|
||||
"""t_mem_short = N·b/(W·B) — doubling B halves it."""
|
||||
m = MachineParams()
|
||||
model = PRESETS["Llama 3 8B"].model
|
||||
assert t_mem_short(m, model, 2) == pytest.approx(
|
||||
t_mem_short(m, model, 1) / 2
|
||||
)
|
||||
|
||||
|
||||
def test_t_mem_long_equals_t_com_at_l_star():
|
||||
"""L* is defined by t_mem_long(L*) == t_com. Cross-check."""
|
||||
m = MachineParams()
|
||||
model = PRESETS["Llama 3 8B"].model
|
||||
l_star = balance_context(m, model)
|
||||
assert t_mem_long(m, model, int(round(l_star))) == pytest.approx(
|
||||
t_com(m, model), rel=0.01,
|
||||
)
|
||||
|
||||
|
||||
def test_t_com_is_batch_independent():
|
||||
"""t_com = 2·N/C. Constant — doesn't depend on B or S_kv."""
|
||||
m = MachineParams()
|
||||
model = PRESETS["Llama 3 8B"].model
|
||||
tc = t_com(m, model)
|
||||
assert tc == pytest.approx(2 * total_active_params(model) / m.peak_flops)
|
||||
|
||||
|
||||
def test_good_batch_is_two_b_star():
|
||||
"""Recommendation is 2 × B* (Pope's rule)."""
|
||||
m = MachineParams()
|
||||
model = PRESETS["Llama 3 8B"].model
|
||||
rec = good_batch(m, model)
|
||||
assert rec.effective == pytest.approx(2 * critical_batch(m, model))
|
||||
assert rec.b_star == pytest.approx(critical_batch(m, model))
|
||||
|
||||
|
||||
def test_good_batch_moe_scales_with_sparsity():
|
||||
"""MoE sparsity=8 → recommended B is 8× dense recommendation."""
|
||||
m = MachineParams()
|
||||
model = PRESETS["Llama 3 8B"].model
|
||||
r_dense = good_batch(m, model, sparsity=1.0)
|
||||
r_moe = good_batch(m, model, sparsity=8.0)
|
||||
assert r_moe.effective == pytest.approx(8 * r_dense.effective)
|
||||
|
||||
|
||||
def test_good_context_returns_l_star_as_ceiling():
|
||||
"""Recommendation is L*; utilization drops past it."""
|
||||
m = MachineParams()
|
||||
model = PRESETS["Llama 3 8B"].model
|
||||
rec = good_context(m, model, s_kv=100)
|
||||
assert rec.l_star == pytest.approx(balance_context(m, model))
|
||||
assert rec.max_efficient == pytest.approx(rec.l_star)
|
||||
|
||||
|
||||
def test_utilization_at_l_star_is_half():
|
||||
"""At S_kv = L*, compute and KV read take equal time → 50% util."""
|
||||
l_star = 3000.0
|
||||
assert utilization_at(int(l_star), l_star) == pytest.approx(0.5)
|
||||
|
||||
|
||||
def test_utilization_at_2_l_star_is_one_third():
|
||||
"""At S_kv = 2·L*, util = 1/(1+2) = 33.3%."""
|
||||
l_star = 3000.0
|
||||
assert utilization_at(int(2 * l_star), l_star) == pytest.approx(1 / 3, rel=1e-3)
|
||||
|
||||
|
||||
def test_utilization_at_zero_context_is_100_percent():
|
||||
"""Zero context, no KV to read → all time is compute."""
|
||||
assert utilization_at(0, 3000.0) == pytest.approx(1.0)
|
||||
|
||||
|
||||
def test_utilization_drops_monotonically_with_context():
|
||||
"""util(S_kv) is strictly decreasing."""
|
||||
l_star = 3000.0
|
||||
vals = [utilization_at(s, l_star) for s in [100, 1000, 3000, 6000, 30000]]
|
||||
for a, b in zip(vals, vals[1:]):
|
||||
assert b < a
|
||||
|
||||
|
||||
# ── App wiring: tab exists on the Streamlit app ────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user