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:
@@ -164,3 +164,95 @@ def bound_regime(machine: MachineParams, model: ModelConfig,
|
||||
"kv-bound": p.kv_s,
|
||||
"compute-bound": p.compute_s}
|
||||
return max(parts, key=parts.get)
|
||||
|
||||
|
||||
# ── Regime-dependent cost terms ────────────────────────────────────
|
||||
|
||||
|
||||
def t_mem_short(machine: MachineParams, model: ModelConfig,
|
||||
batch: int) -> float:
|
||||
"""Per-token weight-fetch time (short-context regime term).
|
||||
|
||||
N_active · b / (W · B). Shrinks as B grows — this is what
|
||||
batching amortizes.
|
||||
"""
|
||||
return (total_active_params(model) * model.bytes_per_elem
|
||||
/ (machine.bw_hbm * max(1, batch)))
|
||||
|
||||
|
||||
def t_mem_long(machine: MachineParams, model: ModelConfig,
|
||||
s_kv: int) -> float:
|
||||
"""Per-token KV-read time (long-context regime term).
|
||||
|
||||
S_kv · kv_bpt / W. Independent of B — each sequence reads its
|
||||
own KV cache; batching doesn't help.
|
||||
"""
|
||||
return s_kv * kv_bytes_per_token(model) / machine.bw_hbm
|
||||
|
||||
|
||||
def t_com(machine: MachineParams, model: ModelConfig) -> float:
|
||||
"""Per-token compute time. Same in both regimes: 2·N/C, peak."""
|
||||
return _FLOPS_PER_PARAM_PER_TOKEN * total_active_params(model) / machine.peak_flops
|
||||
|
||||
|
||||
# ── "Good" batch / context recommendations ─────────────────────────
|
||||
|
||||
|
||||
@dataclass
|
||||
class BatchRecommendation:
|
||||
target: float # Pope's rule: 2 × B*
|
||||
b_star: float # B* itself
|
||||
effective: float # what we recommend using
|
||||
reason: str # short explanation
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContextRecommendation:
|
||||
l_star: float # balance context length
|
||||
max_efficient: float # same as l_star (compute-friendly ceiling)
|
||||
utilization_at: float # utilization at current s_kv
|
||||
reason: str
|
||||
|
||||
|
||||
def good_batch(machine: MachineParams, model: ModelConfig,
|
||||
sparsity: float = 1.0) -> BatchRecommendation:
|
||||
"""Recommended batch size: 2 × B* (Pope's rule of thumb).
|
||||
|
||||
Below B*: memory-bound, doubling B halves cost/token.
|
||||
At 2×B*: 50% excess over compute floor — the sweet spot.
|
||||
Beyond 3×B*: diminishing returns; latency keeps growing linearly.
|
||||
"""
|
||||
b_star = critical_batch(machine, model, sparsity)
|
||||
target = 2 * b_star
|
||||
return BatchRecommendation(
|
||||
target=target, b_star=b_star, effective=target,
|
||||
reason=(f"2·B* = 2 · {b_star:.0f} = {target:.0f}. "
|
||||
"Below B*: memory-bound (doubling B halves cost/token). "
|
||||
"Beyond 3·B*: diminishing returns."),
|
||||
)
|
||||
|
||||
|
||||
def good_context(machine: MachineParams, model: ModelConfig,
|
||||
s_kv: int) -> ContextRecommendation:
|
||||
"""Recommended max context: L* — the compute-friendly ceiling.
|
||||
|
||||
Below L*: KV read is cheap relative to compute → good utilization.
|
||||
Above L*: KV bandwidth wall → utilization = 1/(1 + S_kv/L*).
|
||||
"""
|
||||
l_star = balance_context(machine, model)
|
||||
util = utilization_at(s_kv, l_star)
|
||||
return ContextRecommendation(
|
||||
l_star=l_star, max_efficient=l_star, utilization_at=util,
|
||||
reason=(f"L* = {l_star:,.0f} tokens. Below L*: compute-bound "
|
||||
f"(good util). At {s_kv:,} tokens: peak utilization ≈ "
|
||||
f"{util*100:.1f}% (1 / (1 + S_kv/L*))."),
|
||||
)
|
||||
|
||||
|
||||
def utilization_at(s_kv: int, l_star: float) -> float:
|
||||
"""Peak compute utilization at context length s_kv, given L*.
|
||||
|
||||
util = compute / (compute + KV_read) = 1 / (1 + S_kv/L*).
|
||||
At S_kv=0: 100%. At S_kv=L*: 50%. At 2·L*: 33.3%. At 5·L*: 16.7%.
|
||||
"""
|
||||
return 1.0 / (1.0 + s_kv / l_star)
|
||||
|
||||
Reference in New Issue
Block a user