Download offload/kl1/bandwidth.py from ProprietaryLegal/dsv41-offload: direct link, hf CLI and curl.
- Browser
- Download file 14.3 kB
-
https://huggingface.co/ProprietaryLegal/dsv41-offload/resolve/main/offload/kl1/bandwidth.py
- Command line
-
hf download hf://ProprietaryLegal/dsv41-offload/offload/kl1/bandwidth.py
-
curl -L -o bandwidth.py https://huggingface.co/ProprietaryLegal/dsv41-offload/resolve/main/offload/kl1/bandwidth.py
14.3 kB
| """KL1 item 1: achieved bandwidth per kernel, per step, from both SP1 windows' traces (both ranks). | |
| PYTHONPATH=. python3 -m offload.kl1.bandwidth --window <SP1-W-dir> [--window ...] --output <json> | |
| Every byte count is ESTIMATED (offload/kl1/shapes.py states the formula and each assumption); every duration is | |
| MEASURED by kineto in a profiled window (profile-affected). GB/s = ESTIMATED bytes / MEASURED time. The routed experts' | |
| bytes depend on how many distinct experts a step's rows pick, which no trace records: they are given as a function of | |
| that count, at the uniform-independent count (an upper bound) and at the counts 273 GB/s and the source's 240 GB/s | |
| would allow. Output: numbers, kernel families and hashes only. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import statistics | |
| import sys | |
| from collections.abc import Sequence | |
| from pathlib import Path | |
| from typing import cast | |
| from offload.gb10_budget.trace_budget import rules, sweep | |
| from offload.kl1 import shapes as S | |
| from offload.kl1 import trace_kernels as T | |
| from offload.prefill_maxlen.evidence import Json, number, record | |
| DECODE_CAPTURES = ('prof-short-b1:decode', 'prof-32k-b1:decode', 'prof-150k-b1:decode', 'prof-32k-b4:decode') | |
| PREFILL_CAPTURES = ('prof-32k-b1:prefill-early', 'prof-150k-b1:prefill-late') | |
| FULL_LAYERS = 20 # deepseek_v41 replay cut: layers 0-19 run the whole chunk, 20-39 the 128-row suffix | |
| SUFFIX_ROWS = 128 | |
| def _mean(xs: Sequence[float]) -> float: | |
| if not xs: | |
| raise ValueError('mean of nothing') | |
| return statistics.fmean(xs) | |
| def expert_mean_bytes() -> tuple[float, float]: | |
| """(gate|up, down) bytes of an average routed expert on one rank: the K mix weighted by expert counts (assumes a | |
| pick's K is distributed like the bank's; picks that favour high-K experts move more).""" | |
| total = sum(S.K_MIX.values()) | |
| gu = sum(n * S.expert_gu_bytes(k) for k, n in S.K_MIX.items()) / total | |
| dn = sum(n * S.expert_down_bytes(k) for k, n in S.K_MIX.items()) / total | |
| return gu, dn | |
| def shared_bytes() -> float: | |
| return S.expert_bytes(S.SHARED_K) | |
| def smoe_bytes(unique: float, live_rows: int) -> float: | |
| """One layer's smoe weight bytes: `unique` routed experts read once each (<= 8 rows each, exl3_smoe.cu RX) plus the | |
| shared expert once per run of 8 live rows (no L2 reuse assumed between its runs).""" | |
| gu, dn = expert_mean_bytes() | |
| return unique * (gu + dn) + S.shared_runs(live_rows) * shared_bytes() | |
| def unique_at(gbps: float, span_us: float, live_rows: int) -> float: | |
| """Routed experts a layer could have read in span_us at gbps (shared runs first).""" | |
| gu, dn = expert_mean_bytes() | |
| return (gbps * 1e3 * span_us - S.shared_runs(live_rows) * shared_bytes()) / (gu + dn) | |
| def sp1_split(step: T.Step) -> dict[str, float]: | |
| """SP1's exclusive budget for this step (equal split among active buckets, trace_budget.sweep) twice: with SP1's | |
| rules (l2pf_kernel is 'routed_moe') and with l2pf as its own bucket. ms.""" | |
| out: dict[str, float] = {} | |
| for tag, rename in (('sp1', False), ('l2pf_apart', True)): | |
| spans = [(k.start, k.end, 'l2pf' if rename and k.family == 'l2pf' else k.bucket) for k in step.kernels] | |
| excl, _, gaps = sweep(spans, step.lo, step.hi) | |
| for b, v in excl.items(): | |
| out[f'{tag}.{b}'] = v / 1e3 | |
| out[f'{tag}.idle'] = sum(g1 - g0 for g0, g1 in gaps) / 1e3 | |
| # l2pf removed entirely: what is exclusive routed-MoE time when prefetch time on the side stream is not counted | |
| excl, _, gaps = sweep([(k.start, k.end, k.bucket) for k in step.kernels if k.family != 'l2pf'], step.lo, step.hi) | |
| for b, v in excl.items(): | |
| out[f'no_l2pf.{b}'] = v / 1e3 | |
| out['no_l2pf.idle'] = sum(g1 - g0 for g0, g1 in gaps) / 1e3 | |
| return out | |
| def moe_wall(step: T.Step) -> dict[str, float]: | |
| """Wall time of the MoE phase: the union of router, routing top-k and smoe/TF expert kernels, ms per step.""" | |
| fams = ('router', 'route_topk', 'smoe_rot', 'smoe_gu', 'smoe_down', 'smoe_fused', 'tf_gu', 'tf_down', 'tf_aux') | |
| ks = [k for k in step.kernels if k.family in fams] | |
| experts = [k for k in ks if k.family not in ('router', 'route_topk')] | |
| return {'moe_union_ms': T.union(((k.start, k.end) for k in ks), step.lo, step.hi) / 1e3, | |
| 'expert_union_ms': T.union(((k.start, k.end) for k in experts), step.lo, step.hi) / 1e3, | |
| 'router_topk_union_ms': T.union(((k.start, k.end) for k in ks if k not in experts), step.lo, step.hi) / 1e3} | |
| def dense_decode(step: T.Step) -> dict[str, Json]: | |
| """exl3_mrow launches: four a layer in forward order (wq_a+wkv, wq_b, wo_a, wo_b), K from the template.""" | |
| ks = [k for k in step.kernels if k.family == 'mrow'] | |
| if len(ks) % len(S.DECODE_DENSE): | |
| raise ValueError(f'step {step.index}: {len(ks)} mrow launches, not a multiple of 4') | |
| rows: dict[str, Json] = {} | |
| for j, lin in enumerate(S.DECODE_DENSE): | |
| mine = ks[j::len(S.DECODE_DENSE)] | |
| for k in mine: | |
| if T.template_k(k.name) != lin.k: | |
| raise ValueError(f'{lin.name}: template K {T.template_k(k.name)} != {lin.k}') | |
| us = sum(k.dur for k in mine) | |
| rows[lin.name] = {'launches': len(mine), 'us_per_step': us, 'mb_per_launch': lin.bytes / 1e6, | |
| 'gbps': S.gbps(lin.bytes * len(mine), us), 'grid': cast(Json, list(mine[0].grid))} | |
| us = sum(k.dur for k in ks) | |
| total = sum(lin.bytes for lin in S.DECODE_DENSE) * len(ks) / len(S.DECODE_DENSE) | |
| rows['all'] = {'launches': len(ks), 'us_per_step': us, 'mb_per_step': total / 1e6, 'gbps': S.gbps(total, us)} | |
| head = [k for k in step.kernels if k.family == 'mrow_head' or (k.family == 'exl3_gemm' and T.template_k(k.name) == 6)] | |
| if head: | |
| hus = sum(k.dur for k in head) | |
| rows['lm_head'] = {'launches': len(head), 'us_per_step': hus, 'mb_per_step': S.LM_HEAD.bytes / 1e6, | |
| 'gbps': S.gbps(S.LM_HEAD.bytes, hus), 'kernel': head[0].family} | |
| for fam, nbytes in (('router', S.ROUTER_BYTES), ('hc_prenorm', S.MHC_BYTES)): | |
| ks2 = [k for k in step.kernels if k.family == fam] | |
| if ks2: | |
| us2 = sum(k.dur for k in ks2) | |
| rows[fam] = {'launches': len(ks2), 'us_per_step': us2, 'mb_per_launch': nbytes / 1e6, | |
| 'gbps': S.gbps(nbytes * len(ks2), us2)} | |
| return rows | |
| def smoe_decode(step: T.Step, requests: int) -> dict[str, Json]: | |
| layers = T.moe_layers(step) | |
| if len(layers) != S.LAYERS: | |
| raise ValueError(f'step {step.index}: {len(layers)} smoe layers') | |
| m = step.rows | |
| slots = layers[0].gu.grid[0] // (S.I_LOCAL // 128) | |
| span = sum(L.span for L in layers) | |
| gu_us = sum(L.gu.dur for L in layers) | |
| down_after = sum(L.down_after_gu for L in layers) | |
| rot_us = sum(L.rot.dur for L in layers if L.rot is not None) | |
| uni = S.unique_uniform(m) | |
| per_layer_span = span / len(layers) | |
| return {'rows': m, 'requests': requests, 'graph_slots': slots, 'graph_rows': slots // (S.TOP_K + 1), | |
| 'span_ms': span / 1e3, 'gu_ms': gu_us / 1e3, 'down_after_gu_ms': down_after / 1e3, 'rot_ms': rot_us / 1e3, | |
| 'shared_runs': S.shared_runs(m), | |
| 'unique_uniform': uni, | |
| 'gbps_if_uniform': smoe_bytes(uni, m) * len(layers) / span / 1e3, | |
| 'gbps_if_6_per_request': smoe_bytes(min(uni, 6.0 * requests), m) * len(layers) / span / 1e3, | |
| 'unique_max_at_273': unique_at(S.GB10_SPEC_GBPS, per_layer_span, m), | |
| 'unique_max_at_240': unique_at(S.SOURCE_READ_GBPS[1], per_layer_span, m)} | |
| def tf_prefill(step: T.Step) -> dict[str, Json]: | |
| layers = T.moe_layers(step) | |
| if len(layers) != S.LAYERS: | |
| raise ValueError(f'prefill step {step.index}: {len(layers)} TF layers') | |
| gu, dn = expert_mean_bytes() | |
| out: dict[str, Json] = {} | |
| for tag, sel, rows in (('full', layers[:FULL_LAYERS], step.rows), ('suffix', layers[FULL_LAYERS:], SUFFIX_ROWS)): | |
| uni = S.unique_uniform(rows) | |
| gus = sum(L.gu.dur for L in sel) | |
| dns = sum(L.down.dur for L in sel) | |
| out[tag] = {'layers': len(sel), 'rows': rows, 'unique_uniform': uni, 'gu_ms': gus / 1e3, 'down_ms': dns / 1e3, | |
| 'gu_gbps_if_uniform': S.gbps(uni * gu * len(sel), gus), | |
| 'down_gbps_if_uniform': S.gbps(uni * dn * len(sel), dns), | |
| 'span_ms': sum(L.span for L in sel) / 1e3} | |
| return out | |
| def reconstruct_prefill(step: T.Step) -> dict[str, Json]: | |
| ks = [k for k in step.kernels if k.family == 'reconstruct'] | |
| by: dict[str, list[T.Kernel]] = {} | |
| for k in ks: | |
| by.setdefault('x'.join(map(str, S.reconstruct_shape(k.grid))), []).append(k) | |
| out: dict[str, Json] = {} | |
| tb = tus = 0.0 | |
| for shape, group in sorted(by.items()): | |
| kk = T.template_k(group[0].name) | |
| if kk is None: | |
| raise ValueError('reconstruct without template K') | |
| nbytes = S.reconstruct_bytes(group[0].grid, kk) * len(group) | |
| us = sum(k.dur for k in group) | |
| tb += nbytes | |
| tus += us | |
| out[shape] = {'launches': len(group), 'K': kk, 'us': us, 'gbps': S.gbps(nbytes, us)} | |
| if ks: | |
| out['all'] = {'launches': len(ks), 'us': tus, 'gb': tb / 1e9, 'gbps': S.gbps(tb, tus)} | |
| return out | |
| def _fmean_rows(rows: Sequence[dict[str, Json]], keys: Sequence[str]) -> dict[str, float]: | |
| return {k: _mean([number(r[k]) for r in rows]) for k in keys if all(k in r for r in rows)} | |
| def _flatten(d: dict[str, Json], prefix: str = '') -> dict[str, float]: | |
| out: dict[str, float] = {} | |
| for k, v in d.items(): | |
| if isinstance(v, dict): | |
| out.update(_flatten(v, f'{prefix}{k}.')) | |
| elif isinstance(v, (int, float)) and not isinstance(v, bool): | |
| out[f'{prefix}{k}'] = float(v) | |
| return out | |
| def capture(paths: dict[int, Path], kind: str) -> dict[str, Json]: | |
| ranks: dict[str, Json] = {} | |
| for rank, path in sorted(paths.items()): | |
| trace = T.load(path) | |
| steps = [s for s in T.steps(trace) if s.phase == ('decode' if kind == 'decode' else 'prefill')] | |
| per: list[dict[str, float]] = [] | |
| for s in steps: | |
| row: dict[str, Json] = {'period_ms': s.period / 1e3, 'rows': s.rows} | |
| row.update(cast(dict[str, Json], sp1_split(s))) | |
| row.update(cast(dict[str, Json], moe_wall(s))) | |
| fam = T.family_totals(s) | |
| row['family_union_ms'] = {f: v['union_us'] / 1e3 for f, v in fam.items()} | |
| row['family_sum_ms'] = {f: v['sum_us'] / 1e3 for f, v in fam.items()} | |
| if kind == 'decode': | |
| row['dense'] = dense_decode(s) | |
| row['smoe'] = smoe_decode(s, s.requests) | |
| else: | |
| if s.rows < 1024: # a short tail chunk is not a full-chunk budget | |
| continue | |
| row['tf'] = tf_prefill(s) | |
| row['reconstruct'] = reconstruct_prefill(s) | |
| per.append(_flatten(row)) | |
| if not per: | |
| raise ValueError(f'{path.name}: no complete {kind} steps') | |
| keys = sorted(set().union(*per)) | |
| ranks[str(rank)] = {'file_sha256': hashlib.sha256(path.read_bytes()).hexdigest(), 'steps': len(per), | |
| 'rows_hist': cast(Json, _hist([p['rows'] for p in per])), | |
| 'mean': cast(Json, {k: _mean([p.get(k, 0.0) for p in per]) for k in keys}), | |
| 'per_step': cast(Json, per)} | |
| return {'kind': kind, 'ranks': ranks} | |
| def _hist(xs: Sequence[float]) -> dict[str, int]: | |
| out: dict[str, int] = {} | |
| for x in xs: | |
| out[str(int(x))] = out.get(str(int(x)), 0) + 1 | |
| return dict(sorted(out.items(), key=lambda kv: int(kv[0]))) | |
| def window(root: Path) -> dict[str, Json]: | |
| manifest = [record(x) for x in cast(list[Json], json.loads((root / 'traces-manifest.json').read_text()))] | |
| groups: dict[str, dict[int, Path]] = {} | |
| for row in manifest: | |
| label = f"{row['cell']}:{row['capture']}" | |
| path = (root / str(row['file'])).resolve() | |
| if root.resolve() not in path.parents: | |
| raise ValueError('manifest path leaves the window') | |
| if hashlib.sha256(path.read_bytes()).hexdigest() != row['sha256']: | |
| raise ValueError(f'{path.name}: sha256 differs from the manifest') | |
| groups.setdefault(label, {})[int(number(row['rank']))] = path | |
| out: dict[str, Json] = {} | |
| for label in (*DECODE_CAPTURES, *PREFILL_CAPTURES): | |
| if label not in groups: | |
| continue | |
| out[label] = capture(groups[label], 'decode' if label.endswith(':decode') else 'prefill') | |
| return out | |
| def constants() -> dict[str, Json]: | |
| gu, dn = expert_mean_bytes() | |
| return {'status': 'ESTIMATED bytes (offload/kl1/shapes.py); MEASURED kineto durations; UPSTREAM CLAIM bandwidths', | |
| 'mean_k': S.mean_k(), 'expert_gu_mb': gu / 1e6, 'expert_down_mb': dn / 1e6, | |
| 'shared_expert_mb': shared_bytes() / 1e6, | |
| 'dense_mb_per_layer': {lin.name: lin.bytes / 1e6 for lin in S.DECODE_DENSE}, | |
| 'lm_head_mb': S.LM_HEAD.bytes / 1e6, 'router_mb': S.ROUTER_BYTES / 1e6, 'mhc_mb': S.MHC_BYTES / 1e6, | |
| 'gb10_spec_gbps': S.GB10_SPEC_GBPS, 'source_read_gbps': cast(Json, list(S.SOURCE_READ_GBPS)), | |
| 'source_decode_gbps': cast(Json, list(S.SOURCE_DECODE_GBPS))} | |
| def main(argv: Sequence[str] | None = None) -> int: | |
| ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| ap.add_argument('--window', type=Path, action='append', required=True) | |
| ap.add_argument('--output', type=Path, required=True) | |
| args = ap.parse_args(argv) | |
| if args.output.exists(): | |
| print(f'{args.output} exists; refusing to overwrite', file=sys.stderr) | |
| return 1 | |
| rules() # fail early if buckets.json is unreadable | |
| result: dict[str, Json] = {'type': 'kl1-bandwidth', 'constants': constants(), 'windows': {}} | |
| for root in args.window: | |
| cast(dict[str, Json], result['windows'])[root.name] = window(root) | |
| args.output.write_text(json.dumps(result, indent=1, allow_nan=False) + '\n') | |
| return 0 | |
| if __name__ == '__main__': | |
| raise SystemExit(main()) | |