Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
2f648cf
feat: add EXL3 Flash Next execution and expert offload
BlivionIaG Oct 18, 2026
f2500d8
feat: add TP=2 expert-parallel serving and MTP
BlivionIaG Oct 19, 2026
59a63c6
feat: add deep prefill and long-context state placement
BlivionIaG Oct 19, 2026
4304cb1
feat: add BF16 KV paging and TP=2 graph capture
BlivionIaG Oct 19, 2026
7b960f2
perf: harden TP=2 graph replay and prefill overlap
BlivionIaG Oct 20, 2026
09d7da9
merge: integrate current upstream main for EXL3
BlivionIaG Oct 20, 2026
b244063
docs: keep current EXL3 paging, graph, and validation notes
BlivionIaG Oct 20, 2026
b8a60fd
feat: add EXL3 Flash Next execution and expert offload
BlivionIaG Oct 18, 2026
d9b4c9f
feat: add TP=2 expert-parallel serving and MTP
BlivionIaG Oct 19, 2026
1bf973b
feat: add deep prefill and long-context state placement
BlivionIaG Oct 19, 2026
7f03189
feat: add BF16 KV paging and TP=2 graph capture
BlivionIaG Oct 19, 2026
af3bb9c
perf: harden TP=2 graph replay and prefill overlap
BlivionIaG Oct 20, 2026
b430974
docs: keep current EXL3 paging, graph, and validation notes
BlivionIaG Oct 20, 2026
656bcf9
fix: retain validated merge resolutions after upstream rebase
BlivionIaG Oct 20, 2026
418ecdd
fix: dispatch EXL3 through retained CUDA block entrypoints
BlivionIaG Oct 20, 2026
620a886
fix: cover retained EXL3 dispatch with a host regression
BlivionIaG Oct 20, 2026
e0ca28b
update: integrate latest-main rebase into the EXL3 draft
BlivionIaG Oct 20, 2026
adefc26
perf: omit unused classic MoE scratch for EXL3 engines
BlivionIaG Oct 20, 2026
a07a4b3
update: include EXL3 scratch efficiency in the draft
BlivionIaG Oct 20, 2026
d3662d1
fix: budget EXL3 graph residency before pool and lane admission
BlivionIaG Oct 21, 2026
04657f3
update: include bounded EXL3 graph memory management
BlivionIaG Oct 21, 2026
1ed05d8
perf: retain hot EXL3 graphs with workload-aware cache budgets
BlivionIaG Oct 21, 2026
40fda50
fix: align EXL3 device tests with upstream rank loader API
BlivionIaG Oct 21, 2026
ca2b13c
fix: reclaim local graphs after coordinated TP cache exhaustion
BlivionIaG Oct 21, 2026
e316d75
fix: integrate bounded hot graph retention and CUDA test compatibility
BlivionIaG Oct 21, 2026
453519a
feat: add bounded opt-in EXL3 graph cache profiling
BlivionIaG Oct 21, 2026
33d3829
fix: subdivide BF16 attention paging by cache capacity
BlivionIaG Oct 21, 2026
3eef88f
feat: add opt-in coordinated GDN state rebinding for CUDA graphs
BlivionIaG Oct 21, 2026
0de41d3
feat: share BF16 KV page storage across request lanes
BlivionIaG Oct 21, 2026
8a0e023
fix: guard shared KV lease rollback and restored epochs
BlivionIaG Oct 21, 2026
ccb32fc
test: exercise TP2 shared KV snapshot isolation and remapping
BlivionIaG Oct 21, 2026
dd08cf5
feat: integrate qualified shared BF16 paging checkpoint
BlivionIaG Oct 21, 2026
4d28a4e
docs: record scoped shared BF16 paging qualification
BlivionIaG Oct 21, 2026
713e379
fix: retain graph state bindings across bounded verify windows
BlivionIaG Oct 21, 2026
b3a7b56
test: exercise TP2 state rebinding for wider verify windows
BlivionIaG Oct 21, 2026
d490c3f
feat: measure expert and BF16 paging misses independently
BlivionIaG Oct 21, 2026
975c1df
fix: scope cache metrics at common MTP verification entry
BlivionIaG Oct 21, 2026
001de24
docs: record bounded replay and cache-counter qualification
BlivionIaG Oct 21, 2026
52ed4ac
perf: fuse EXL3 split-K counter reset into input rotation
BlivionIaG Oct 21, 2026
217f4a3
refactor: name EXL3 launch regression inputs explicitly
BlivionIaG Oct 21, 2026
b9f4919
perf: rebind BF16 prefill graph origins without recapture
BlivionIaG Oct 21, 2026
4afc616
perf: recycle BF16 paged request allocations with fresh owners
BlivionIaG Oct 21, 2026
4307b69
docs: distinguish churn implementations from qualified GPU gates
BlivionIaG Oct 21, 2026
328e12b
docs: record real BF16 lifecycle and prefill rebinding gates
BlivionIaG Oct 21, 2026
99cab1b
feat: seed empty EXL3 pools from learned expert frequencies
BlivionIaG Oct 21, 2026
cf3f535
feat: persist bounded EXL3 placement frequency profiles atomically
BlivionIaG Oct 21, 2026
53ef503
update: exercise EXL3 frequency profile restarts on CUDA
BlivionIaG Oct 21, 2026
f56726b
feat: restore and save EXL3 placement priors across engine lifetimes
BlivionIaG Oct 21, 2026
ee4c246
fix: reject nonregular EXL3 profile locks without blocking startup
BlivionIaG Oct 21, 2026
babc976
fix: open EXL3 profile directories with chmod-capable handles
BlivionIaG Oct 22, 2026
b46e273
docs: record native TP2 expert-profile restart qualification
BlivionIaG Oct 22, 2026
2016e6c
refactor: name the profile warm-start pool parameter explicitly
BlivionIaG Oct 22, 2026
ce10271
perf: overlap prefill staging and coalesce release fences
BlivionIaG Oct 22, 2026
090fabf
feat: attribute expert transfers to demand and prefetch origins
BlivionIaG Oct 22, 2026
b5b385d
perf: coalesce proven contiguous expert DMA ranges
BlivionIaG Oct 22, 2026
a25fa22
fix: bind prefill graphs to their actual GDN layer buffers
BlivionIaG Oct 22, 2026
abc040f
docs: clarify transfer provenance and the prefill default
BlivionIaG Oct 22, 2026
73d6065
perf: size speculative prefetch from matching target-layer history
BlivionIaG Oct 22, 2026
0272aaf
perf: integrate qualified target-layer prefetch history
BlivionIaG Oct 22, 2026
5d5104b
perf: accelerate Blackwell mul1 prompt fragment loads
BlivionIaG Oct 22, 2026
5a94359
test: check nonfinite EXL3 prompt fragment classes
BlivionIaG Oct 22, 2026
dfdf27c
docs: record TP2 prefill overlap profile
BlivionIaG Oct 22, 2026
ac22ec0
perf: consume TP2 prefill reductions directly in writeback
BlivionIaG Oct 22, 2026
cb77e30
docs: record reduced TP2 prefill writeback result
BlivionIaG Oct 22, 2026
c095205
docs: record rejected dense prompt tile experiment
BlivionIaG Oct 22, 2026
d16c1d0
docs: record larger TP2 prefill chunk screen
BlivionIaG Oct 22, 2026
84d202b
docs: record TP2 chunk ABBA and AVX2 feasibility
BlivionIaG Oct 22, 2026
73a27bd
docs: record TP2 decode critical path
BlivionIaG Oct 22, 2026
be136f5
docs: record PCIe and no-prefetch measurements
BlivionIaG Oct 22, 2026
6ce99f4
docs: confirm prefetch result on both cards
BlivionIaG Oct 22, 2026
689c55a
docs: add DRAM counters for decode kernel
BlivionIaG Oct 22, 2026
a258160
docs: record TP2 no-prefetch serving result
BlivionIaG Oct 22, 2026
8642d88
perf: increase EXL3 prompt chunk size
BlivionIaG Oct 22, 2026
9d6c0b5
docs: record TP2 decode profile and cumulative gains
BlivionIaG Oct 22, 2026
a3246ef
docs: record TP2 prefill optimization screens
BlivionIaG Oct 22, 2026
c5c53ef
docs: record persisted expert-placement A/B
BlivionIaG Oct 22, 2026
ab7ac3f
docs: record TP2 collective grouping regression
BlivionIaG Oct 22, 2026
763e662
docs: record matched TP2 prefill gain over 5a baseline
BlivionIaG Oct 22, 2026
54ee8b8
docs: record matched TP2 graph decode comparison
BlivionIaG Oct 22, 2026
3c83cb1
docs: record TP2 paged decode miss profile
BlivionIaG Oct 22, 2026
54f9799
docs: record fragment kernel serving abba
BlivionIaG Oct 22, 2026
6b042b2
docs: extend fragment abba to 512 tokens
BlivionIaG Oct 22, 2026
df4ab21
fix: preserve EXL3 dispatch in batched decode
BlivionIaG Oct 23, 2026
6604d23
docs: record TP2 prefill overlap ABBA results
BlivionIaG Oct 23, 2026
c9fe688
docs: record prefill expert lookahead measurements
BlivionIaG Oct 23, 2026
ab030af
docs: record reduced TP2 prefill writeback result
BlivionIaG Oct 23, 2026
0be29f9
docs: record shared paging graph budget failure
BlivionIaG Oct 23, 2026
c2b669f
fix: size EXL3 memory budgets for server concurrency
BlivionIaG Oct 23, 2026
d073b22
perf: tune TP2 prefill expert lookahead
BlivionIaG Oct 23, 2026
f9c38cd
docs: record score-grid graph ABBA result
BlivionIaG Oct 23, 2026
0031f54
docs: record MTP verify-best ABBA result
BlivionIaG Oct 23, 2026
4bff271
docs: record clean TP2 graph decode ABBA
BlivionIaG Oct 23, 2026
c94226e
docs: record expert-copy overlap and cache miss results
BlivionIaG Oct 23, 2026
09bf5e7
docs: record concurrent long-context BF16 paging proof
BlivionIaG Oct 23, 2026
f61e68b
docs: record matched expert profile miss A/B
BlivionIaG Oct 23, 2026
7d0970d
feat(exl3): add recent-demand device eviction
BlivionIaG Oct 24, 2026
740b23a
fix: exclude deep-prefill staging from expert history
BlivionIaG Oct 24, 2026
e22c455
fix: retain paged prefill graphs across snapshot restore
BlivionIaG Oct 24, 2026
ebc318a
docs: update BF16 snapshot graph gate results
BlivionIaG Oct 24, 2026
eda5aad
perf: add EXL3 prefill demand planning and telemetry
BlivionIaG Oct 24, 2026
f83d18a
perf: run the MTP head over a paired prefill window in two deep slices
BlivionIaG Oct 24, 2026
55846d7
docs: record the snapshot gate on the paired-window head build
BlivionIaG Oct 24, 2026
67592af
docs: record window admission thresholds and the 60k-token prompt ABBA
BlivionIaG Oct 24, 2026
6c0df33
docs: correct the report; the window gives no decode gain
BlivionIaG Oct 24, 2026
ba7e606
docs: estimate the exposed expert-copy share of decode time
BlivionIaG Oct 24, 2026
8867c5f
perf: default-off DPLAN trace dump and a next-layer prefetch predicta…
BlivionIaG Oct 24, 2026
a5bada4
docs: two-chunk window on different real-text prompts per request
BlivionIaG Oct 24, 2026
57281dc
docs: graph reservation trimming does not admit the window; default t…
BlivionIaG Oct 24, 2026
4a350bf
docs: nsys decode profile and a null DCOPY_BLOCKS sweep
BlivionIaG Oct 24, 2026
2093513
perf: hit/miss overlap on by default with few copy blocks (+4% decode…
BlivionIaG Oct 24, 2026
da88828
docs: drafted decode is slower than undrafted on real text
BlivionIaG Oct 24, 2026
850b631
perf: decode plain by default when experts are offloaded (TF_FLASHNEX…
BlivionIaG Oct 24, 2026
92a5592
docs: second independent ABBA of the default-path changes on real text
BlivionIaG Oct 24, 2026
bffe8da
docs: Strata comparison on the same hardware; add the single-request …
BlivionIaG Oct 25, 2026
45049cc
docs: Strata comparison on the same hardware; add the shared bench cl…
BlivionIaG Oct 25, 2026
6296087
docs: Strata's expert cache hit rate explains most of the decode gap
BlivionIaG Oct 25, 2026
7d705d3
docs: per-rank memory ledger and the expert-lines sweep (52 lines fit…
BlivionIaG Oct 25, 2026
0e42a66
tools: launch script for the measured-fastest TP=2 settings (52 lines…
BlivionIaG Oct 25, 2026
21689a9
docs: pack size breakdown and a sized plan for a larger expert cache;…
BlivionIaG Oct 25, 2026
a2d29c6
tools: serve script defaults to 68 lines with the KV window sized for…
BlivionIaG Oct 25, 2026
5b04f51
tools+docs: single-user 80-line setup; the cache curve flattens, the …
BlivionIaG Oct 25, 2026
f06f236
docs+tools: nsys decode-step breakdown at 80 lines; matmul kernels, n…
BlivionIaG Oct 25, 2026
b2f5162
docs: map decode kernels to weights and their bandwidth; two TP-prese…
BlivionIaG Oct 25, 2026
c9e724f
docs: why the dense kernels sit at half the memory limit and the exac…
BlivionIaG Oct 25, 2026
e47a3f4
docs: wide split-K for the 5-bit matmuls tested and rejected
BlivionIaG Oct 25, 2026
757af79
docs: clocks and power are healthy; dense kernels use 45% of memory t…
BlivionIaG Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions docs/plans/exl3-graphs.md

Large diffs are not rendered by default.

460 changes: 460 additions & 0 deletions docs/plans/exl3-kv-paging.md

Large diffs are not rendered by default.

1,373 changes: 1,373 additions & 0 deletions docs/plans/exl3-on-upstream-final-report.md

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions tools/exl3_dplan_predict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Next-layer expert predictability from DPLAN v4 trace dumps (TF_FLASHNEXT_EXL3_DPLAN_TRACE_DIR). Usage: dir."""
import struct, glob, os, re, collections, sys
D = sys.argv[1]
pools = {}
for f in glob.glob(D + "/*.bin"):
idx = int(re.search(r"pool(\d+)", f).group(1))
w = struct.unpack("<%dI" % (os.path.getsize(f) // 4), open(f, "rb").read())
layer, lines, maxr, sw, stride, cap, rec = w[3], w[4], w[5], w[6], w[7], w[8], w[9]
off = 16 + 2 * sw
rounds = []
for k in range(rec):
r = w[off + k * stride: off + (k + 1) * stride]
rows = r[0]
picks = [struct.unpack("<i", struct.pack("<I", x))[0] for x in r[stride - maxr * 11: stride - maxr * 11 + rows * 11]]
rounds.append((rows, frozenset(e for e in picks if 0 <= e < 512)))
pools[idx] = (layer, lines, rounds)
n = sorted(pools)
print("pools", len(n), "layers", [pools[i][0] for i in n][:5], "recorded", set(len(pools[i][2]) for i in n), "lines", set(pools[i][1] for i in n))

L = [i for i in n if len(pools[i][2]) > 0]
K = min(len(pools[i][2]) for i in L)
rows_ok = sum(1 for k in range(K) if len({pools[i][2][k][0] for i in L}) == 1)
print("layers with data", len(L), "rounds", K, "rounds with equal rows across layers", rows_ok)
sets = {i: [pools[i][2][k][1] for k in range(K)] for i in L}
LINES = pools[L[0]][1]
# per-layer LRU simulation -> miss sets
miss = {}
for i in L:
cache = collections.OrderedDict()
ms = []
for k in range(K):
need = sets[i][k]
m = set(e for e in need if e not in cache)
for e in need:
if e in cache: cache.move_to_end(e)
for e in m:
while len(cache) >= LINES:
for old in cache:
if old not in need:
del cache[old]; break
else:
break
cache[e] = 1
ms.append(m)
miss[i] = ms
tot_miss = sum(len(m) for i in L for m in miss[i]); tot_need = sum(len(s) for i in L for s in sets[i])
print("LRU(%d lines) misses per layer-round %.2f of %.2f needed (%.1f%%)" % (LINES, tot_miss / (K * len(L)), tot_need / (K * len(L)), 100 * tot_miss / tot_need))
T = K // 2
def evaluate(Ns, use_prev_round=False):
# co-occurrence: layer i -> layer i+1 within the same round, trained on rounds < T
out = {N: [0, 0, 0, 0] for N in Ns} # covered misses, total misses, predicted-not-cached, useful(pred & need & notcached)
for a, b in zip(L, L[1:]):
co = collections.defaultdict(collections.Counter)
freq = collections.Counter()
for k in range(T):
freq.update(sets[b][k])
for e in sets[a][k]:
co[e].update(sets[b][k])
# replay cache state for layer b through test rounds again to know what's cached
cache = collections.OrderedDict()
for k in range(K):
need = sets[b][k]
if k >= T:
score = collections.Counter()
for e in sets[a][k]:
tot = sum(co[e].values()) or 1
for x, c in co[e].items(): score[x] += c / tot
for x, c in freq.items(): score[x] += 0.01 * c / T
ranked = [x for x, _ in score.most_common() if x not in cache]
m = set(x for x in need if x not in cache)
for N in Ns:
pred = set(ranked[:N])
out[N][0] += len(m & pred); out[N][1] += len(m); out[N][2] += len(pred); out[N][3] += len(m & pred)
m = set(e for e in need if e not in cache)
for e in need:
if e in cache: cache.move_to_end(e)
for e in m:
while len(cache) >= LINES:
for old in cache:
if old not in need:
del cache[old]; break
else: break
cache[e] = 1
return out
res = evaluate([2, 4, 8, 16, 32])
for N, (cov, tm, pred, useful) in res.items():
print("prefetch top-%d from previous layer's picks: covers %.1f%% of misses; %.2f copies issued per layer-round, %.1f%% of issued copies useful" % (N, 100 * cov / tm, pred / ((K - T) * (len(L) - 1)), 100 * useful / pred))
144 changes: 144 additions & 0 deletions tools/exl3_kv_budget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""Per-card memory budget for Qwen3.8-Flash-Next EXL3 at 200k context on two 16 GiB cards (docs/plans/exl3-kv-memory.md).

exl3_kv_budget.py [CONTEXT]

Every byte formula is upstream's (cuda_prompt.zig seqBytes, cuda_batch_state.zig Scratch.bytes, cuda_mtp_draft.zig
Draft.bytes) with the K10 levers applied (cuda_exl3_kv.zig seqBytes gives the same numbers; its host test checks the
default policy against upstream's). Inputs that are not formulas are marked where they come from.
"""
import sys

G = 2 ** 30
CTX = int(sys.argv[1]) if len(sys.argv) > 1 else 200_000
CAP = CTX + 16 # the window a stream holds: context + mtp.max_rows (cuda_lanes_load.zig)

# -- model shapes (cuda_prompt_wt.zig, cuda_gdn.zig) ------------------------------------------------------------------
dims, streams, low, vocab, experts_n, slots, moe_w, tile = 2560, 4, 320, 248320, 512, 11, 640, 16
wide = streams * dims
q_heads, kv_heads, head_dim, index_heads, index_dim, half, proj_n, out_k, nch = 24, 2, 256, 4, 128, 32, 13952, 6144, 5
idw, att_rows, hc_sk, out_sk, plan_small = 2052, 256, 32, 8, 1024
nk, nv, dk, dv, taps = 16, 48, 128, 128, 4
conv_dim = 2 * nk * dk + nv * dv
value_dim = nv * dv
proj_row = max(conv_dim + nv * dv + 2 * nv, proj_n)
pss_n = (dims // 256) * streams
state_n = nv * dv * dk
linear_n, attn_n, ple_tail_n = 36, 12, 9
cdiv = lambda a, b: (a + b - 1) // b


def scratch(cap, whole_head=False):
"""cuda_batch_state.zig Scratch.bytes(cap), term for term; whole_head drops head_local (cap * vocab bytes), which
only a vocabulary-split head reads (cuda_batch.zig projectHead)."""
pairs = cap * slots
nblk = cdiv(pairs, plan_small)
items = cdiv(pairs, tile) + experts_n + 1
n = cap * 4 + (cap + 1) * 16
n += cap * wide * 2 * 2 + cap * pss_n * 4 + cap * (low + 4) * 2 + hc_sk * cap * (low + 4) * 4
n += cap * wide * 2 + 8 * cap * dims * 4
n += cap * low * 2 + cap * (low // 32) * 4 + cap * streams * 2 + cap * dims * 2 + cap * (dims // 32) * 4
n += cap * proj_row * 2 + out_sk * cap * dims * 4 + cap * value_dim * (2 + 4 + 4) + cap * (value_dim // 32) * 4
n += cap * (2 * nk * dk + 2 * nv) * 4
n += cap * q_heads * head_dim * 2 + cap * index_heads * index_dim * 2
n += att_rows * nch * q_heads * (head_dim + 2) * 4 + cap * out_k * 2 * 2 + cap * (out_k // 32) * 4
n += att_rows * (idw + 2) * 4
n += cap * (experts_n + 1) * 4 + pairs * 4 * 2 + pairs * 4 + items * 3 * 4 + 8 + pairs * 4 + nblk * (experts_n + 1) * 4
n += cap * moe_w * 6 + cap * dims * 4
n += pairs * moe_w * 2 + pairs * dims * 4 + cap * dims * 2
n += cap * dims * 2 + cap * (dims // 32) * 4 + cap * wide * 2 + cap * dims * 2 + 4 * cap * dims * 4
n += cap * wide * 2 * 2 + cap * streams * 4 + vocab * 2 + (0 if whole_head else cap * vocab) + half * 4 + 4096
return n


def kvrow(kv, heads):
per = {"bf16": head_dim * 2, "int8": head_dim + head_dim // 32 * 2, "int4": head_dim // 2 + head_dim // 32 * 2}[kv]
return 2 * heads * per


def pooled(cap):
return cdiv(cap, 4) * index_dim * 2


def seq(cap, kv="bf16", heads=2, kv_host=False, ikc_host=False):
"""(device, host) bytes of one stream: cuda_prompt.zig seqBytes with the levers."""
gdn = linear_n * ((taps - 1) * conv_dim * 2 + state_n * 4)
k = attn_n * cap * kvrow(kv, heads)
i = attn_n * cap * index_dim * 2
dev = gdn + (0 if kv_host else k) + (0 if ikc_host else i) + attn_n * pooled(cap) + ple_tail_n * wide * 2
return dev, (k if kv_host else 0) + (i if ikc_host else 0)


def draft(cap, kv="bf16", kv_host=False, ikc_host=False, rows=1536, draft_n=79591):
"""cuda_mtp_draft.zig Draft.bytes (rank 0, MTP head's one attention layer under the same policy)."""
fixed = max(rows, 16) * wide * 2 + wide * 2 + 16 * vocab * 2 + draft_n * 2 + 16 * 16 + 16 * wide * 2 + linear_n * state_n * 4
return fixed + (0 if kv_host else cap * kvrow(kv, kv_heads)) + (0 if ikc_host else cap * index_dim * 2) + pooled(cap)


# -- inputs that are not formulas -----------------------------------------------------------------------------------------
CARD = 15.568 # GiB, cuMemGetInfo total on a 5060 Ti (measured, exl3-kv bench)
CONTEXT_MODULES = 0.40 # GiB, CUDA context + every module the engine loads (estimate; our test's modules alone: 0.134 measured)
WEIGHTS = {0: 5.107 + 0.475, 1: 5.245} # GiB, T10's expert-parallel placement (read from the pack's headers); rank 0 + MTP head
OTHER = 0.05 + 0.015 + 0.05 # GiB: block scores at 200k (scoreBytes, read), lanes logits (read), EXL3 Dense scratch (estimate)
LINE = 0.136 # GiB a pool line in every one of 48 layers (T10: 10 lines/layer = 1.36 GiB)
FLOOR = 10 # lines a layer, the pool's floor
# decode hit rate at tp=1 by lines a layer, measured by E2 on real routing (512 experts in one pool)
HIT = [(10, 0.33), (24, 0.505), (48, 0.64), (96, 0.78), (160, 0.88)]
MISS_MS = 0.147 # one 3-bit expert staged from a pinned bank (measured, E2)


def hit_tp1(lines):
import math
if lines <= HIT[0][0]:
return HIT[0][1] * lines / HIT[0][0]
for (a, ha), (b, hb) in zip(HIT, HIT[1:]):
if lines <= b:
t = (math.log(lines) - math.log(a)) / (math.log(b) - math.log(a))
return ha + t * (hb - ha)
return HIT[-1][1]


def miss_ms(lines_rank):
"""Expert-miss staging a token on one rank (estimate): a rank serves its 256 experts, ~5 picks a row a layer; its
L lines hit like a tp=1 pool of 2L lines over all 512."""
h = hit_tp1(2 * lines_rank)
return 48 * 5 * (1 - h) * MISS_MS, h


LEVERS = [
# name, kv dtype, heads a rank, kv_host, ikc_host, chunk, whole-head scratch
("A upstream: bf16 KV, chunk 1536", "bf16", 2, False, False, 1536, False),
("B int8 KV", "int8", 2, False, False, 1536, False),
("C = B + index keys in host memory", "int8", 2, False, True, 1536, False),
("D = C + scratch: chunk 1024, no head_local", "int8", 2, False, True, 1024, True),
("E = D + attention head split", "int8", 1, False, True, 1024, True),
("F bf16 KV + head split + index keys on host + D's scratch", "bf16", 1, False, True, 1024, True),
("G host KV (int8, mapped) + index keys on host + D's scratch", "int8", 2, True, True, 1024, True),
]


def table(drafts=True):
print(f"drafts {'on (rank 0 holds every stream' + chr(39) + 's draft cache)' if drafts else 'off'}")
print(f"| lever (context {CTX:,}) | seqs | rank 0 seq+draft GiB | rank 0 left for pool GiB | lines/layer r0 | r1 lines | decode miss ms/token (est.) | host GiB |")
print("|---|---:|---:|---:|---:|---:|---:|---:|")
for name, kv, heads, kvh, ikh, chunk, whole in LEVERS:
for n in (1, 2, 4):
d, h = seq(CAP, kv, heads, kvh, ikh)
dr = draft(CAP, kv, kvh, ikh, rows=chunk) if drafts else 0
fixed = CONTEXT_MODULES + scratch(chunk, whole) / G + OTHER
left = {r: CARD - WEIGHTS[r] - fixed - n * d / G - (n * dr / G if r == 0 else 0) for r in (0, 1)}
lines = {r: max(0, int(left[r] / LINE)) for r in (0, 1)}
ok = min(lines.values()) >= FLOOR
ms, hit = miss_ms(min(lines.values())) if ok else (float("nan"), 0)
host = n * h / G
print(f"| {name} | {n} | {n * (d + dr) / G:.2f} | {left[0]:.2f} | {lines[0] if left[0] > 0 else 'no fit'} | {lines[1] if left[1] > 0 else 'no fit'} | {'%.1f (hit %.2f)' % (ms, hit) if ok else 'below floor'} | {host:.1f} |")


if __name__ == "__main__":
print(f"scratch GiB by chunk: " + ", ".join(f"{c}: {scratch(c) / G:.3f} ({scratch(c, True) / G:.3f} whole head)" for c in (1536, 1024, 512, 256)))
d, _ = seq(CAP)
print(f"one stream at {CTX:,} (bf16, upstream): {d / G:.3f} GiB; draft cache {draft(CAP) / G:.3f} GiB")
print()
table(True)
print()
table(False)
Loading