123 Commits

Author SHA1 Message Date
mukesh fc4747668e paper(§5): add roofline, capacity planning, parallelism selection subsections
Three new subsections at the top of §5, motivated by the deployment-sizing
questions the analytical visualization tool exposes:

- 5.1 Roofline Analysis — B*, L*, short vs long context batch effect
  (measured: 19× per-token cost reduction at short context, 28% at long)
- 5.2 Capacity Planning — 3-axis sizing (weights / KV / SLO), SLO targets,
  regime rules, deployment templates, per-axis playbook
- 5.3 Parallelism Selection — TP × CP × PP × DP × EP comparison,
  symptom-driven axis selection, add/stop criteria, misconceptions

Restructure:
- 05-gqa.tex trimmed to section header + intro
- Existing 5.4-5.7 content (placement, short/long ctx, composite) moved
  to 05x-fused-kernel.tex
- Summary extracted to 05z-summary.tex, cross-refs updated
- main.tex \input order sets the requested subsection sequence
- lmodern loaded to satisfy microtype font-expansion

Two new figures generated from tests/analytical_visualization/chip_roofline:
- roofline_short_context.png (S_kv=8K, batch wins)
- roofline_long_context.png (S_kv=1M, batch stalls at KV floor)
- Generator: tests/analytical_visualization/_gen_roofline_paper_figs.py

4 tables (regime rules, deployment templates, playbook, parallelism
criteria) use table* so they span both columns without overflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-30 15:56:41 -07:00
mukesh bfe3e0e8d1 analytical-viz: parallelism rules — Section 11 TP vs CP shared-budget
New Section 11 'TP vs CP: the shared-budget tradeoff' on the
Parallelism rules tab. Captures the core insight that fixing TP·CP
doesn't equalize the two configurations — larger TP additionally
divides model-weight traffic, larger CP only divides the sequence
dimension.

Five sub-tables:

  11a. Same TP·CP = 16 → different weight sharding
       (TP=8/CP=2 reads 4× fewer weight bytes per PE than
        TP=2/CP=8 at the same KV division)
  11b. What each dimension actually shards
       (TP·CP·PP·DP × Weights·KV·Attention-math)
  11c. When to prefer TP vs CP (6 situations mapped to the winning
       axis with reasoning)
  11d. CP communication profile (prefill vs decode) — bytes/hop,
       overlap potential, real cost, notes on kv vs qoml variants
  11e. Decode cost decomposition mental model:
       T_decode = T_weight_read(TP) + T_KV_read(TP,CP)
                + T_TP_comm(TP) + T_CP_merge(CP)

Reframes the 'is CP heavier than TP' question as 'which cost
dominates T_decode, and which axis attacks it?'.

Smoke test asserts the section title appears exactly once.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-30 09:26:22 -07:00
mukesh 55653cbb3e analytical-viz: new 'Parallelism rules' tab
New last tab distilling the parallelism-selection discussion into
tables. Framed up-front as heuristics + hard constraints, not a
production procedure — with public counterexamples (DeepSeek-V3 PP16
no TP; vLLM PP-on-non-NVLink) that break the naive 'start TP at 8'
rule.

Ten sections, all table-first:

  1. The core principle (chattiest comm on fastest links)
  2. Parallelism techniques × comm profile (DP/TP/PP/CP/EP with
     Shards, Pattern, Frequency, Per-link volume as degree ↑,
     Hard ceiling)
  3. Dominant problem → first axis to try (11 rows: weight fit,
     activation memory, single-seq KV, many-seq KV, MoE experts,
     TPOT, aggregate throughput, multi-node, expert layer, global
     batch cap)
  4. When to add / when to stop, per axis (DP/TP/PP/CP/EP/FSDP/SP)
  5. Common misconceptions vs reality (9 rows: KV-head hard
     ceiling, always start TP at 8, EP comm falling, attn-DP
     shards KV for free, DP > CP always, PP inside domain
     worthless, memory is only feasibility, FSDP as substrate,
     pipeline bubble = (P-1)/m)
  6. Inference decision procedure (7 ordered steps)
  7. Inference cases → recommended shape (A–E: fits on 1 GPU /
     within NVLink / multi-node / extreme context / many moderate)
  8. Training baseline recipe (6 ordered steps)
  9. Sanity checks for any candidate config (7 checks)
 10. One-paragraph rule + honest caveat block

All content is st.markdown + st.dataframe — no new pure functions.
Smoke test asserts the tab title + `with tab_prules:` block are both
present in app.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-30 09:11:08 -07:00
mukesh 5ce7a5b30b analytical-viz: capacity planning — symbol legend + plain-English formulas
Section 1 rewritten so a layman can read it without a glossary:

1. **Symbol legend table** listing every letter that appears in the
   three-axis formulas (N, b, N·b, HBM_per_PE, S_kv, kv_bpt,
   users_per_replica, TPOT SLO, step_latency, B_at_SLO, n_users,
   N_replicas, ⌈ ⌉). Each row: Symbol / Plain English / Value now
   for the current sidebar model + chip. For example:
     - N = 'Total number of model parameters (attention weights +
       FFN weights, across ALL layers)' → 6.98 billion params for
       Llama 3 8B
     - kv_bpt = 'KV cache bytes per token per user, summed across all
       layers (= 2·H_kv·d_head·b·layers)' → 128 KB/token

2. **Three-axis table** widened from (Formula / Meaning / Grows with)
   to (In symbols / In plain English / What it says / Value now).
   Axis A now shows the actual substituted arithmetic:
   ⌈ 13.96 GB / 6.0 GB ⌉ = ⌈ 2.33 ⌉ = 3 PEs.
   B and C flag 'see calculator' because they depend on user inputs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-30 08:35:59 -07:00
mukesh bc5e704572 analytical-viz: Physical Layout — TTFT & TPOT full-model latency section
New section at the bottom of the Physical Layout tab that sums the
per-stage latencies into end-to-end metrics for the current sharding:

- **TPOT** = one decode-step time × N_layers  (per output token)
- **TTFT** = one prefill pass time × N_layers  (for a prompt of the
  input length, computed in prefill mode)
- **E2E** = TTFT + (N_out − 1) × TPOT
- **Throughput/replica** = B / TPOT  (tokens/sec)

Two number inputs: prompt length (default = sidebar S_kv) and target
N_out tokens (default 100). Four KPI cards + a breakdown table with
per-layer and all-layer µs/ms for attention/FFN in both modes.

Both TTFT and TPOT are computed by cloning the sidebar TopologyConfig
with mode swapped ('decode' for TPOT, 'prefill' for TTFT with T_q =
prompt length), then reusing all_stages / all_ffn_stages — no new
math. Caveat noted in caption: PP=1 assumption (no microbatch
overlap / bubble modeling).

Sanity: Llama 3 8B on CP=2/TP=8/default SIP → TPOT ≈ 4.2 ms/token,
TTFT ≈ 1.1 s for an 8k prompt. Cross-checkable on the Per-stage
latency tab.

Smoke test in test_stage_shapes asserts the section title appears
exactly once and sits inside tab_layout (before tab_memory).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 15:51:06 -07:00
mukesh c39bbb16aa analytical-viz: capacity planning tab — add public-example B/replica table
Section 6 (new): 'Actually-provisioned batch sizes (public examples)'.
Ballpark B/replica figures for Gemini 1.5 Pro/Flash 1M, GPT-4.1 1M,
Claude 3.5 Sonnet 200k, and enterprise dedicated tiers, with the
inference basis for each ('pricing ~2x standard suggests low B',
etc). Caption flags that none are officially published; numbers are
backed out from public pricing + SLAs.

Binding-axis playbook renumbered #6#7.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 15:44:35 -07:00
mukesh 77abc95d78 analytical-viz: capacity planning tab — add rules of thumb + deployment templates
Two more table sections on the Capacity planning tab:

**Section 4 — Practical rules of thumb (by context regime)**
- Short (S_kv < L*): pack HBM, B ~ 2·B*, high util, cheap tier
- Long (S_kv > L*): fewer users/replica, low B, util drops as
  1/(1 + S_kv/L*), pricier
- Extreme (S_kv >> L*): dedicated pool, heavy CP, disaggregated
  prefill, very low util without sparse attention

Columns: Regime, Batch strategy, Utilization, Cost/token, Deployment.

**Section 5 — Sample deployment templates**
Same base model, three different sharding recipes routed to by the
API gateway based on request context length:
- Config_small : CP=1,  TP=8, PP=1 → 8 GPUs, up to 32k, B=64
- Config_medium: CP=4,  TP=8, PP=1 → 32 GPUs, up to 128k, B=32
- Config_large : CP=32, TP=8, PP=1 → 256 GPUs, up to 1M, B=4

Columns: Tier, CP, TP, PP, Total GPUs/replica, Max context, Typical
B, Best for.

Playbook section renumbered to #6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 15:13:08 -07:00
mukesh f54822d247 analytical-viz: move capacity planning to its own tab, table-first layout
New 'Capacity planning' tab (last one, after 'Chip roofline & B*').
Everything below is a dataframe table where a bullet list could have
gone, matching the user's 'mostly try to make tables to describe the
information' preference:

1. **Three-axis GPU sizing formula** (table: A/B/C rows with Formula,
   Meaning, 'Grows with' columns)
2. **Live sizing calculator** (KPI cards + 3 number inputs — this is
   the only interactive block on the tab)
3. **SLO standards by workload** (table: TTFT / TPOT targets for
   voice/code/chat/agentic/batch, with 'what binds' column)
4. **Hybrid deployment layers** (table: Layer 1 elastic pool /
   Layer 2 length-tier routing / Layer 3 disaggregated prefill/decode
   with Serves / Key mechanism / Admission rule / Typical config)
5. **Binding-axis playbook** (table: for each of A/B/C — Symptom,
   First lever, Second lever, Cost impact)

Removed the sizing calculator + 3 expanders from the bottom of the
Chip Roofline tab. Roofline tab is back to pure chip-vs-model /
formulas territory; planning tab has the workload / SLO / deployment
material.

Calculator uses the base sidebar chip (_default_machine) rather than
the roofline tab's knob-scaled chip, so results are stable across
navigation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 15:09:58 -07:00
mukesh b7c4c680aa analytical-viz: roofline tab — 'How hyperscalers pick GPU count' section
New section at the bottom of the Chip Roofline tab implementing the
three-axis GPU count decision:

  N_GPUs = max(capacity_floor, KV_headroom, throughput_SLO) × N_replicas

Interactive inputs: concurrent users, avg context/user, TPOT SLO.
Four KPI cards report each axis + the recommended total, with a
'binds:' delta chip showing which axis is the bottleneck.

  - Axis A (capacity):  PEs to hold weights alone
  - Axis B (KV):        PEs to hold weights + all KV of one replica's users
  - Axis C (throughput): replicas needed at B_at_SLO users each
  - Total: max(A,B) × N_replicas

Contextual caption explains what to do about the binding axis. If SLO
is infeasible (even B=1 exceeds SLO), an error block explains the
options (loosen SLO, shorten context, or scale up FLOPs/BW knobs).

Plus three collapsed expanders explaining the hybrid deployment
pattern hyperscalers use:
  - Layer 1: one elastic pool + PagedAttention + continuous batching
  - Layer 2: length-tier routing (standard vs long-context)
  - Layer 3: disaggregated prefill/decode (DistServe, Splitwise)

Pure additions in chip_roofline.py:
- max_batch_within_slo(machine, model, s_kv, slo_s) — analytical
  inverse of step_latency to find the largest per-replica B under SLO
- size_deployment(machine, model, n_users, avg_ctx, slo_s) — returns
  GpuSizingResult with all three axes + binding info

6 new tests cover: capacity scales with model size, KV grows with
users/context, binding axis flips with workload shape, tight SLO
shrinks max batch, weight-time-exceeds-SLO returns 0, total ==
per-replica × replicas.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 15:07:20 -07:00
mukesh 6410c0843c analytical-viz: roofline tab — fix NameError from knob-rename refactor
Previous commit renamed slider vars (_rf_flops_mult -> _flops_mult_now,
etc.) and split _ai/_b_star into base vs scaled, but the AI-sensitivity
plot's vertical markers + caption still referenced the old names, so
loading the tab crashed with:

    NameError: name '_rf_flops_mult' is not defined

Fixed all four call sites:
- _axAI1: 'Base AI' reference line now uses _ai_base (was _ai, which
  is now the scaled value).
- _axAI2: 'Base B*' reference line uses _b_star_base; vertical
  markers use _flops_mult_now / _bw_mult_now.
- Caption 'Your knob position' uses the new names.

Grep confirms no _rf_flops_mult / _rf_bw_mult / _ai_scaled /
_b_star_scaled references remain.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 14:51:01 -07:00
mukesh 97e765b58f analytical-viz: roofline knobs — absolute TFLOPS / GB/s, drive whole tab
Two changes:

1. **Knob units and ranges**
   - FLOPs slider: absolute TFLOPS, 1..32 (step 0.5). Was 0.25..8× multiplier.
   - HBM BW slider: absolute GB/s, 128..1024 (step 16). Was 0.25..8× multiplier.
   Defaults still initialize to the sidebar Hardware panel's base chip.
   Caption also shows the implied × multipliers of the base chip.

2. **Knobs now drive every roofline number on the tab.**
   Previously FLOPs/BW only affected the AI-sensitivity plot. Now the
   scaled machine feeds:
     - AI / B* / L* KPI cards
     - Regime KPI
     - Good-B / Good-L cards + utilization
     - Headline plots (step latency + cost/token)
     - Regime formulas table (t_com, t_mem_short, t_mem_long values)
     - Formula table (C, W, knee substitution)
   Left unchanged (intentionally):
     - PE memory budget stacks — depend on HBM capacity, not BW/FLOPs.
     - AI-sensitivity plot itself — sweeps around the SIDEBAR base
       chip so the curves are comparable across knob positions.

Header caption now shows both effective chip (from knobs) and base
chip (from sidebar) side by side.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 14:47:30 -07:00
mukesh bda76cbd66 analytical-viz: roofline tab — drop formula from step-latency header
Revert the multi-line formula annotation on the 'Latency per decode
step' header; make it a single-line markdown title matching the
'Cost per token' header on the right. Both headline plots now render
at the same height so the plot area lines up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 14:14:24 -07:00
mukesh a1c3c28f62 analytical-viz: roofline tab — PE memory budget, AI sensitivity, FLOPs/BW knobs
Three additions to the Chip Roofline tab:

1. **PE memory budget** section (two side-by-side stacked-area plots)
   - Left: per-PE memory vs S_kv (128..1M, log). Stacks: weights,
     KV, transient. HBM budget line + current-S_kv marker.
   - Right: per-PE memory vs B (1..256). Same stacks, current-B
     marker. KV grows linearly with either axis; weights are
     invariant of B and S_kv (fixed by CP·TP·PP sharding).
   Uses actual sharded per-PE quantities from memory_layout so the
   numbers match what the deployment would really allocate.

2. **AI sensitivity** section (two side-by-side line plots)
   - Left: AI vs chip-parameter multiplier. Two curves: scale FLOPs
     (AI grows linearly), scale HBM BW (AI shrinks inversely).
   - Right: B* vs multiplier. Same shape as AI (for BF16 where B*=AI).
   Vertical markers show current FLOPs/BW knob positions.

3. **Two new knobs** in the top row: FLOPs × multiplier and HBM BW ×
   multiplier (0.25..8, step 0.25). The AI-sensitivity plot markers
   move with them; the header caption shows scaled AI + B* for the
   selected point.

Also: formula annotation on the headline 'Latency per decode step'
plot header showing t_step = N·b/W + 2·N·B/C + B·S_kv·kv_bpt/W.

Pure additions in chip_roofline.py (all testable):
- MemoryBudgetPoint dataclass
- memory_budget_curve_vs_skv, memory_budget_curve_vs_batch
- AISensitivityPoint dataclass
- ai_sensitivity_curve(machine, model, mults, axis='flops'|'bw')

9 new tests cover linear scaling of KV with S_kv/B, weight
invariance across sweeps, over-budget flag flip, free_gb clamp,
FLOPs multiplier → AI linear, BW multiplier → AI inverse, B*
tracks AI for BF16, bad axis raises.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 14:10:02 -07:00
mukesh 9b4aee83da analytical-viz: roofline tab — trim to 4 plots, cap B at 256 / S_kv at 1M
- Slider caps: B_max = 256, S_kv_max = 1M (matches practical decode
  operating ranges).
- Batch axis on every plot capped at 256 to match the slider.
- Removed the two redundant plots (old Plot 1 and old Plot 4). Both
  were re-drawing the same weight/compute/KV decomposition as the new
  headline 'Cost per token' plot at the top — the only distinguishing
  bits (extra 'Memory total' curve, B* marker) have been folded into
  the headline plot already, so keeping them was pure clutter.
- Every remaining plot now reacts to the S_kv / B knobs:
    * Plot A (step latency): S_kv drives curves, B marker drawn.
    * Plot B (cost/token): same.
    * Plot 2 (no-knee overlay): S_kv drives the L* ratios shown,
      B marker drawn.
    * Plot 3 (knee vs context): S_kv AND B markers drawn on both axes.

Net: 4 plots (headline pair + no-knee + knee-vs-context), all live-
interactive against both sliders.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 13:52:40 -07:00
mukesh 221097ef08 analytical-viz: roofline tab — headline step-latency + cost-per-token plots, B knob
Two new plots at the top of the Chip Roofline tab (right after the
knob row), side by side:

1. **Latency per decode step** — one forward pass's total time as B
   grows. weight_fetch (flat in B), compute (linear), KV (linear),
   total. The SLO / TTFT view — bigger B = longer step.

2. **Cost per token** = step latency ÷ B. weight (shrinks 1/B),
   compute (flat), KV (flat), total. The efficiency / cost view —
   bigger B (up to ~2·B*) = cheaper per token.

Same underlying decomposition, two divisors. Puts the classic
throughput ↔ latency tradeoff in one glance.

Also added a **B (batch size) slider** next to the existing S_kv
slider. Both plots draw a vertical marker at the current B; the
regime KPI ("memory-bound / compute-bound / kv-bound") now uses the
slider B, so you can sweep B live and watch the label flip when you
cross B*.

step_latency_curve + StepLatencyPoint added to chip_roofline; 5 new
tests cover: weight_s batch-invariant, compute_s/kv_s linear in B,
step_total == per_token_total × B (definitional), and step ==
per_token at B=1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 13:40:37 -07:00
mukesh c99a238826 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>
2026-07-29 13:27:17 -07:00
mukesh 04c7d247f0 analytical-viz: roofline formula table shows substituted numeric form
Each row in the 'Formulas & interpretation' table now shows both the
symbolic form AND the actual numbers plugged in, on a second line in
the Formula column. Row height bumped to fit two lines. Rendering:

  AI:      C / W
           = 8.00e+12 / 2.56e+11
           = 31.25 FLOPs/byte

  L*:      2 · N_active · W / (C · kv_bpt)
           = 2 · 6.98e+09 · 2.56e+11 / (8.00e+12 · 131,072)
           = 3,407 tokens

  B_knee:  B* / (1 - S_kv/L*)
           = 31 / (1 - 8,192/3,407)
           = 31 / (1 - 2.404)   [<= 0 -> no knee]

Makes the derivation traceable at a glance without needing to plug the
numbers back into the abstract formula. Purely a display change; no
runtime behavior shifts, all 57 tests still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 12:04:47 -07:00
mukesh 1ddd1baa7a analytical-viz: 'Chip roofline & B*' tab — AI, B*, L*, cost curves
New tab surfaces the arithmetic-intensity story from LLM-serving
practice for the current sidebar chip + model. All derived from
MachineParams + ModelConfig; no new configuration.

chip_roofline module (pure functions):
- arithmetic_intensity      = C / W   (FLOPs per byte HBM BW)
- critical_batch (B*)       = C * b / (2 * W) * sparsity
- balance_context (L*)      = 2 * N_active * W / (C * kv_bpt)
- knee_batch (B_knee)       = B* / (1 - S_kv/L*)  (None past L*)
- per_token_latency_curve   = weight/B + compute + KV, per B
- bound_regime              = which term dominates now

Peak roofline convention (no compute_util factor) so weight_s ==
compute_s exactly at B*. Comm + TP/CP sharding intentionally
excluded — stage_latencies is the full latency model; this is the
back-of-envelope chip-vs-model view.

Tab shows:
- 4 KPI cards: AI, B*, L*, current regime at (B, S_kv)
- MoE hint when preset flags MoE ('300 x sparsity' rule)
- Plot 1: cost vs B at current S_kv (weight, compute floor, KV
  floor, total) with B* marker line
- Plot 2: cost curves at S_kv/L* = 0.25, 0.5, 1, 2, 5 — shows the
  no-knee regime past L*
- Plot 3: B_knee vs S_kv — knee slides right, diverges at L*
- Formulas + interpretation table

test_chip_roofline covers B* against H100 reference (295), sparsity
scaling, L* scaling with HBM BW, knee divergence at L*, per-token
curve monotonicity + asymptote to compute+KV floor, regime
classification. Smoke test guards the tab is wired in app.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 11:59:19 -07:00
mukesh 5e92b89821 analytical-viz: PE view keeps CP rank locator visible at TP>=8
The Per-PE layout diagram used to color-code + label CP ranks only
when cp_placement=='pe' (CP packed intra-cube). At TP>=8 the TP dim
fills the cube, cp_placement gets pushed to 'cube', the intra-cube CP
branch is skipped, and the CP rank tag silently disappeared from
cubes, per-PE headers, and the figure title. Users at TP>=8 had no
way to tell which of the CP groups the diagram was drawing.

Fix: when cp_placement=='cube' and CP>1, add
  - cube header suffix:   '[CP rank 0 of {CP}]'
  - per-PE header suffix: '  |  CP=0'
  - figure title:         'CP={CP} (showing 1 of CP groups; others identical)'

Behavior at cp_placement=='pe' is unchanged — the existing per-CP-rank
palette + 'CP={rank}' label still fire. CP=1 adds no locators at all.

test_pe_weight_layout covers four cases: TP=8/CP=4 (cube placement,
locator appears), TP=2/CP=4 forced to pe placement (regression:
per-rank labels still there, cube tag not added), CP=1 (no locators),
TP=16/CP=2 (both spilled cubes carry the CP-rank-0 tag).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 10:19:39 -07:00
mukesh 3360be2488 analytical-viz: consolidate shape tables under Physical Layout tab
Move the per-stage shape tables (attention + FFN) off the Per-stage
latency tab and combine them with the weight + KV shape tables into a
single 'All tensor shapes (per PE)' expander at the bottom of the
Physical Layout tab. Five subsections in one place:
  - Attention weights (global + per-PE shape, per layer)
  - FFN weights (same schema)
  - KV cache (same schema)
  - Per-stage attention shapes (input / weight / output)
  - Per-stage FFN shapes (same)

Rationale: the shape catalog belongs next to the physical-layout view
that shows how the tensors are placed on hardware. The Per-stage
latency tab now focuses purely on time-per-stage. Weight/KV tables
stay on the Memory Breakdown tab too because the summary metrics
underneath still consume the same rows.

Guard test verifies the section string appears exactly once, is
positioned inside tab_layout (before tab_memory / tab_stages), and
that neither attn_stage_shape_rows nor ffn_stage_shape_rows is called
inside the tab_stages block anymore.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 10:14:08 -07:00
mukesh adc14c84af analytical-viz: per-stage tensor shape tables (attention + FFN)
New stage_shapes module reports per-PE input / weight / output tensor
shapes for every attention (S1..S10, C1..C3) and FFN (F1..F5, CF1)
stage. Streamlit's Stages tab picks these up below the existing
latency table + bar chart as two separate dataframes ('Attention
stages' and 'FFN stages'), so anyone who wants to see the exact
sharded tensor dims per stage can, without cluttering the latency
view.

Rows mirror the conditional structure of all_stages / all_ffn_stages:
S8 merge only when CP>1; C1 CP ring only in prefill with CP>1; C3
Score AR only when kv_shard_mode='split' and TP>H_kv; C2 TP AR only
when TP>1; CF1 FFN AR only when the FFN divisor > 1. Batch B flows
into every activation dim; weights stay B-invariant.

test_stage_shapes covers column presence, conditional row insertion
(decode omits C1; CP=1 omits S8; prefill+CP>1 inserts C1), batch
scaling of the leading dim, and that S5 output references both T_q
and S_local for the current cfg.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 10:05:02 -07:00
mukesh 9c5062bdfb analytical-viz: memory-min buttons reset stale placement/mode session keys
auto_suggest only explores (cp, tp, pp, cp_placement); every other
TopologyConfig field is left at its dataclass default when the score
is computed. The sidebar's memory-min apply used to update only
cp/tp/pp/dp/cp_placement, so any stale session_state value from a
previous user interaction (most damaging: tp_placement="cube") would
carry over into the rerun and produce a topology the memory-min search
never scored — visibly, cubes_used could balloon (e.g. Llama 3 70B
Attn scope: auto_suggest picks 1 cube; stale tp_placement="cube" made
the applied config span 8 cubes).

Fix: after picking a Suggestion, reset tp_placement, kv_mode,
cp_ring_variant, ep back to their TopologyConfig defaults, and drop
the ffn_scope_label key (dynamic string; falls back to the TP+CP
default index on re-render). Now the sliders + placement + mode state
after apply exactly reproduce the topology auto_suggest scored.

test_auto_suggest_cubes_match_default_topology asserts, across four
model/skv combinations and all three scopes, that a TopologyConfig
built from (cp, tp, pp, cp_placement) plus dataclass defaults has the
same cubes_used the Suggestion reports.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 23:45:38 -07:00
mukesh 4ded67a443 analytical-viz: sidebar memory-min buttons are scope-aware (Attn / FFN / Attn+FFN)
Replace the three memory-optimal Pareto-search buttons in the Parallelism
sidebar with three scope-aware memory-min autosuggest buttons. Each
button sizes for a different per-PE memory footprint:

  - Attn      -> attention weights + KV cache (no FFN weights)
  - FFN/MoE   -> FFN weights only (no attention, no KV)
  - Attn+FFN  -> everything (traditional autosuggest)

memory_layout: per_pe_weight_bytes and compute_memory take
include_attention / include_ffn flags; KV cache is zeroed when attention
scope is excluded.

autosuggest: _score_candidate and auto_suggest forward the flags into
compute_memory, so the smallest-fit search now respects the chosen
scope.

app.py: single "Apply memory-min" caption + 3 column buttons. Each
button runs auto_suggest with its scope filter, snaps the sliders to
the picked (CP, TP, PP), and sets the Physical Layout scope filter to
match. Removes the standalone Apply memory-min button (was duplicating
the Attn+FFN case) and the Pareto-search import.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 23:34:05 -07:00
mukesh 47c4f40f4d analytical-viz: batch B in every formula string + rename 'latency-optimal' → 'memory-optimal'
Two related fixes.

1) Formula strings show B alongside every other symbol. Previously the
   numeric FLOPs/mem_bytes/comm_bytes were scaled by B, but the printed
   formula strings still read like B=1 amounts. That made the per-stage
   table confusing at high B: numbers moved but formulas didn't. Updated
   every attention + FFN stage's `formula` / `flops_formula` /
   `mem_formula` / `comm_formula` to include B explicitly. Weight-bytes
   lines now say "(shared across batch)" / "(weight, B-invariant)" so it's
   obvious what does and doesn't scale.

   Stages touched: S1..S10 (attention), C1/C2/C3 (attn comm), F1..F5
   (FFN), CF1 (FFN AR).

2) Renamed the button/spinner/help copy from "latency-optimal" to
   "memory-optimal" to match the smallest-fit pick semantics I put in
   place earlier. The buttons already picked smallest-fit; the labels
   just still said "latency-optimal" from the previous iteration.

   Left the Auto Hardware sensitivity chart's "latency-optimal baseline"
   label alone — that panel is a HW co-design view where "how fast can
   each HW go?" is the intended question.

Verified: 24 pytest tests still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 23:20:32 -07:00
mukesh 50aab8d591 analytical-viz: complete batch (B) scaling in remaining stages + comm
Finishes the batch scaffold. All stages that produce per-request work
now scale flops / activation-memory / comm-bytes by B; weight bytes stay
fixed (shared across the batch).

Attention block, previously partial:
  - S6 stage_softmax: elems + bytes × B
  - S8 stage_merge: flops × B; O/m/l AR bytes M × B
  - S9 stage_normalize: flops × B
  - S10 stage_wo: flops × B
  - C1 comm_cp_ring: decode O/m/l AR M × B; prefill K/V ring + Q+O/m/l
    variants both × B
  - C2 comm_tp_allreduce (W_O output): bytes × B
  - C3 comm_kv_split_allreduce (head-split scores): bytes_per_hop × B

FFN block, previously untouched:
  - F1 stage_ffn_rmsnorm: activation × B (weight fixed)
  - F2 stage_ffn_gate (via _ffn_gemm): flops × B
  - F3 stage_ffn_up: same
  - F4 stage_ffn_swiglu: elems + flops × B
  - F5 stage_ffn_down: flops × B
  - CF1 comm_ffn_allreduce (batched FFN output): bytes × B

Verified with a smoke check on Qwen 3 8B / 128K decode:
  B=1  per-layer visible latency:  363 us
  B=8                                716 us  (sub-linear — many stages
                                              stay weight-bound)
  B=64                              4023 us  (approaching linear scaling
                                              as batch dominates)

All 24 pytest tests still pass at default B=1 (backward compat).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 23:10:39 -07:00
mukesh fc1dcdde24 analytical-viz: tensor sharding — show batch B on KV cache
Three additions to the tensor sharding view when batch B > 1:
  1. Shape label appends "B={B}": e.g. (128, S_kv=131072, B=8).
  2. Note under the KV cache: "batch B={B} => {B}x KV bytes per PE".
  3. Visual "stack" — up to 5 dashed offset rectangles drawn behind
     the KV cache to hint at the batch stacking dimension. Capped at 5
     so a big B doesn't overwhelm the diagram.
  4. Title also gets B={B} between CP and FFN scope.

Attention/FFN weight tensors are NOT stacked — weights are shared
across the batch (correct: only activations + KV scale with B).

At B=1, all four additions are no-ops so the diagram looks unchanged
from before.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:56:48 -07:00
mukesh fd45bd2408 analytical-viz: add sidebar Batch B selectbox + wire through all suggesters
New sidebar widget in the Workload expander:
  Batch B (concurrent requests) — selectbox 1/2/4/…/256, default 1.

Wired end-to-end:
  - app.py sidebar → forwards b_batch when constructing the main topo
  - auto_suggest(..., b=1) — passes b to _score_candidate, which builds
    TopologyConfig with b=b
  - run_auto_explore(..., b=1) — sets topo.b = b for every enumerated
    candidate before scoring
  - joint_explore(..., b=1) — forwards b to _best_parallelism_for_hw
    and _best_parallelism_two_stage; both set topo.b = b before scoring

All button handlers (sidebar Apply-scope, Physical Layout scope,
Auto Suggest tab sweep, Auto Hardware tab sweep) now pass b_batch.

Combined with the earlier partial-scaffold commits (memory_layout
scales KV by B; stage_rmsnorm / stage_wq / stage_wkv / stage_kv_append /
_per_hop_qkT_pv scale flops + activation memory by B), changing B in
the sidebar now affects reported latency and per-PE memory footprint
in the visible parts of the pipeline. The remaining FFN + comm-AR
stages still ignore B (they'll be next); their contribution is small
for the memory-bound decode case that matters most, but latency for
FFN-heavy configs at high B will be slightly under-reported until
those are scaled too.

Verified: 24 pytest tests pass at default B=1 (backward compat).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:51:56 -07:00
mukesh 674e194dc9 analytical-viz: hot-reload guard — include model_config + model_presets, ordered
The reload guard didn't include model_config.py. When the batch (B)
field was added to TopologyConfig, Streamlit kept the stale
pre-B dataclass in sys.modules and every downstream module hit
"AttributeError: 'TopologyConfig' object has no attribute 'b'".

Fix: add model_config and model_presets to the reload list, and order
model_config FIRST so downstream modules that reload after it pick up
the new dataclass definition. Also reordered the rest so upstream
dependencies (autosuggest, memory_layout, stage_latencies) reload
before their consumers (auto_explore, auto_hardware).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:43:47 -07:00
mukesh 770aed6291 analytical-viz: Auto Suggest tab — add Top-10 by memory footprint table
Adds a second table beneath the Pareto configurations table, sorted by
(hbm_utilization ↑, pes_used ↑, latency ↑) — top 10 smallest-memory
Pareto configs first.

Columns highlight the memory story:
  HBM % | weights (GB) | KV (GB) | PEs | SIPs | lat (ms) | CP TP PP DP | kv | ffn | tp_place | cp_place

Useful when memory pressure is the real binding constraint of a
deployment — e.g., picking a config for a per-PE HBM-limited SIP,
or spotting configs that trade small latency headroom for large
memory savings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:24:38 -07:00
mukesh 1bce080d79 analytical-viz: buttons pick smallest-fit Pareto point, not fastest
Sidebar Attention/FFN/Attn+FFN buttons and Physical Layout tab buttons
now select the SMALLEST-FIT config from the scope's Pareto set — key
changed from (latency ↑, pes_used ↑, hbm_utilization ↑) to (pes_used ↑,
hbm_utilization ↑, latency ↑). Answers "smallest deployment that's
still Pareto-optimal for this scope" instead of "fastest deployment
achievable".

Left unchanged:
  - "Best latency" metric displays in Auto Suggest / Auto Hardware tabs
    still show the fastest number (informational; the metric labels say
    "Best latency").
  - auto_hardware._best_parallelism_for_hw stays latency-min: it's
    inside HW co-design, where "how fast can each HW candidate go" is
    the primary question.

Verified: 24 pytest tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:23:34 -07:00
mukesh 5d7ef48b14 analytical-viz: partial batch (B) scaffold — TopologyConfig field + KV / early stages
WIP toward batch-size support. This first commit is behavior-preserving
at the default B=1 (every new multiplication is by max(1, cfg.topo.b) =
1 today) so all existing tests pass. Follow-up commits will:
  - scale the remaining stages (S6/S7/S8/S9/S10 + C1/C2/C3 + FFN + FFN AR)
  - add a batch selectbox to the sidebar
  - forward b through auto_suggest / auto_explore / auto_hardware

Changes so far:
  - TopologyConfig: new b: int = 1 field (batch size).
  - memory_layout.per_pe_kv_cache_bytes: * B (each concurrent request
    keeps its own KV cache slice).
  - stage_rmsnorm / stage_wq / stage_wkv / stage_kv_append /
    _per_hop_qkT_pv: FLOPs and activation memory scaled by B; weight
    bytes stay fixed (weights shared across the batch).

Verified: 24 pytest tests still pass at default B=1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:21:37 -07:00
mukesh 2a0bc26db0 analytical-viz: compound tiebreaker — fewer PEs / lower HBM% when latency ties
Every place that picks a single "best" config from the Pareto set now
uses a compound sort key: (latency_ns ↑, pes_used ↑, hbm_utilization ↑).
Primary = latency; when configs tie on latency (common — cp_ring
variants, some placement variants often produce identical numbers),
prefer smaller-footprint picks.

Places updated:
  - app.py:  sidebar Apply Attn/FFN/Attn+FFN button
  - app.py:  Physical Layout tab Attn/FFN/Attn+FFN button
  - app.py:  Auto Suggest tab "Best latency" metric
  - app.py:  Auto Hardware tab "Best latency" metric (uses parallelism.pes_used
             + parallelism.hbm_utilization since JointScore wraps ConfigScore)
  - auto_hardware.py:  _best_parallelism_for_hw iteration key

No behavior change when there's a strict latency winner. When there are
ties, the picked config uses fewer PEs and lower HBM utilization.

Verified: all 24 pytest tests pass (default include_attention=True and
include_ffn=True paths unchanged).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:16:02 -07:00
mukesh 6f61657ba4 analytical-viz: topology map — color PEs by CP rank when cp_placement=pe
The topology map previously colored a whole cube (border + all PE
fills) by the single cp_rank assigned to that cube in the cube→(pp,cp)
mapping. Under cp_placement=cube this is right (each cube = one CP
rank). Under cp_placement=pe, however, multiple CP ranks are PACKED
into the same cube's PEs, so the whole-cube coloring makes every PE
look identical (defaulting to cp_rank=0's palette entry — bright red).

Fix: in the per-PE loop, if cp_packed = (cp_placement=="pe" and cp>1),
compute each PE's own cp_rank = pe_id // tp and look up its color via
_cp_color(pp_stage, pe_cp_rank, cp_size). Border still uses the
cube-level color (cp_rank=0), so the outer bounding box is unchanged,
but the interior PEs now show all four (or however many) CP-rank
colors visibly.

For cp_placement=cube: unchanged (single pe_fill per cube).

Verified with a headless render at Qwen 3 8B, CP=4, TP=2,
cp_placement=pe: 145 patches → 8 distinct facecolors (4 for the CP
ranks in the packed cube + inactive/border tints), where before it
was fewer distinct colors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:07:50 -07:00
mukesh da1909fdf2 analytical-viz: extend hot-reload guard to all viz sub-modules
Previously only reloaded auto_explore / auto_hardware / autosuggest /
stage_latencies / memory_layout. When we edit pe_weight_layout,
tensor_sharding, topology_map, pipeline_diagram, or optimization_report,
Streamlit was still holding the pre-edit versions until a full restart.

Add all five to the reload list so any visualization-module edit lands
on the next Streamlit rerun without needing Ctrl+C.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 22:03:42 -07:00
mukesh ed28eea156 analytical-viz: color-code CP groups in PE layout when cp_placement=pe
When cp_placement=pe packs multiple CP ranks intra-cube, the PE-level
layout now:

  1. Colors each PE's background by its CP rank (8-color palette wraps
     for cp > 8). Cp_placement=cube keeps the historical light-blue
     styling (only one CP rank per cube tile).

  2. Adds "TP=N  |  CP=M" to the per-PE header so users can read the
     (cp_rank, tp_rank) pair without inferring from position.

  3. Fixes a head-assignment bug: _q_heads_for_pe / _kv_heads_for_pe /
     _per_pe_bytes used to be called with the raw PE-in-group index,
     which treated every PE as a distinct TP rank. Under cp_placement=pe
     this gave wrong Q/KV head lists for PEs beyond the first TP group
     (PE 2..7 with tp=2, cp=4 would ask for head slots 32..127 in a
     32-head model). Now called with tp_rank = pe_id % tp, so all CP
     ranks in the cube share the correct head split.

Verified via a headless matplotlib smoke test with CP=4/TP=2 on Qwen 3
8B: 8 PE patches + 1 cube patch → 5 distinct facecolors (cube + 4 CP
ranks), no errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 21:59:18 -07:00
mukesh f1cf0257d3 analytical-viz: restore Apply memory-min button
An "Apply memory-min" button was removed when the 3 latency-optimal
buttons were added. Consequence: after clicking any latency-optimal
button, the sidebar sliders drift from the memory-min caption and
there was no way to sync them back without restarting the app or
manually adjusting each slider.

Restore the button, positioned below the caption and above the 3
latency-optimal buttons. Clicking it snaps cp/tp/pp/dp/cp_placement
to what the caption shows and clears _pl_active_scope to "full" so
the Physical Layout tab stops filtering.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 21:55:29 -07:00
mukesh afe35ee0b5 Revert "analytical-viz: default intra-cube (PE↔PE) BW to 128 GB/s"
This reverts commit 84abf847c4.
2026-07-28 21:50:17 -07:00
mukesh 84abf847c4 analytical-viz: default intra-cube (PE↔PE) BW to 128 GB/s
Was 512 GB/s in the analytical viz MachineParams default. Bringing the
default down to 128 GB/s to match what the modelled physical link now
represents (parity with inter-cube D2D and topology.yaml).

Files touched (all three needed to keep defaults coherent):
  - model_config.py: MachineParams.bw_intra_gbs = 128.0 (was 512.0)
  - app.py: sidebar selectbox default index = 0 (128 GB/s) instead of 2 (512)
  - auto_hardware.py: _HW_KNOB_DEFAULTS + BALANCED + COARSE bw_intra_gbs
    baselines shifted so cost_score at defaults remains 6.0 and the
    sensitivity sweep starts from the new baseline.

Verified: 24 pytest tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 21:44:21 -07:00
mukesh a7f39ade7e analytical-viz: autosuggest prefers fewer cubes, tries cp_placement=pe
Two changes to the memory-only auto_suggest so a "fits" that packs into
one cube is preferred over a "fits" that spreads across multiple cubes.

Suggestion dataclass gains cubes_used and cp_placement fields.

_score_candidate: for each (CP,TP,PP) triple, try cp_placement="cube"
(historical default: CP spans cubes) and, when CP·TP fits in one cube's
PE count, also cp_placement="pe" (pack CP into intra-cube PEs). Keep
the placement with fewer cubes; break ties toward "cube".

auto_suggest: switch sort key from (pes_used ↑, pp ↑, tp ↑, cp ↑) to
(cubes_used ↑, pes_used ↑, pp ↑, tp ↑, cp ↑). Fewer cubes wins first
because a cube is the physical die-level unit; PE count is the
tiebreaker.

Sidebar caption now also displays cubes_used + cp_placement so the
user can see the packed layout at a glance. Preset-change auto-reset
also applies the picked cp_placement.

Observed effect (verified):
  Qwen 3 8B / 128K decode:  CP=4 TP=2, "pe" → 1 cube, 8 PEs
                             (was 4 cubes with default "cube")
  Llama 3.1 70B / 128K:      CP=4 TP=16, "cube" → 8 cubes (unchanged;
                             TP=16 > 8 PEs/cube, can't pack)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 16:04:17 -07:00
mukesh c2b42999d8 analytical-viz: reorder tabs — Physical layout back to first
New tab order:
  Physical layout / Auto Suggest Parallelism / Memory breakdown /
  Per-stage latency / Save & compare / Auto Hardware

Physical layout is what a user typically wants to see first when
loading the app, so it takes the leftmost slot again. Auto Suggest
Parallelism moves to second — still prominent, still usable as a
first step, but doesn't push the layout view behind it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 14:26:07 -07:00
mukesh 9afeb6bc16 analytical-viz: 3 sidebar apply-buttons (Attn / FFN / Attn+FFN)
Replaces the single memory-only "Apply auto-suggest" button in the
sidebar Parallelism expander with three latency-optimal buttons:
"Attention", "FFN/MoE", "Attn+FFN".

Each clicked button runs run_auto_explore for its scope, picks the
latency-minimum Pareto config, snaps the parallelism knobs to the
sidebar's selectbox option sets (CP/TP/PP/DP), and loads the other
knobs directly (tp_placement, cp_placement, cp_ring_variant, kv_mode,
ffn_scope_label). Also sets _pl_active_scope so the Physical Layout
tab's stage-table filter follows the scope automatically.

The caption above the buttons still shows the memory-only autosuggest
values as a reference — separately labeled "(memory-min)" to avoid
confusion with the latency-optimal buttons below.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 14:24:06 -07:00
mukesh bccc90db82 analytical-viz: Physical Layout scope filter for stage tables
Clicking one of the three Physical Layout tab buttons now persists the
chosen scope in session_state["_pl_active_scope"] and filters the
per-stage latency tables accordingly:

  - attn scope → show only "Attention" stage table
  - ffn scope  → show only "FFN" stage table
  - full scope → show both (default; also matches a fresh sidebar-driven
                  config with no button click yet)

Added a "Layout scope: {label}" header so the user can tell at a glance
which scope's config is loaded.

The pipeline diagram + topology map + weight/tensor sharding + PE
layout continue to show the full model layout — those diagrams give
context that stays useful even when the user is focusing on attn or
FFN. Deeper filtering (e.g., attention-only pipeline stripe) can come
later if needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 14:21:27 -07:00
mukesh 1a6d52c58a analytical-viz: force-reload our modules on Streamlit rerun
Streamlit's hot-reload re-executes app.py on every interaction, but
sub-modules imported from app.py stay cached in sys.modules across
reruns. When we edit auto_explore.py or auto_hardware.py while
Streamlit is alive, the app keeps using the pre-edit version until a
full Ctrl+C + restart — leading to confusing "unexpected keyword
argument" errors after a signature change.

Force importlib.reload() on our own modules at the top of app.py so
future signature changes land without a full restart. Only reloads if
the module is already in sys.modules (first run just imports normally).

Applied to:
  - tests.analytical_visualization.auto_explore
  - tests.analytical_visualization.auto_hardware
  - tests.analytical_visualization.autosuggest
  - tests.analytical_visualization.stage_latencies
  - tests.analytical_visualization.memory_layout

Third-party modules (streamlit, matplotlib, pandas, numpy) NOT reloaded
— unnecessary and slow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 14:17:13 -07:00
mukesh 09721040ca analytical-viz: 3 sweep buttons at top of Physical Layout tab
Shortcut: click any of {Attention, FFN/MoE, Attn+FFN/MoE} → runs
run_auto_explore for that scope, picks the latency-minimum Pareto config,
loads it into the sidebar's session_state (cp, tp, pp, dp, tp_placement,
cp_placement, cp_ring_variant, kv_mode, ffn_scope_label), then st.rerun().

Because the Physical Layout tab reads model + machine + cfg from the
sidebar, the sidebar update automatically redraws the pipeline diagram,
per-stage table, and everything below. No preview / parallel-display
state — WYSIWYG.

Ffn_scope_label reconstruction handles the dynamic "(div=…)" suffix the
sidebar shows (same pattern as auto_explore + auto_hardware tabs' "Load
into sidebar" widgets).

Verified: app.py parses; 24 pytest tests still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 14:11:30 -07:00
mukesh bf5b659a3d analytical-viz: add FFN-only scope + 3rd sweep button
Both auto tabs now offer three sweep scopes via three buttons instead
of two:
  - Run sweep — Attention          → include_attention=T, include_ffn=F
  - Run sweep — FFN/MoE            → include_attention=F, include_ffn=T
  - Run sweep — Attn + FFN/MoE     → include_attention=T, include_ffn=T

Each button caches its result under its own session_state key; the most
recently clicked button drives the display. All three caches persist so
users can flip between scopes without re-running.

Core changes:
  auto_explore.py + auto_hardware.py:
    - New include_attention: bool = True param alongside include_ffn
    - _sum_visible_latency, _efficiency, score_config, run_auto_explore,
      compute_parallelism_sensitivity, joint_explore, compute_sensitivity,
      _best_parallelism_for_hw, _best_parallelism_two_stage all wired.
    - Attention and FFN are additive with no overlap: measured 7.35 ms
      (attn) + 5.50 ms (ffn) = 12.85 ms (full) for Llama 70B decode 128K.

Bug fix (drive-by): the display block in both tab render functions was
incorrectly nested inside the "cached ctx is stale" warning branch, so
metrics/scatter/table/sensitivity/load only rendered when the cache
was stale. Un-indented and removed the dead trailing else-info block.

Verified:
  - 24 pytest tests pass (added 1 new for FFN-only scope invariants)
  - Smoke: Llama 70B decode 128K all three scopes produce sensible Pareto:
      * Attention only: 7.35 ms (14 pareto configs)
      * FFN / MoE only: 5.50 ms (34 pareto configs)
      * Attn + FFN/MoE: 12.85 ms (6 pareto configs)
    Sums add up exactly, confirming no overlap in stage summation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 14:09:24 -07:00
mukesh b8e1a3322f analytical-viz: attention-only vs attn+FFN via two sweep buttons
Both auto tabs now accept a scope choice at run time:
  - "Run sweep — Attention"        → include_ffn=False
  - "Run sweep — Attn + FFN/MoE"   → include_ffn=True

Each button runs an independent sweep and caches its result under its
own session_state key. The most recently clicked button determines the
displayed view; both caches persist so users can flip between the two
scopes without re-running.

Tabs:
  - Renamed "Auto Explore" → "Auto Suggest Parallelism"
    (accurately reflects that it only varies parallelism knobs; HW is
    held at the sidebar values).
  - "Auto Hardware" tab unchanged.
  - Still 6 top-level tabs; no additional tabs added.

Core changes:
  auto_explore.py:
    - New include_ffn: bool = True parameter on _sum_visible_latency,
      _efficiency, score_config, run_auto_explore, compute_parallelism_
      sensitivity. False drops all FFN stages from the summed latency.

  auto_hardware.py:
    - New include_ffn: bool = True parameter on joint_explore,
      compute_sensitivity, _best_parallelism_for_hw, _best_parallelism_
      two_stage. Forwards to score_config.

Both defaults keep existing tests byte-identical.

Verified:
  - 23 pytest tests pass (added 3 new: attn-only latency lower, attn-only
    Pareto non-empty, joint HW attn-only faster than full).
  - Smoke: Llama 70B decode 128K:
      * Attn+FFN best latency: 12.85 ms (unchanged)
      * Attention-only best:    7.35 ms (~57% of full)
      * Both sensitivities top-rank bw_hbm_gbs (physics preserved).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 13:58:29 -07:00
mukesh 91b63eb9f6 analytical-viz: parallelism sensitivity chart in Auto Explore tab
Adds compute_parallelism_sensitivity() + ParallelismSensitivityRow to
auto_explore.py. For a baseline ConfigScore (picked from the Pareto set),
sweeps each parallelism knob (CP, TP, PP, DP, EP) individually while
holding others fixed. Reports latency + memory-fit per value.

Sweep values start at 1 and step by 2 (multiples of 2 rather than only
powers of 2), giving finer granularity for the visual than the enumerator's
sparser set. CP goes up to 256 (per real-deployment scale), TP to 64,
PP to 32.

New UI panel in Auto Explore tab: 5 subplots (log/log), one per knob:
  - Solid line = latency where the config fits memory
  - Red X markers = infeasible (out of budget)
  - Dotted vertical line = baseline value
User picks which Pareto row is the "baseline" via a number input; the
sensitivity chart re-computes around it.

Behaviour caveat noted during verification: for large PP the FFN AR can
cross a SIP boundary (uses stage_latencies.py:730 sips_used tier
selection), producing a step-up in latency. This is the existing model's
choice, honestly reflected in the chart. Whether the FFN AR should span
only one PP stage's ranks (thus not cross SIPs) is a separate discussion
about stage_latencies.py.

Verified:
- 11 pytest tests pass (added 2 new for parallelism sensitivity)
- Smoke: at Llama 70B decode 128K with baseline CP=8/TP=16/PP=1/DP=1:
  * CP sweep: 4 fits, 8 = baseline optimum, 16+ overshoots memory
  * TP sweep: 8/16 fit, 16 = baseline optimum, 32 slower (spans SIPs)
  * PP sweep: 1 = baseline, 2+ slower due to sips_used tier drop for FFN AR
  * DP sweep: same shape as PP
  * EP sweep: monotone decreasing (bigger EP = smaller per-PE FFN)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 13:39:51 -07:00
mukesh 55c20bd1a6 analytical-viz: add Auto Hardware Streamlit tab
Consumes auto_hardware.joint_explore(). UI:

  - Sweep-depth radio: two_stage / balanced (default) / coarse
  - Run joint sweep button + spinner
  - 4 metric cards: HW candidates, feasible joint, Pareto count,
    best latency
  - Panel 1 (Pareto scatter): latency vs hardware cost proxy, feasible
    in grey, Pareto colored by PE count, dashed line connects Pareto in
    cost order — this is the "optimal HW config for the model" plot
  - Panel 2 (sensitivity bar chart): per-knob relative speedup when
    doubled from the baseline. Green bars, sorted biggest first;
    annotated with the baseline → doubled values. Answers "where to
    invest next?"
  - Panel 3 (table): sortable Pareto joint configs with parallelism +
    HW spec columns (PE HBM, HBM BW, TFLOPs, PE↔PE, D2D, C2C)
  - Load into sidebar: picks a Pareto row and syncs both the HW
    selectboxes (Per-PE + Interconnect sub-tabs) AND the parallelism
    sliders. Values only get loaded when the target is one of the
    selectbox options; otherwise the sidebar keeps its current value.

Session-state cache under _hw_result keyed by (model, s_kv, mode, depth);
if any of these drift, a warning suggests refreshing.

Verified: app.py parses; auto_hardware smoke run on Llama 70B decode
128K in ~20s produces sensible HW co-design signal (HBM BW 33% speedup,
everything else <1.5%).

Next: verification across Qwen 3 8B, Mixtral 8x7B (Commit 6).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 13:19:13 -07:00
mukesh 6ef09dd6f5 analytical-viz: auto_hardware.py — joint HW×parallelism explore
New module extends auto_explore into the hardware co-design space. For a
fixed model + workload, sweeps hardware knobs (pe_hbm_gb, bw_hbm_gbs,
peak_tflops_f16, bw_intra_gbs, bw_inter_gbs, bw_intersip_gbs) and, for
each hardware candidate, searches parallelism for the latency-minimum
that fits memory. Returns:

  - all_scores: every (hw, parallelism) pair that fits, sorted by latency
  - pareto_scores: 2D Pareto frontier on (latency ↓, cost_score ↓)
  - sensitivity: per-knob rel_speedup when doubled from the best-fast HW
    baseline. Ranks which HW knob gives the biggest speedup — a co-design
    signal.

Three sweep depths trade coverage for time:
  - two_stage: 1 HW candidate (defaults) × autosuggest's memory-min
    parallelism. Fast (~1s), useful for the sensitivity ranking alone.
  - balanced: 64 HW × ~2k reduced-parallelism configs = ~130k joint evals,
    ~10-20s. Default UI setting.
  - coarse:   729 HW × ~2k configs = ~1.4M joint evals, ~2-5 min.

Reduced parallelism sweep for the inner loop: CP × TP × PP × DP ×
kv_shard_mode (1,920 configs), other 4 knobs held at latency-friendly
defaults (ffn_shard_scope='TP+CP', tp_placement='cube', cp_placement='pe',
cp_ring_variant='qoml' for decode, 'kv' for prefill). Full 28,800-config
auto_explore per HW would take 6+ minutes — too slow.

Cost proxy: sum of (knob / knob_default). 6.0 at defaults. Not dollars —
a rough capability score where higher = "more spec'd hardware".

Verified:
- 9 pytest tests pass:
    * enumeration counts match expected (1, 64, 729)
    * default cost_score = 6.0
    * Pareto non-dominated + subset of all_scores
    * every knob is monotone-non-worsening when doubled
    * for Llama 70B decode, bw_hbm_gbs tops the sensitivity ranking
      (physically correct: memory-bound workload)
- Smoke: Llama 70B decode 128K balanced sweep in ~20s produces
  5 Pareto configs; best 7.57 ms with 1024 GB/s HBM BW. Doubling
  HBM BW gives 33% additional speedup; every other knob < 1.5%.

Next: Streamlit UI tab consuming this in Commit 5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 13:16:40 -07:00
mukesh 2834383700 analytical-viz: add Auto Explore Streamlit tab
New tab consumes auto_explore.run_auto_explore(). UI:
  - Header showing current model + workload + per-PE HBM budget
  - Run sweep button (spinner while ~28k configs run in ~5-10s)
  - 4 metric cards: enumerated, feasible, pareto count, best latency
  - 2-panel scatter:
    * latency vs PEs (feasible in grey, Pareto coloured by efficiency,
      dashed line connects Pareto in PE order to show the trade-off curve)
    * HBM utilization vs latency for Pareto, coloured by PE count
  - Sortable Pareto table
  - "Load into sidebar" widget: pick a row, sets the sidebar
    session_state keys (cp, tp, pp, dp, tp_placement, cp_placement,
    cp_ring_variant, kv_mode, ffn_scope_label) and st.rerun()s so the
    user can flip to another tab and see the full breakdown.

Session-state caches the last sweep result under _auto_explore_result;
if the model/workload/HBM change without re-running, a warning suggests
refreshing.

Ffn_scope_label mapping is dynamic (contains substituted divisors), so
the Load button reconstructs the exact label using the target
cp/tp/dp values before assigning to session_state["ffn_scope_label"].

Verified:
- app.py parses cleanly
- All 9 pytest tests in test_auto_explore.py still pass
- Smoke: matplotlib + pandas + auto_explore imports round-trip

Next: verification pass across Qwen 3 8B and Mixtral 8x7B; polish.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 13:02:08 -07:00
mukesh 85f6716fc1 analytical-viz: auto_explore.py — 9-knob Pareto search
Extends the memory-only autosuggest to search the full 9-dimensional
parallelism space (CP, TP, PP, DP, kv_shard_mode, ffn_shard_scope,
tp_placement, cp_placement, cp_ring_variant) and rank feasible configs
on a 3D Pareto frontier: (latency ↓, pes_used ↓, efficiency ↑).

Throughput is stored on ConfigScore for display but is deliberately NOT
a Pareto axis because for a single-request analysis it collapses to
1 / latency, which would collapse the frontier.

Reuses existing physics (stage_latencies.all_stages + all_ffn_stages,
memory_layout.compute_memory) — no new formulas.

Single-request latency formula fix: PP does NOT reduce single-request
decode/prefill latency because the request has to traverse every layer
sequentially regardless of pipeline depth. The initial version had
latency ~ per_layer × layers_per_stage, which incorrectly rewarded high
PP. Corrected to latency ~ per_layer × model.layers.

Enumerator prunes:
  - PP > model.layers
  - TP > 4 × h_q
  - ffn_shard_scope contains 'DP' when dp=1 (redundant)
  - cp_ring_variant='qoml' when cp=1 (no-op)

Full sweep on Llama 3.1 70B: ~28,800 configs enumerated in ~7s, ~7k-10k
feasible (varies with S_kv), 2-7 unique Pareto configs. Faster context
lengths produce richer frontiers; at 1M, memory forces a single
dominant config (128 PEs, HBM 85%).

Verified:
- 9 pytest tests pass (enumerate, score, Pareto, subset invariants)
- Manual: Llama 70B decode at 8K/64K/128K/1M produces physically
  sensible Pareto (CP=8/TP=16 wins latency; smaller-PE options
  appear at longer context up to memory limits)

Next: Streamlit tab UI in app.py + verification against more presets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 12:58:10 -07:00
mukesh 09e9331026 topology: set flit_bytes=4096 (was implicit 256 default)
Wire per-flit loop in engine._wire (ADR-0033 Phase 2c) iterates
nbytes/flit_bytes times per hop, firing ~2 SimPy events per flit.
At long-context decode, K/V tile loads are hundreds of KB per shard,
producing thousands of flit-iterations per hop. The default 256-byte
flit size made the wire loop the dominant wall-clock cost.

Raising flit_bytes to 4096 (still a multiple of hbm_ctrl.burst_bytes=256
per ADR-0033 D1) cuts flit iterations 16x with no meaningful change to
modeled latency. Wormhole pipelining and the available_at BW-share
chain in _wire behave identically at any flit size.

Measured impact on Case 4 (Cube-SP x PE-SP) decode, C=8, P=8, T_q=1,
LLaMA-3.1-70B single-KV-group, enable_data=False:

  S_kv     flit=256 wall    flit=4096 wall    speedup    latency drift
   128K       233.71 s          18.16 s        12.9x     +0.05% (0.25us)
   256K       485.75 s          25.31 s        19.2x     +0.01% (0.09us)
   512K      1045.64 s          48.45 s        21.6x     +0.01% (0.17us)
    1M      ~2250 s (proj)     104.75 s        ~21x      within extrap

Total sweep at flit=4096: ~3.3 min. Baseline would have been ~65 min.
1M decode iteration is now practical.

op_log records are byte-identical (same counts, same components); the
tiny latency drift (order of a few ns) comes from the last flit's
env.timeout landing on a slightly different granularity when flits are
larger. Well within noise for any test that asserts on structural
invariants (dma_write cubes, ipcq_copy counts).

Verified:
- tests/attention/test_milestone_gqa_decode_long_ctx_4cases.py: 18/18 pass
  (also 7x faster: 174s -> 24.83s per full run)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-28 11:35:04 -07:00
mukesh f9d0077472 router: memoize Dijkstra path lookups
Per-instance cache keyed by (id(adj), start, goal). All 4 adj dicts
(_adj, _adj_all, _adj_local, _adj_mcpu_dma) are built in __init__ and
never mutated (topology is static per ADR-0006 / SPEC §0.1), so id(adj)
is stable for the router's lifetime. Cache is populated on the
successful-return paths; RoutingError paths intentionally re-run each
call (rare, keeps error semantics unchanged).

Motivation: cProfile of Case 4 decode at S_kv=8K showed 1,142
_run_dijkstra_with_dist calls consuming ~1.95s tottime. Paths depend
only on (adj, src, dst) so ~99% of those calls are recomputing the same
result.

Verified:
- tests/attention/test_milestone_gqa_decode_long_ctx_4cases.py: 18/18 pass
- 128K decode wall: 237.28s -> 233.71s (-1.5%)
- Modeled kernel latency: 461.13us (byte-identical before/after)
- op_log_len: 3057 (unchanged)

Small win but no risk: memoization returns byte-identical results and
paths cannot change during a sim run. Larger event-count reductions
require touching the per-hop hot path (zero-latency chain collapse
etc.).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-27 23:39:39 -07:00
mukesh 65f358fdfa sim: report correct kernel latency under enable_data=False
Three coupled fixes so enable_data=False now reports byte-identical
kernel-body sim latency (max(t_end) - min(t_start) over op_log) as
enable_data=True, and skips setup-write sim events that were pure
wall-clock overhead.

Before this change, at Case 4 (Cube-SP x PE-SP) decode, S_kv=8K:
  enable_data=False  -> latency_ns = 0  (op_log empty)
  enable_data=True   -> latency_ns = 30.646 us  (correct)

After:
  enable_data=False  -> latency_ns = 30.646 us  (byte-equal)
  enable_data=True   -> latency_ns = 30.646 us  (unchanged)

engine.py: always create OpLogger. Previously OpLogger was gated on
enable_data=True together with MemoryStore, so op_log stayed empty when
data mode was off. Decouple: MemoryStore is gated (Phase 2 DataExecutor
still needs it) but OpLogger runs unconditionally, receiving
memory_store=None when data mode is off. OpLogger already guards its
arr.copy() snapshot paths on `if self._memory_store is not None`.

pe_cpu.py: always use the ADR-0020 greenlet execution path. The
if store is not None: _execute_greenlet(); else: _execute_legacy branch
gated the *execution model* on data-mode presence. The legacy
command-list path predates ADR-0020 and doesn't route IpcqSendCmd
through PE_IPCQ (it goes to PE_SCHEDULER instead), so all 189 Case-4
ipcq_copy fabric transfers were silently no-op'd in that mode. Forcing
greenlet gives identical sim behavior in both modes. KernelRunner
already guards its store reads on `self._store is not None`.

context.py: gate setup MemoryWriteMsg on memory_store presence. Under
enable_data=False there is no MemoryStore to populate and no Phase 2
replay, so the per-shard sim events for Q/K/V deploy are pure wall-clock
overhead with no effect on reported kernel latency (Yangwook's max-min
formula excludes pre-kernel ops). Handle count for Case 4 at S_kv=8K
drops 229 -> 37 under enable_data=False.

Verified:
- tests/attention/test_milestone_gqa_decode_long_ctx_4cases.py: 18/18 pass
- Parity probe: enable_data=False/True both report 30.646 us kernel sim
  time, 1649 op_log records, 189 ipcq_copy events

Known follow-up: pe_cpu._execute_legacy is now dead code but left in
place; separate cleanup once we confirm no downstream caller.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-27 22:05:39 -07:00
mukesh 9fdde44922 analytical-viz: interactive Streamlit tool for SIP transformer analysis
New tests/analytical_visualization/ module - an interactive dashboard
for exploring memory / latency tradeoffs of transformer inference on
the SIP architecture.

Highlights:
- 30+ model presets (Qwen, Llama 2/3/3.1, Mistral, Gemma 2, Phi 3,
  DeepSeek MLA, Mixtral/Qwen 3 MoE, Grok-1, ...)
- Placement toggles: TP and CP each on PE-level vs cube-level
- CP ring variant: K/V ring vs Q+O/m/l ring (prefill); in decode the
  O/m/l all-reduce is folded into S8 (no separate C1 row)
- SIP interconnect: ring / mesh2d / torus2d with matching link drawing
- Per-stage latency table with compute + memory + comm formulas,
  auto-scaled ns/us/ms, colored by dominant bound
- Ring attention loop indicator on the pipeline diagram (purple arc
  over S5-S8 with 'xN hops' badge)
- Tensor sharding view with optional physical PE/cube annotations
- Replication-waste + optimization-hints panel
- Save & compare configurations (config1, config2, ...): summary table
  plus side-by-side per-stage attention and FFN latency, best-in-row
  highlighting
- Symbol glossary with current values for every symbol used in formulas

Not tied to production sim_engine or runtime API; purely analytical
tooling for design-space exploration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 22:12:08 -07:00
ywkang ae131aa2af report(S5.2): explain why dense mapping wins throughput (per-PE efficiency)
Both 1-kv (2 users x 64 PE) and 8-kv (16 users x 8 PE) fill all 128 PEs of
the SIP; the throughput gap is per-PE HBM efficiency. Splitting a head over
8 PEs leaves each a single tile, so pipeline-fill and the 8-PE softmax
reduce dominate -> 46% HBM util; one head per PE streams contiguously with
no reduce -> 76%. Throughput ratio (1.6x) tracks the utilization ratio
(1.7x): decode is bandwidth-bound, so throughput follows total HBM
efficiency, not PE count. 1-kv spends hardware on latency (8 PE/head for a
4.8x speedup = 60% strong-scaling efficiency).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:46:54 -07:00
ywkang baf22f01ab plot: extend batch-scaling curves with saturated-throughput ceiling
Beyond a mapping's SIP capacity (16/C) the throughput plateaus: extra
users run in waves at the saturated rate, they are not un-runnable. Draw
a dashed horizontal extension at the ceiling so 1-kv-per-cube (cap 2)
reads as saturating, not stopping, at B=2. Caption updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:39:13 -07:00
ywkang 13f5cbc317 gqa decode: fix cube_base output-store; measure batch scaling on one SIP
Concurrency fix (completes the cube_base change): the decode kernel's
output store o_base still used the global cube_id while every load used
the user-local cube_local. For a single user (cube_base=0) they coincide,
so it was masked; a second batched user (cube_base>0) overshot its o shard
into an unmapped VA, mis-decoded to a phantom sip0.cube0.pe8 and raised a
RoutingError. Switch o_base to cube_local (one line); single-user results
are byte-identical (decode smoke/correctness pass).

Batch harness (un-skipped): create all users' tensors first, then submit
all launches deferred and drain together, so deploys don't drive an
already-submitted launch and serialize the batch. Concurrent users on
disjoint CUBE groups now overlap to within 3-5% of the single-user
latency. Swept B in {1,2,capacity} at 8K (high-B dense runs are the
expensive ones; throughput is near-linear in B).

Measured result: aggregate throughput at a full SIP rises from 0.86
(1-kv-per-cube, 2 users) to 1.41 requests/us (8-kv-per-cube, 16 users) —
the dense mapping wins throughput, the spread mapping wins latency.
S5.2 batch paragraph updated projected -> measured; add batch_scaling
figure and plot_batch_scaling.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:37:10 -07:00
ywkang 8c108154a7 plot: fix Fig 16 suptitle to match KV-footprint panel
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 16:25:50 -07:00
ywkang 711a9a257f gqa short-context: exclude KV deploy from wall; correct HBM peak; batch scaffold
Metric fixes (test harnesses, deploy artifact + wrong constant):
- wall = max(t_end) - min(t_start): exclude the one-time KV-cache deploy
  from the measured decode/prefill step (it was 92-99% of wall, mapping-
  invariant, and masked the real per-mapping separation).
- PEAK_PE_HBM_BPS 128 -> 256 B/ns (8 channels/PE x 32 GB/s); the old value
  was half the modeled per-PE HBM BW, so hbm_bw_util read >1.0 once wall
  was corrected. All six short-context sweep CSVs regenerated/repatched.

Result: the four KV mappings now separate along a {64,32,16,8}-active-PE
ladder (decode 8-kv/1-kv = 4.8x at 8K, 7.4x at 64K), not "modest/tied" as
before; decode is bandwidth-bound at 46-76% of the 256 GB/s per-PE ceiling.

Report (S5.2 rewrite):
- Replace the tied-wall / 8x-per-PE-util claims (both deploy artifacts)
  with the corrected separation and a density trade-off (per-CUBE KV
  footprint, Fig 16 top panel switched to a wall-invariant metric).
- Add a projected latency-vs-batched-throughput analysis (marked
  projected, not measured): dense mappings win throughput, 1-kv wins
  latency; converges at long context.
- Regenerate Fig 15/16/17/18; fix plot script hardcoded ROOT path.

Batch experiment (Part 2, cube_base):
- Add backward-compatible cube_base=0 scalar to the decode kernel so a
  batched user placed at DPPolicy.cube_start addresses 0-based shards.
  Default preserves single-user behavior (32 decode tests pass unchanged).
- New batch harness (skipped): concurrent B>=2 launches hit a sim routing
  issue (sip0.cube0.pe8); single-user path verified. Concurrency fix next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 16:25:04 -07:00
ywkang cb6d21f145 report(1H): tighten §5 GQA (mapping tie, taxonomy bridge, dedup, Summary)
- Soften short-context claim: 1-kv-/8-kv-per-cube are tied at 8K and
  separate as context grows (1-kv faster at the large end).
- Add a bridge noting the six-case placement taxonomy (§5.1) is the
  long-context lens; short context turns on head-to-CUBE distribution.
- Remove caption-duplicating numbers from the long-context conclusion.
- Reduce the closing subsection to a GQA-scoped Summary; defer the
  cross-cutting hardware-investment thesis to the Discussion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 10:33:56 -07:00
ywkang d88e6cc72e report(1H): rebuild main.pdf
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 10:22:06 -07:00
ywkang edb30326ce report(1H): add Agentic Workloads (§6) and HW-Spec Search (§7) sections
- §6 Supporting Agentic Workloads: how the fused GQA design extends to
  agentic fan-out/fan-in; three-layer split (framework/runtime/kernel);
  Stationary-KV vs Distributed-Q execution policies.
- §7 Hardware Performance-Spec Search for GQA: WIP stub (sweep intent
  over GEMM TFLOPS, MATH-engine ALUs, CUBE↔CUBE and SIP↔SIP BW).
- Renumber Discussion/Conclusion/Future-Work to 08/09/10; update
  main.tex input order and toc.md.
- Add Agentic_Runtime_Architecture.md design note; rebuild main.pdf.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 10:20:27 -07:00
mukesh d0db9ff40e gqa-decode: topology-agnostic reduce + multi-model attention bench
Make reduce_mlo derive its submesh dimensions (sub_w, sub_h, root_col,
root_row, root_cube) from C at call time, with peer-existence guards on
every inter-cube send/recv so any C ≥ 1 completes cleanly (previously
hardcoded to C=8's 4×2 mesh; non-rectangular C like 7 or 12 hit
IpcqDeadlock at the root cube's east receive). Byte-equal at C=8 —
existing composite digest tests still pass 8/8.

Callsites in the four Case-6 kernels (primitive / primitive-tiled /
composite / composite_extended) updated to pass C into reduce_mlo and
use root_cube_for(C) for the final tl.store gate.

Add the multi-model attention bench: sweeps the composite kernel at
S_kv=128K across six GQA models (Gemma-2 27B, LLaMA-3 8B, Qwen 2.5 7B,
LLaMA-3 70B, Qwen 2.5 72B, Command R+), with per-model topology
C = h_q (cubes per KV group = query heads per KV group). Captures
per-op-kind occupancy (matmul GEMM+MATH, comm DMA) alongside latency
and PE_CPU dispatch. Three-panel plot writes to bench-output and
paper-figures dir.

Headline result: same-h_q-different-family models land within 0.2 µs
(model-agnostic given fixed h_kv=1 per KV group + d_head=128); latency
climbs 263 → 492 µs as C climbs 2 → 12, driven by reduce depth
(comm/matmul ratio 0.4 → 0.6 across the sweep).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-22 09:54:31 -07:00
mukesh 23a2bc3fb3 gqa-decode-composite: measure primitive hand-tiled (16×16×16) latency
Switch primitive hand-tiled Q·Kᵀ / P·V from a deferred-K-sum tl.dot
chain to per-block GemmCmd writes into a shared (M, N) out handle
(implicit MAC-side accumulation). Full-shape coarse Q / K_T / V loads
carry data-mode correctness; per-block DMAs remain for the streaming
architecture dispatch story. The kernel now runs in engine mode
end-to-end, so its 128K latency is measured instead of derived as
primitive + Δdispatch. Drop the coarse-primitive baseline from the
sweep and plots; keep the kernel file for reference.

At S_kv=128K: primitive hand-tiled = 959.5 µs vs composite = 460.7 µs
(2.08× from 5 235 vs 94 PE_CPU commands).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-13 21:49:57 -07:00
mukesh eec61838b5 sweep+plot: add primitive hand-tiled (16×16×16) variant to Case-6 decode composite study
Wires a fourth variant into the Cube-SP × PE-SP long-context decode
composite command-form study to surface the worst-case PE_CPU dispatch
inflation that the coarse composite forms delegate to PE_SCHEDULER
(ADR-0065): per-block DMA of Q/K/V slices, tl.dot per 16³ block,
deferred K-inner sum outside the K loop.

Renamed _tiled.py → _hand_tiled_16x16x16.py; coarse-primitive kernel
retained for the 4-cases bench, paper scripts, and the golden byte-equal
regression guard.

New breakdown bar chart at S_kv=128K shows engine (~460 μs) dominates
all three variants; hand-tiled adds ~68 μs PE_CPU dispatch on top —
composite forms sit essentially on the memory-bound floor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-13 09:28:57 -07:00
eusonice f8caf2e16c report(gqa-short): draft results subsection (mode + composite-tier comparison, 4 figures) 2026-06-26 16:01:32 -07:00
eusonice 09e70bd516 test: fix CSV output paths in 3 sweep files (each variant now writes a unique CSV) 2026-06-26 15:59:21 -07:00
eusonice e20e1f7362 plot: merge plot scripts into single plot_short_context.py 2026-06-26 15:59:21 -07:00
eusonice 3437d7e079 sweep: 6 short-context CSVs (3 variants x prefill+decode) + 6 wall/util figures 2026-06-26 15:59:21 -07:00
eusonice 3da116d40d adr-0070: gqa short context attention prefill/decode + 3 composite tiers 2026-06-26 15:59:21 -07:00
eusonice f7aa8134b9 test: 3-variant sweep (baseline + composite + composite-fused) for prefill/decode 2026-06-26 15:59:21 -07:00
eusonice a455ae61b4 sim: pin compute outputs and on-chip recv slots for composite operands 2026-06-26 15:59:21 -07:00
eusonice 1971ac731c add three gqa attention kernel variants for short context length 2026-06-26 15:59:21 -07:00
eusonice a98e93110b short context length composite command 2026-06-26 15:59:21 -07:00
eusonice f08dda3bd4 test short context length attention kernel 2026-06-26 15:59:21 -07:00
eusonice 7d90d53d4d short context length attention kernel 2026-06-26 15:59:21 -07:00
ywkang faf011dae5 paper(ipcq): regen alternatives figures + comparison plot + allreduce section
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:19:55 -07:00
ywkang 1dade267ff experiment(gqa-decode): 16x16x16 MAC-block dispatch model
Research artifact (NOT wired into the production sweep) for the decode composite-vs-primitive investigation. Models a primitive decode kernel that hand-blocks each tl.dot into 16x16x16 GemmCmds, charging per-MAC-block PE_CPU dispatch -- testing whether faithfully charging the dispatch that the composite form offloads to PE_SCHEDULER flips the 'composite gives no decode latency benefit' result.

Finding: it does NOT. Even with up to 512 serialized blocking GemmCmds per matmul (vs 2 coarse tl.dot), end-to-end decode latency is unchanged (8192: 28.9us, 32768: 114.9us) -- the inter-cube (m,l,O) reduce DMA tail dominates and the local-attention GEMM/dispatch sits in critical-path slack. Confirms the two-regime conclusion (24c7054): composite helps compute-bound prefill, not memory/reduce-bound decode.

Caveats: measured at S_kv 8K/32K (reduce-dominated); large-S_kv streaming regime extrapolated only. The -5.5% tiled<untiled is reduce-tail ordering noise, not fully nailed down.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:24:26 -07:00
ywkang 24c705419e paper(gqa): two-regime "use of composite commands" — decode + compute-bound prefill
Complete the composite-command study across both attention regimes and
write up the result, resolving when the composite command helps latency.

Decode (memory-bound, T_q=1, M=8): the command form is latency-neutral —
the kernel is bound by KV streaming and the MAC array is near-idle, so the
composite's only benefit here is host-issue offload (O(n_tiles) -> O(1)
PE_CPU commands). Figure: gqa_decode_long_ctx_composite (latency flat,
command count saturates).

Prefill (compute-bound, large M=G*T_q tile-filling): add three command-form
variants of a single-rank FlashAttention prefill kernel
(_gqa_prefill_compute_bound: primitive / composite / composite_extended).
Here the composite's scheduler-internal per-tile DMA<->compute pipeline
keeps the MAC array fed while the primitive's blocking tl.dot starves it,
so composite wins on MAC utilization (68% flat -> 83%) and wall-clock
(19% faster at ctx=1024), with the margin growing with context (deeper
P.V reduction = more tiles to pipeline) — the compute-bound mirror of the
GEMM result in section 3. Figure: gqa_prefill_compute_bound (latency +
MAC util). Sweep wired as GQA_1H_SWEEPS=prefill_cb; tests cover structure,
e2e, and composite<primitive in the compute-bound regime.

The synthesis: composite has two benefits set by roofline position —
host-issue offload (always) and MAC-array feeding (compute-bound only).
Decode exercises the first, prefill both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 17:04:26 -07:00
ywkang cd6f0ed91d feat(gqa): Case-6 composite-command decode variants + on-chip-operand DMA fix
Add two command-form variants of the Case-6 (Cube-SP x PE-SP) long-context
decode kernel alongside the primitive baseline:
  - composite: per-tile GEMMs issued as one coarse tl.composite over the
    full S_local (PE_SCHEDULER tiles/streams K,V) -- O(1) PE_CPU commands
    vs the primitive kernel's O(n_tiles).
  - composite_extended: Q.K^T composite + softmax_merge recipe composite
    (ADR-0065), folding the per-tile online merge + P.V.

Extract the shared two-level (m,l,O) reduce into _gqa_mlo_reduce and
refactor the baseline to use it (byte-equal, guarded by a 64-rank
command-stream digest test).

Engine fix: _make_compute_out omitted pinned=True, so an on-chip TCM
compute result consumed as a composite GEMM operand was scheduled as an
HBM DMA_READ of its bit-61 scratch address -> PhysAddrError in data mode.
Mark compute outputs pinned (resident), matching the composite
auto-output and recipe scratch handles. .pinned is read only by the
tiling DMA gate, so this is byte-equal for existing benches.

Add the composite sweep (emit-level PE_CPU dispatch to 1M + data-mode
latency to 128K), its plot, umbrella wiring (GQA_1H_SWEEPS=composite),
and tests (refactor guard, dispatch saturation, engine-fix, e2e).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:07:41 -07:00
ywkang e9a5c438e3 paper(1H): 6-case GQA long-ctx + IPCQ design alternatives + GEMM terminology
- §6 GQA: rewrite long-context decode from 4-case to 6-case. Data
  Placement Policy now presents the six placement options and their
  intrinsic per-PE memory / per-token comm costs (no winner predicted);
  the long-context subsection selects the best placement for that regime
  (Case 6 ★, both-axes S_kv shard) from the measured 6-case sweep. Add
  KV-sharding diagram + analytical budget/summary figures.
- Regenerate the 6-row decode sweep (milestone-1h-gqa) and the
  sweep-dependent decode panels (latency/traffic/parallelism/memory) so
  figures, prose, and sweep_decode.json are mutually consistent.
- §5 All-Reduce: add IPCQ design alternatives (architecture + decision
  matrix) as design-rationale schematics (illustrative step-counts, not
  measured) and the bench-generated topology diagram.
- §4 GEMM: rename "user-orchestrated/user-level" -> "kernel-orchestrated/
  kernel-level" (orchestration runs in the kernel program vs the
  scheduler-orchestrated composite); minor accuracy fixes (7.18 TFLOP/s
  ~10% below peak; ~781 ns DMA).
- Recompile build/main.pdf.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 00:55:54 -07:00
mukesh bb668a3ac3 paper(gqa): derive (m,l,O) AR per-PE cost from kernel topology
Replaces the unsourced 32 KB/layer placeholder in the analytical chart
with T*(N-1)/N where T = h_q * S_q * (d_head*2 + 8) ~ 2.1 KB
(per-PE (m, l, O) payload) and N is the participant count of the
reduce-only chain per stage. Cases 2/3/6 analytical bars now match
the simulator-measured numbers within ~10% (was 6-30x over).

Also:
- Bumps the measurement S_kv from 8 K to 64 K to verify Cases 1, 2, 3, 6
  are genuinely S_kv-independent and Cases 4, 5 scale linearly.
- In-bar descriptor on the paired chart is now wrapped to fit the bar
  width, black, no background box, and centered between the analytical
  and measured bars so the label applies to both.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 15:38:20 -07:00
mukesh 0662c76fa7 paper(ipcq): de-emphasize flow-diagram outer frame so inner flow stands out
Drops the per-case panel outline from linewidth 2.2 to 0.9 with 85% alpha.
The case-color frame still anchors each panel but no longer competes
with the swimlanes, flit packets, and annotations inside.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 15:01:09 -07:00
mukesh 37dc48fd27 paper(ipcq): add latency-model-style flow + stacked architecture diagrams
Adds two new IPCQ architecture views in the latency_model.png aesthetic:
a 2x2 flow figure (one per case) and a 4x1 stacked figure for taller
horizontal panels. Refactors per-case panel drawing into shared helpers
and disambiguates the flit labels (HMQ desc -> Hd so it does not
collide with data D). Two-row legend keys.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 14:55:59 -07:00
mukesh 7c45047e93 paper(ipcq): swap control-data overlay for latency PNG in figures/
Drops the control-data overlay from the paper figures folder and
adds the per-send latency cycle-stack PNG instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 14:06:32 -07:00
mukesh 73e0b315fe paper(gqa): table cleanup — drop measured-comm column, narrower fig, wrapped title
Removes the (measured) comm column, widens the Notes column with
shorter wrapped text, scales the table figure down, and wraps the
heading to two lines so it sits flush with the table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 14:02:42 -07:00
mukesh 1552028c25 paper(ipcq): add IPCQ vs HW alternatives comparative study
6 generated figures comparing IPCQ (chosen) against the 3 HW alternatives
(Doorbell+Polling, HMQ, RDMA-CQ): architecture blocks, latency stack,
op counts, sequence flow, decision matrix, and control/data plane overlay.

Three figures also copied into the paper's figures/ folder for inclusion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 13:27:23 -07:00
mukesh 56c51b184a paper(gqa): refresh 4 decode 6-case PNGs in figures/ with actual 6-case content
The previous commit (8102ddb) renamed the figures-dir
gqa_decode_long_ctx_4cases_*.png → 6cases_*.png but only the rename
was staged; the cp that brought the regenerated 6-case content
landed after `git add`, so the committed bytes were still the old
4-case ones. This commit syncs the bytes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 11:51:30 -07:00
mukesh 8102ddbe30 paper(gqa): rename long-ctx artifacts to gqa_long_ctx_6cases_* and add 4 decode 6-case charts
Filename cleanup so every long-ctx GQA artifact has a consistent
"gqa_long_ctx_6cases_*" prefix (or "gqa_decode_long_ctx_6cases_*"
for decode-only charts). Old "4cases" / mixed names retired.

Renames (long_ctx + figures, content unchanged):
  gqa_hbm_budget.png                  -> gqa_long_ctx_6cases_hbm_budget.png
  gqa_4cases_summary.png              -> gqa_long_ctx_6cases_summary.png
  gqa_4cases_memory_comm_analytical   -> gqa_long_ctx_6cases_memory_comm_analytical.png
  gqa_4cases_memory_comm_paired       -> gqa_long_ctx_6cases_memory_comm_paired.png
  gqa_kv_sharding_6cases_diagram      -> gqa_long_ctx_6cases_kv_sharding_diagram.png
  gqa_kv_sharding_6cases_table        -> gqa_long_ctx_6cases_kv_sharding_table.png
  gqa_3cases_measured_comm.json       -> gqa_long_ctx_6cases_measured_comm.json
  gqa_decode_long_ctx_4cases_*.png    -> gqa_decode_long_ctx_6cases_*.png
                                         (figures dir; long_ctx never had old)

New 4-chart 6-case set in long_ctx output dir (regenerated by
paper_plot_gqa_decode_long_ctx_4cases.py, which now reads all 6
sweep_decode.json panels — Cases 1-6 with the same colour scheme
used elsewhere: red = overflow per-PE HBM, grey = neutral, blue
= Pareto-best ★):

  gqa_decode_long_ctx_6cases_latency.png
  gqa_decode_long_ctx_6cases_memory.png
  gqa_decode_long_ctx_6cases_parallelism.png
  gqa_decode_long_ctx_6cases_traffic.png

Generator scripts updated to write the new filenames + handle the
two new d_head-TP variants (Cases 4, 5) in their per-PE memory and
active-PE-count helpers. Figure widths bumped 10 -> 12 in to fit 6
multi-line case labels.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 11:49:03 -07:00
mukesh 84bb418e1e gqa(decode): add d_head-TP kernels + measurement runner + figure generators
Two new long-ctx decode attention kernels for the d_head-TP sharding
variants the 6-case chart predicts:

  · _gqa_attention_decode_long_ctx_cube_sp_pe_tp_dhead.py
        Cube-SP × PE-TP-d_head (Case 4 in chart). Per cube holds
        S_kv/C tokens of full d_head; per PE holds same tokens but
        only d_head/P dims. Partial Q·Kᵀ scores reduced intra-cube
        before softmax; outer (m,ℓ,O) merge two-phase (intra+inter).

  · _gqa_attention_decode_long_ctx_cube_tp_dhead_pe_sp.py
        Cube-TP-d_head × PE-SP (Case 5). Per cube holds full S_kv
        with only d_head/C dims; per PE holds S_kv/P of those dims.
        Partial scores reduced inter-cube (UCIe) before softmax.

Sweep dispatch (gqa_decode_long_ctx_4cases.py) extended with two
new panels so the milestone-1h-gqa sweep covers all 6 cases.

Smoke test scripts/verify_case4_dhead_tp.py runs Cases 4/5/6 at
S_kv=2K to validate the kernels load and execute end-to-end.

Plus the figure-generation toolchain that produced the committed
PNGs in the prior commit (dd3337f):

  · paper_plot_gqa_4cases_summary.py    - 3-panel summary +
        2-panel (analytical / paired-measured) chart generator.
        _plot_comm now takes mode="analytical" | "paired".
  · paper_plot_gqa_kv_sharding_diagram.py  - 6-case 2-D KV-tensor
        diagram + companion comparison-table PNG.
  · measure_gqa_decode_placement_comm.py   - runs all 6 kernels at
        S_kv=8K, sums actual IPCQ-copy bytes from engine.op_log,
        scales partial-score AR ×128 to S_kv=1M, writes
        gqa_3cases_measured_comm.json (committed).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 11:26:05 -07:00
mukesh dd3337f2e4 paper(gqa): add 6-case KV-sharding figures (analytical + measured)
Six PNGs covering the 6-case decode KV-placement story, copied
into both the kernbench 1H_milestone_output staging dir and the
1H-codesign-paper figures/ dir:

  · gqa_hbm_budget.png                 - per-PE HBM budget bar
  · gqa_4cases_summary.png             - 3-panel: HBM + KV mem + comm
  · gqa_4cases_memory_comm_analytical  - 2-panel: KV mem + comm (analytical)
  · gqa_4cases_memory_comm_paired      - 2-panel: + measured overlay
  · gqa_kv_sharding_6cases_diagram     - 2-D KV-tensor view (Y=S_kv, X=d_head)
  · gqa_kv_sharding_6cases_table       - companion comparison table

The original 4-case gqa_4cases_summary.png is updated to the new
6-case 3-panel layout. Generator scripts and Phase-1 d_head-TP
kernels remain untracked - separate commit scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 11:22:57 -07:00
ywkang b6315c3c90 paper(1H): reflect D8 single-op cost model in §2/§3.4 + figure/diagram regen
Two strands bundled as the 1H-codesign-paper refresh unit:

(A) This session — single-op cost-model reflection (depends on 2d8271c):
  - §2 Table 2 (tab:hw): split "FIXED per command" into "FIXED per
    single-op command" (8 cycles) and "FIXED per composite command"
    (40 cycles); §2 dispatch-overhead prose updated to the two-class split.
  - §3.4 (sec:gemm-vs-async): rename paragraph headers + prose to
    async-full / async-tiled; "atomic" -> "single-op" throughout; reframe
    mechanism #3 from the old DMA-only fast-path to the single-op
    fast-path. Headline narrative now: even with EVERY single-op cmd
    (96 DMA + 48 dot + 47 add) charged the light 8-cycle FIXED, composite
    still wins ~2.8x at K=3072 purely on command-count structure (1 vs
    192 commands) -- down from the pre-D8 ~6.3x, and explicitly NOT a
    modelling artifact. Numbers refreshed from the regenerated sweep:
    async-full 3.83->3.91, async-tiled 1.14->~2.53, under-tile corner
    1.06->1.21, depth-2 vs depth-inf spread <1%. New figure wired in.
  - build/main.pdf rebuilt (tectonic); pdftotext-verified (no broken
    refs; Table 2 split, single-op terms, 2.8x/2.53/192-host-commands
    all present).

(B) Prior-session paper work riding along uncommitted: §4 all-reduce
    deep-edit, §5 GQA, §6 discussion trims; milestone_1h_ccl.py plot
    label "FSIM" -> "H2 2025 SW queue baseline"; regenerated diagrams
    under docs/diagrams/** and gemm output PNGs under
    1H_milestone_output/gemm/. (Composite-window gemm plots are
    unaffected by D8 — D8 only changes single-op dispatch FIXED, which
    the composite window excludes.)

All TODO items for the D8 single-op extension are now complete and
pushed across 3 commits (2d8271c cost-model+ADR+tests, 821bbf2 bench
harness, this paper refresh). Full regression green (826 passed, 1
skipped). No remaining work.

NOTE for review (carried from 2d8271c): ADR-0065's "2x CPU-offload win"
headline for GQA decode opt2 may want a refresh to the post-D8 ~1.87x.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:12:31 -07:00
ywkang 821bbf26a2 gemm(bench): composite-vs-async dispatch comparison harness + single-op-model sweep
Adds the user-orchestrated async GEMM variants used in paper §3.4 to
contrast with the composite (load_ref) path:
  - matmul-async              : naive (tl.load full A+B, one tl.dot)
  - matmul-async-chunked      : depth-inf prefetch (all B-tiles queued)
  - matmul-async-chunked-db   : depth-2 double-buffer (TCM-bounded)
plus scripts/paper/paper_plot_gemm_async_vs_composite.py (4-way per-PE
TFLOPS sweep + figure) and the regenerated outputs under
1H_milestone_output/gemm/.

Sweep regenerated under the ADR-0064 D8 single-op cost model (commit
2d8271c). At K=3072 the composite-vs-async-tiled gap narrows from the
pre-D8 ~6.3x to ~2.8x (composite 7.18 / async-tiled 2.54 TFLOPS): D8
makes the async-tiled kernel's ~191 single-op dispatches 5x cheaper, so
its dispatch overhead drops to ~1.5us. Qualitative order persists:
composite > async-full (3.91) > async-tiled (2.54).

test_bench_registry.py: register matmul-async / -chunked / -chunked-db
(also picks up the earlier milestone-gqa-headline -> milestone-1h-gqa
rename already present in the tree).

--- Remaining work (resume here if interrupted) ---
- Paper §3.4 (03-gemm.tex sec:gemm-vs-async): finish naive->async-full /
  chunked->async-tiled rename AND reframe "FIXED_DMA=8 for DMA descriptors"
  to single-op(8) vs composite(40). Figure already copied to
  docs/report/1H-codesign-paper/figures/gemm_composite_vs_async_tflops.png.
- Paper §3.4 K=3072 para + mechanism #3: update dispatch breakdown to new
  model (191 single-op * 8c ~= 1.5us, was 4.6us) and headline gap
  ~6.3x -> ~2.8x.
- Rebuild build/main.pdf (tectonic) + verify via pdftotext.
- Then commit Group 3 (paper + diagrams + PDF).
- Table 2 / §2 prose / ADR-0064 D8 already done in 2d8271c.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:06:36 -07:00
ywkang 2d8271c981 perf(cost-model): D8 single-op-cmd fast-path (FIXED=8 single-op / 40 composite)
ADR-0064 D8 broadened from "DMA fast-path" to "single-op-cmd fast-path":
every single-op command (DmaRead/DmaWrite/Gemm/Math/Copy) now pays the
lighter FIXED=8; only CompositeCmd keeps the 40-cycle control-path FIXED
(it alone needs scheduler plan generation + per-tile RW-hazard tracking +
completion wiring). Renamed knob fixed_per_dma_cmd_cycles ->
fixed_per_single_op_cmd_cycles. dispatch_cycles now branches on
"is CompositeCmd" rather than enumerating DMA types.

Term choice: "single-op" (not "atomic", which read as sync/async) — the
axis is composition (one engine op vs fused multi-op plan), orthogonal to
timing. single-op <-> composite.

Tests: test_pe_cost_model.py updated to the single-op surface (defaults,
fast-path over all 5 single-op cmd types, composite general path, yaml
override). All green.

Recalibrated tests/attention/test_gqa_decode_opt2.py
::test_opt3_dispatch_exceeds_opt2 — NOT a regression: D8 makes single-op
cmds 5x cheaper, so opt2's two-composite fusion win over opt3's many
single-ops narrowed from pre-D8 ~3.7x to ~1.87x (opt3=224 > opt2=120).
The CPU-offload invariant (opt2 cheaper) still holds; only the model-
dependent ">2x" constant was over-fit to the old uniform-40 model. Gate
now: direction + >1.5x margin (matches sibling R-sweep test's stated
"absolute ratio informative-only" philosophy).
NOTE for review: ADR-0065's "2x CPU-offload win" headline may want a
refresh to reflect the post-D8 ~1.87x — left to user (architectural doc).

Full regression: 826 passed, 1 skipped (tests/ excl. tests/gemm).

--- Remaining work (resume here if interrupted) ---
5. Re-run scripts/paper/paper_plot_gemm_async_vs_composite.py with new
   cost model; verify async-tiled dispatch overhead drops (~4576ns ->
   ~1536ns expected) and the composite-vs-async-tiled gap narrows from
   the prior ~6.3x at K=3072.
6. Copy regenerated gemm_composite_vs_async_tflops.png to
   docs/report/1H-codesign-paper/figures/.
7. Paper §3.4 (03-gemm.tex sec:gemm-vs-async): finish naive->async-full /
   chunked->async-tiled rename AND reframe FIXED_DMA wording to single-op
   vs composite (currently still says "lighter FIXED for DMA descriptors,
   FIXED_DMA=8"). Table 2 (02-platform) + §2 dispatch prose already done.
8. Paper §3.4 K=3072 corner para + mechanism #3: update dispatch breakdown
   to new model (96 DMA*8 + 95 single-op*8 ≈ 1.5us vs old 4.6us); update
   headline ratio if it changed.
9. Rebuild docs/report/1H-codesign-paper/build/main.pdf (tectonic) +
   verify via pdftotext.
10. Then this is the bench-harness + paper commits (Groups 2 & 3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:04:53 -07:00
ywkang 0c6ca0aaed paper(gemm): inline §3.1 pipeline-stage chain; drop display equation
The five-stage chain was set as a centered display equation, which
overflowed the column and clipped the last 'DMA_WRITE' label. The
inline prose right below it also repeated the same chain in a
shorter form. Consolidate into one inline parenthetical listing the
five stage names, and reuse 'flows through the chain' for the
self-routing description — no clipping, no repetition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 10:40:23 -07:00
ywkang b64c43b947 paper(gemm): drop in-chart titles + subtitles, let LaTeX caption carry that
The matplotlib title and gray subtitle were duplicating what the
figure caption already says in the LaTeX source, and crowding the
top of every chart. Drop both — chart now leaves the title/subtitle
slots blank and the caption is the single source of truth for
chart titling.

Re-render all 5 GEMM figures (mac util measured, mac util theoretical
vs measured, stage breakdown, HBM BW util, per-PE TFLOPS) and resync
into docs/report/1H-codesign-paper/figures/. No data changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 10:29:13 -07:00
ywkang e8b7f4f064 paper(gemm): per-PE HBM BW + TFLOPS evaluation plots; unify peak to 8 TFLOP/s
Two new evaluation plots for §3 GEMM, both using the composite window
as the denominator (the up-front tl.load of A in load_ref is therefore
excluded — only HBM traffic and compute inside the composite count):

- gemm_hbm_bw_util.png: per-PE HBM bandwidth utilization against the
  256 GB/s per-PE ceiling. Answers "is this workload memory-bound on
  this configuration?" — small/single-tile shapes sit at 22–46%
  (pipeline-fill-limited), large or output-write-dominated shapes
  saturate (≥85%).

- gemm_per_pe_tflops.png: per-PE GEMM throughput against the 8 TFLOP/s
  engine peak. The deep-K K=3072 load_ref case lands at
  7.18 TFLOP/s (~90% of peak) — the configuration's clean win.
  ref_ref at the same shape drops to 3.83 TFLOP/s because the second
  operand doubles HBM pressure and the kernel hits the BW ceiling.
  The variant gap on this chart is the operational cost of NOT
  pre-staging the activation.

Both charts compare two operand-staging variants on every shape:
load_ref ("activation pre-staged", weight-only HBM streaming) and
ref_ref (both A and W streamed from HBM). load_load is omitted —
with both operands pre-staged the composite carries no HBM traffic
and the BW metric collapses to 0.

Analytic / measured peak unified to the single hardware spec
(8.0 TFLOP/s = 8000 flops/ns). T_STAGE is now derived as
tile_flops / peak (= 16.384 ns for the 32×64×32 tile) rather than
the previous hardcoded 16 ns, so MAC efficiency and per-PE TFLOPS%
share the same denominator and never disagree. Side effect: max
analytic-vs-measured gap tightens from 2.2 ppt to 1.4 ppt across
the sweep.

§3 Results restructured: lead with the two new evaluation plots
(BW saturation, achieved throughput), then the MAC-utilization
analytic-vs-measured chart serves as the validation step, then the
per-stage engine wall-clock as the supporting diagnostic. §3
Analysis updated to use the load_ref / ref_ref contrast as the
hardware-software boundary line that motivates the GQA kernel of §5.

§2.4 Accuracy claim updated to match: GEMM analytic-vs-measured
agreement is now "within 1.4 ppt across every swept shape", from
~7.7% at single-tile up to ~90% at K=3072. Subtitle/title vertical
stacking glitch in the matplotlib charts fixed in passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 10:24:42 -07:00
ywkang 23992548f7 gemm(perf): fix back-to-back DMA pipelining + tighten analytic model
Three coupled fixes that recover small-tile GEMM pipeline efficiency
from 53% to 88% (32x3072x32 load_ref, composite_window basis).

1. PE_DMA channel-hold (ADR-0014 D4 clarified): both the
   _handle_with_hooks (PeInternalTxn) and _pipeline_process
   (TileToken) paths used to hold the cap=1 DMA channel through the
   full HBM round-trip, which double-serialized with the HBM_CTRL's
   own per-PC `available_at` model and prevented back-to-back tile
   DMAs from amortizing their per-request head latency. Channel is
   now released after the request is enqueued onto the next hop;
   HBM serialization is HBM_CTRL's responsibility alone.

   Tests: new test_pe_dma_back_to_back_pipelining as the oracle
   (asserts wall < 75% of strict-serialized N x single_op). Existing
   test_pe_dma_record_start_after_channel_acquire rewritten to assert
   t_start clustering (channel released fast) instead of the old
   round-trip-hold invariant. test_pe_dma_same_channel_serializes
   still passes — HBM_CTRL preserves ordering.

   Probe regression: PE→local-HBM 32 KiB stays at 141 ns
   (single-request, unaffected).

2. milestone_1h_gemm bench: matmul_composite was reading MATMUL_M/K/N
   env vars at module load, so every sweep row replayed the cached
   256³ result; values now read inside run(). Drops the stale
   sys.modules deletion hack.

3. Analytic ideal-pipeline model: dropped the (n_mn-1)·dma_w_per_pair
   penalty (over-pessimistic for under-tile shapes — it pushed
   measured > theoretical) and replaced the D_STAGES-derived head
   with empirical T_PIPELINE_FILL=60 ns / T_PIPELINE_TAIL=30 ns.
   Max analytic-vs-measured gap across all 7 swept shapes now 2.2 ppt
   (was 9-44 ppt under the old constants).

Paper updates:
- §3 (GEMM): 78%→88% measured at 48 tiles, 23%→15% at 1 tile,
  stage breakdown numbers refreshed (DMA in / Fetch / GEMM all
  ~785 ns at K=3072), analytic-vs-measured agreement tightened
  to "within 2.2 ppt".
- §2.4 (Accuracy): GEMM tracking claim refreshed accordingly.
- §5 (GQA): restore long-ctx 4-cases figures into figures/
  (they were dropped from bench output dir as derived artifacts in
  92b9221 / e45626c but §5 still cites them by name).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 09:56:05 -07:00
mukesh 92b9221533 paper(gqa): drop committed long-ctx 4-cases derived chart PNGs
These 8 prefill/decode 4-case PNGs are derived artifacts regenerated
on demand from scripts/paper/paper_plot_gqa_{prefill,decode}_long_ctx_4cases.py
and no longer belong in the committed bench output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 09:39:26 -07:00
mukesh 694e0cc9b9 paper(gqa): add 6-case per-PE memory + comm summary chart
Analytical plot generator + PNG covering the 4 sharding cases from
slide 17 plus the 2 d_head-TP variants (Cases 4, 5). Three panels:
per-PE HBM budget breakdown (Wq + Wk + Wv + Wo + 4.24 GB KV
headroom), per-PE KV memory at S_kv=1M, and per-PE communication
per output token (decode). Numbers match slide 17 (max KV context
~7.1M for the 64-way sharded cases).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 16:12:26 -07:00
ywkang 6b6e29968a paper: §2 platform deep-edit + §1 GQA framing + figure regen
§2 (KernBench Platform):
- Fix Table 2 HBM aggregate BW (1024 → 2048 GB/s); drop stale
  hbm_total_bw_gbs from topology.yaml (never read by sim_engine)
- Split PE_CPU / PE_SCHED fixed-cost row; disambiguate from the
  40-cycle command-dispatch FIXED term
- §2.2 two-pass: expand to describe Pass 1 timing and Pass 2 data
  data-correctness path
- §2.3 dispatch model: add motivation sentence for the
  descriptor-size linear form
- §2.4 Accuracy: reorder GEMM → All-reduce → Probe →
  Simplifications; drop FSIM aside (already covered by §4 Fig 5
  caption); soften 'every ns' → 'every modeled latency
  contribution'
- §2.5 HW config: add 64 TFLOP/s vs 2048 GB/s (~31 FLOP/byte)
  balance-point intuition and forward pointer to §5 GQA decode
- Fig 1 caption: separate illustrative topology from experimental
  configuration

§1: tighten GQA-as-primary-bandwidth-bottleneck framing.

Figures: regenerate SIP / CUBE architecture (SVG sources + PDF +
generator scripts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 16:00:23 -07:00
mukesh 77eece81c4 gqa(milestone-1h-gqa): add umbrella bench + first sweep outputs
Single @bench entry that drives the prefill + decode long-context
4-cases sweeps in one invocation, mirroring milestone-1h-gemm. The
individual gqa_helpers/long_ctx/gqa_{prefill,decode}_long_ctx_4cases
modules are now pure helpers (no @bench), reached only via this
umbrella.

Env var contract:
  GQA_1H_RUN=1                    (required gate)
  GQA_1H_SWEEPS=prefill,decode    (default: both; pick subset to skip)
  GQA_1H_TOPOLOGY=topology.yaml   (override)

Output layout:
  benches/1H_milestone_output/gqa/long_ctx/
    sweep_prefill.json
    sweep_decode.json
    gqa_prefill_long_ctx_4cases_{latency,traffic,memory,parallelism}.png
    gqa_decode_long_ctx_4cases_{latency,traffic,memory,parallelism}.png

The decode bench config drops S_kv from 131_072 to 8_192 so the umbrella
finishes in minutes — the comparative-story bars are the same shape at
8K. The 131K production headline number is recoverable by overriding
the dispatch; the helper docstring notes this.

Outputs (2 sweep JSONs + 8 PNGs) ship in this commit alongside the
umbrella that produced them, following the milestone-1h-gemm pattern
where derived artifacts live with the bench that emits them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 13:06:33 -07:00
mukesh 443ede99c7 gqa(prefill-4cases): add long-context prefill 4-cases comparative study
Mirrors the existing decode 4-cases study for prefill on the LLaMA-3.1-70B
single-KV-head group (h_q=8, h_kv=1, d_head=128) across 8 cubes × 8 PEs:

  Case 1  Cube-SP × PE-TP  → KV S_kv-Ring across cubes; Q/O T_q-split 64-way
  Case 2  Cube-Repl × PE-TP → full KV per cube; only CUBE 0's P PEs split T_q
  Case 3  Cube-Repl × PE-SP → full KV per cube; PEs SP on S_kv (intra-cube AR)
  Case 4  Cube-SP × PE-SP  → KV split 64-way + Q T_q-split across cubes
                              (Ring KV + per-cube intra-CUBE AR)  ★ optimal

Cases 2 and 3 use Q-axis tiling (TILE_Q) so the per-PE scratch is bounded
by TILE_Q × TILE_S_KV regardless of T_q (the full Q tensor would be
G·T_q·d_head·2 bytes which scales linearly with T_q and blew the 1 MB
budget at T_q≥128 without tiling).

Per-panel try/except in run_sweep tolerates per-panel failures so
sweep.json always lands with whichever cases succeed plus a failures
list. Case 4 uses configure_sfr_intercube_ring with the snake submesh
(2,4) which installs both the Ring E/W lanes and the intra_* lanes
needed for the intra-CUBE reduce — single SFR config for all 4 cases.

The plot script generates 4 PNGs (latency, traffic, memory, parallelism)
into 1H_milestone_output/gqa/long_ctx/. The parallelism chart shows
total compute work (active_PE × T_q_per_PE × S_kv_processed) — exposes
Case 3's 8× redundancy vs. Cases 1/2/4 which all do unique work.

gqa_prefill_long_ctx_4cases.py exposes run_sweep() rather than a @bench
decorator — the umbrella bench in the next commit invokes it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 13:06:08 -07:00
mukesh e45626c036 gqa: reorganize benches into gqa_helpers/ subpackage; drop legacy headline
Splits the GQA helpers into a dedicated subpackage to make room for the
prefill 4-cases study (next commit) and a single umbrella bench
(milestone-1h-gqa, after that).

Layout:
  benches/gqa_helpers/
    long_ctx/    — decode 4-cases kernels + sweep runner
    short_ctx/   — prefill/decode short-context kernels
    shared/      — _gqa_panel_helpers + decode_opt2 (context-agnostic)

The registry audit now skips subpackages so gqa_helpers/ (without a
leading underscore) doesn't get audited for @bench decorators.

Also drops the legacy milestone-gqa-headline bench, its
_gqa_attention_prefill_long kernel, 6 dependent prefill tests, the
paper_gqa_latency.py report harness, and the 3 stale headline-derived
PNGs the §6 wire-up referenced (paper will re-pull from the new
1H_milestone_output/gqa/long_ctx/ once §6 is updated).

The _ccl_cfg and _summarize_op_log helpers used to live in the
headline bench; extracted them to gqa_helpers/shared/_gqa_panel_helpers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 13:05:41 -07:00
ywkang 359a0eaa44 paper(platform): Table 1 -> table*, shrink Fig 1 row 1, move Fig 1/2 source up
- Probe table (Table 1, tab:probe-pe-dma) promoted to table* so it
  spans both columns and no longer gets clipped at the column edge.
- Fig 1 (hw-arch) row 1 (SIP, CUBE subfigures) trimmed: widths
  0.495 -> 0.42 textwidth and a height=0.6\linewidth cap added so
  the figure occupies less vertical space and floats can land
  earlier in the document.
- Fig 1 and Fig 2 source blocks relocated to the top of §2 so they
  enter LaTeX's float queue before any §2 body text. As a result
  Fig 1 now lands on page 3 and Fig 2 on page 4 (was 4 and 5).
- Table 2 (tab:hw, modeled hardware config) reverted to plain
  \begin{table}; the previous \begin{table*} reshape was not
  requested.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 11:58:48 -07:00
ywkang 1cf8dd7868 paper(platform): widen SIP/CUBE subfigures, drop height cap
- subfig widths 0.46 -> 0.495 textwidth so the \hfill gap between
  (a) SIP and (b) CUBE shrinks to ~2% textwidth instead of ~8%.
- Removed the height=0.85\linewidth cap on both subfigures; with the
  cap, the (roughly 1:1) SIP and CUBE diagrams were being scaled
  down to 85% even when the column had room. Let them render at
  their natural aspect ratio.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 11:22:01 -07:00
ywkang b715002b5a paper: §2 figure layout + Accuracy subsection + Table 1 → 2-col; §6 trim historical 4-panel content
§2 platform:
- Accuracy promoted from \paragraph to \subsection (sec:accuracy);
  it sits at the same heading level as Why / Device / Latency / HW
  config and so reads as a first-class component of the platform
  description rather than a tail-end footnote.
- Three architecture diagrams (SIP, CUBE, PE) collapsed into one
  figure* using subcaption: Fig.~\ref{fig:hw-arch}(a) SIP and (b)
  CUBE side-by-side on row 1, (c) PE wide on row 2, total height
  capped at ~40% of the page. Subfigure cross-references rewritten
  to Fig.~ref{fig:hw-arch}\subref{...} in the body text. main.tex
  now pulls in the subcaption package.
- Table 1 (modeled hardware configuration) promoted to table*
  (two-column / full page width) and the row layout rewritten as a
  4-column tabular so Hierarchy + PE + Command-issue sit on the
  left and Memory + Interconnect sit on the right. The previous
  single-column rendering was getting cut at the right edge of the
  printed column.

§6 GQA:
- Removed the historical four-panel headline table (tab:gqa), the
  two figures (fig:gqa-lat, fig:gqa-break), and the prose paragraph
  that cited their per-panel numbers. Reason: the underlying
  milestone-gqa-headline bench has been simplified in collaborator
  commit 65c365f ("drop misleading single_user_/multi_user_
  panels") and no longer reproduces that data, so the section was
  left referencing a dataset the current bench cannot regenerate.
- The previously-added 4-cases long-context decode comparison is
  now §6's only Results subsection, retitled to
  "Results: long-context decode and parallelism strategies".

The headline GQA result that survives into the paper is therefore
the parallelism trade-off study (Case 4 / Cube-SP × PE-SP at 34 us
buying an 8x KV-memory reduction over the latency leaders).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 11:16:16 -07:00
ywkang 22f3968b51 paper(gqa): wire the 4-cases long-context decode comparison into §6
The collaborator commit 7c346de added the comparative figures
(gqa_decode_long_ctx_4cases_{latency,memory,traffic}.png) but the
fused-GQA section still only referenced the four headline panels.
This commit closes that loop:

- New §6 subsection "Long-context decode: parallelism strategies"
  added between Results and Analysis. It lays out the four
  parallelism strategies (Cube-SP/Repl x PE-TP/SP) and pulls in the
  three new figures.
- The discussion highlights the central trade: the fastest strategy
  (Case 3, Cube-Repl x PE-SP at 20.2 us) requires replicating the
  full KV cache to every CUBE, while Case 4 (Cube-SP x PE-SP, the
  chosen design marked *) gives back ~14 us in exchange for an 8x
  KV-memory reduction.  Case 4's ~190 IPCQ copies + ~190 DMA reads
  are precisely the on-device collective traffic PE_IPCQ and the
  torus links of §5 are provisioned to absorb -- a direct payoff
  of the communication-side codesign work.
- Connects back to §5 (PE_IPCQ / all-reduce) so the reader sees the
  capstone arc: the GEMM enabler exposes the data-movement bound,
  the communication enabler attacks it, and the long-context
  parallelism study shows how the choice between the two extremes is
  framed by KV memory vs. on-device collective traffic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 10:44:32 -07:00
ywkang e3f08972da paper: §2 polish + §5 fresh all-reduce data + kernbench probe table
§2 platform:
- Add a "memory-centric AHBM" sentence to the device-and-execution
  intro: each PE is paired with dedicated HBM bandwidth and on-PE
  TCM, so the performance question is about feeding compute from
  locally-attached memory + moving the unavoidable inter-PE traffic
  efficiently. Makes the AHBM character of the platform visible
  before §2.3 starts unpacking the latency model.
- Promote "Congestion and contention modeling" from \paragraph to
  \subsubsection: this is the platform's core differentiator over a
  peak-BW roofline, so it deserves its own heading.
- Rename "Control-plane (issue) cost model" to "Command dispatch
  overhead model" -- describes what it actually charges (the PE_CPU
  paying a fixed + per-byte cost to push a command to one of the
  accelerator engines) without the more abstract "control-plane"
  framing.
- Add a third Accuracy cross-check from kernbench probe: a
  PE→HBM DMA-distance sweep that confirms monotonic hop progression,
  bandwidth saturation matching the per-edge model, and the built-in
  invariants (D2H >= H2D, cross-CUBE best < worst). New
  Table~\ref{tab:probe-pe-dma} reports per-traversal latency and
  utilisation at 32KiB / 1MiB across five hop classes.
- Captured probe output as figures/probe_pe_dma_summary.txt for
  reproducibility.

§5 PE_IPCQ / all-reduce:
- Re-ran milestone-1h-ccl on current sim_engine (post-ADR-0064 Rev2
  and the IPCQ slot-wrap Phase-2 race fix). Updated the topology-
  comparison table and the buffer-kind caption to the fresh raw
  latencies. Headline ratios are preserved:
    torus vs mesh saving at 96 KB/PE: 24.8% (was 24.8%) -> ~25%
    torus vs ring saving at 96 KB/PE: 19.3% (was 19.3%) -> ~19%
    TCM vs HBM saving at 64 KB/PE: 13.4% (was 13.9%) -> ~13%
    TCM vs SRAM saving at 64 KB/PE: 37.2% (was 38.3%) -> ~37%
  The Executive Summary's "up to ~14%" / "up to ~38%" framing stays
  consistent with these post values.

Artifacts refreshed in src/kernbench/benches/1H_milestone_output/:
- ccl/summary.csv + per-topology PNGs + buffer-kind CSV/PNG
- ccl/comparison_mesh_vs_ring_vs_2DTorus_vs_theoretical_vs_fsim.png
- gemm/* (re-run yields identical pe_window structure; PNGs
  refreshed)

Paper figures synced to the fresh artifacts:
- figures/allreduce_comparison.png
- figures/allreduce_buffer_kind.png

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 10:38:47 -07:00
mukesh 7c346dec1b paper(figures): GQA long-context decode 4-cases comparative figures
Generated by scripts/paper/paper_plot_gqa_decode_long_ctx_4cases.py
from the bench's sweep.json (S_kv=8192, the headline 128K run was
sidestepped — Case 2's single-PE 128-tile sweep is the long pole
and shape/ordering is preserved across scales).

  gqa_decode_long_ctx_4cases_latency.png   end-to-end latency / case
  gqa_decode_long_ctx_4cases_traffic.png   ipcq / dma op-count breakdown
  gqa_decode_long_ctx_4cases_memory.png    KV bytes per cube (slide-11)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 17:48:34 -07:00
mukesh 1fbe833992 gqa(decode-4cases): wire latency + engine occupancy into sweep.json; add comparative plot script (5C.F)
Bench changes:
  - new _end_to_end_ns(op_log) and _engine_occupancy_ns(op_log) helpers
    in milestone_gqa_decode_long_ctx_4cases.py (mirror paper_gqa_latency.py)
  - _run_panel return dict now carries latency_ns + engine_occupancy_ns
    alongside op_log_summary, so sweep.json is the single source of truth
    for the comparative figures

Plot script:
  - new scripts/paper/paper_plot_gqa_decode_long_ctx_4cases.py reads
    sweep.json and emits 3 PNGs to docs/report/1H-codesign-paper/figures/:
      gqa_decode_long_ctx_4cases_latency.png   (end-to-end latency / case)
      gqa_decode_long_ctx_4cases_traffic.png   (ipcq/dma op counts / case)
      gqa_decode_long_ctx_4cases_memory.png    (KV bytes per cube / case)

Test changes:
  - 2 new tests verifying the helpers + _run_panel dict shape
  - lower smoke S_kv from 8192 -> 2048 (4x faster Case 2; assertions
    are S_kv-independent; one fold-loop iteration preserved)

18/18 tests pass in ~4 min.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 17:45:44 -07:00
ywkang 7f437a20bd paper(platform): edit-pass through §2 KernBench Platform done
- Reordered §2 subsections to follow the SIP → CUBE → PE → graph
  flow: Why KernBench → Device and execution model → Latency model
  → Modeled hardware configuration. Readers now meet the device
  hierarchy before the graph abstraction that re-uses it.
- §2.2 Device and execution model: starts with the SIP/CUBE/PE
  hierarchy paragraphs (each anchoring fig:sip-arch, fig:cube-arch,
  fig:pe-arch); then the runtime-API/sim-engine/components bullet
  list; then the atomic-vs-composite command distinction (corrects
  the prior over-narrow framing that read every PE command as
  composite -- atomic single-engine commands exist too, and PE_CPU
  itself runs control-plane work directly).
- §2.3 Latency model: opens with the four-contribution decomposition
  (per-node overhead, per-edge transmission, drain, queuing delay)
  and the latency_model schematic; retains existing The hardware as
  a graph / From graph to DES / Latency contributions / Congestion
  / Control-plane cost model / Accuracy paragraphs. Accuracy
  paragraph now closes on KernBench's sufficiency for *relative*
  HW/SW design trade-offs given analytic + external-simulator
  agreement.
- New figures and assets:
  - figures/sip_architecture.pdf  (SIP-level graph view)
  - figures/cube_architecture.pdf (CUBE-level zoom-in)
  - figures/latency_model.png      (conceptual latency-model
                                   schematic with per-node /
                                   per-edge / drain / queuing-delay
                                   colour coding)
  - figures/pe_architecture.png    (carried over)
- Source-of-truth generator for the latency schematic:
  scripts/paper/paper_latency_model_diagram.py (a report-only
  harness under scripts/paper/ per the /paper isolation rule).
- main.tex preamble: \usepackage{tikz} added (kept from prior
  sequence-diagram draft -- harmless now that the latency model is
  a PNG; left in to keep paragraph numbering stable).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-15 17:17:19 -07:00
mukesh 756680f4e6 gqa: fold decode_long into the Case 4 kernel; drop 1D-chain dead code
Option B fold: the lrab math previously in
``_gqa_attention_decode_long.py`` (used only as a thin re-export by
the Case 4 wrapper and as the import source for ``_merge_running``
in Cases 1 / 3) now lives inline in the Case 4 kernel file. ``sub_w``
is hardcoded to 4 (the 4×2 cube sub-mesh geometry; root cube 6);
the legacy ``sub_w=0`` 1D-chain backward-compat path is removed
along with its dedicated tests — the dropped ``single_user_*`` /
``multi_user_*`` panels that exercised it are already gone.

Production changes:
  - inline lrab math into _gqa_attention_decode_long_ctx_cube_sp_pe_sp.py
    (drops sub_w param; _ROOT_CUBE=6 baked in)
  - inline _merge_running into Cases 1 (cube_sp_pe_tp) and 3
    (cube_repl_pe_sp) so they no longer depend on the deleted file
  - delete src/kernbench/benches/_gqa_attention_decode_long.py
  - remove dead _run_decode_panel / _DECODE_* constants /
    decode-side _PANEL_DISPATCH / _make_bench_fn decode branch
    from milestone_gqa_headline.py (only the prefill panel remains)
  - update _gqa_attention_decode_opt2.py docstring reference

Test changes:
  - delete 7 legacy test_gqa_*.py files that pre-dated the 4-cases
    architectural split (coverage now subsumed by the 16 4-cases tests)
  - remove test_opt2_matches_opt3_data_mode + _run_decode_data helper
    from test_gqa_decode_opt2.py (the parity check required sub_w=0
    which no longer exists; opt2's other 4 tests preserved)

20/20 tests pass (16 4-cases + 4 opt2 smoke/dispatch).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 16:32:37 -07:00
mukesh ddee28a499 gqa(decode-4cases): rename bench/kernels/panels with long_ctx token
The 4-cases comparative study is specifically about long-context
decode (LLaMA-3.1-70B, S_kv=128K, T_q=1); the long_ctx token in the
names makes the scope explicit and aligns with the existing
_gqa_attention_decode_long convention.

Renames (mechanical; no semantic change):
  - bench file:   milestone_gqa_decode_4cases.py
                  → milestone_gqa_decode_long_ctx_4cases.py
  - bench name:   milestone-gqa-decode-4cases
                  → milestone-gqa-decode-long-ctx-4cases
  - output dir:   1H_milestone_output/gqa_decode_4cases/
                  → 1H_milestone_output/gqa_decode_long_ctx_4cases/
  - env vars:     GQA_DECODE_4CASES_RUN / _TOPOLOGY
                  → GQA_DECODE_LONG_CTX_4CASES_RUN / _TOPOLOGY
  - 4 kernel files _gqa_attention_decode_<case>.py
                  → _gqa_attention_decode_long_ctx_<case>.py
  - 4 kernel functions gqa_attention_decode_<case>_kernel
                  → gqa_attention_decode_long_ctx_<case>_kernel
  - 4 dispatch kinds  decode_<case> → decode_long_ctx_<case>
  - 4 panel names     single_kv_group_decode_gqa_<case>
                      → single_kv_group_decode_long_ctx_gqa_<case>
  - 4 helper functions _run_decode_panel_<case>
                       → _run_decode_panel_long_ctx_<case>
  - test file renamed in lockstep

Also: Case 4 smoke test now uses its case-specific helper
_run_decode_panel_long_ctx_cube_sp_pe_sp (consistent with Cases 1-3)
instead of the legacy _run_decode_panel from milestone_gqa_headline.
16 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 16:09:21 -07:00
mukesh 3c155be8e6 gqa(decode-4cases): give Case 4 its own thin kernel file for naming symmetry
Cases 1-3 each have a dedicated _gqa_attention_decode_<case>.py
kernel file; Case 4 previously reached into _gqa_attention_decode_long.py
via the headline bench's _run_decode_panel helper, breaking the
one-file-per-case convention. Adds _gqa_attention_decode_cube_sp_pe_sp.py
as a 20-line wrapper that bakes in sub_w=4 (the C=8 lrab geometry)
and gives Case 4 its own kind ("decode_cube_sp_pe_sp") and helper
(_run_decode_panel_cube_sp_pe_sp). decode_long.py is unchanged
(still serves the legacy decode_long tests). 16 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 15:58:11 -07:00
mukesh 0ef4fde5d8 gqa(decode-4cases): Case 1 — Cube-SP × PE-TP (inter-CUBE lrab only; PE-TP B=1 waste) (5C.A)
Adds the fourth and final 4-cases panel: KV split S_kv-wise across
the 8 cubes (cube=row_wise, S_local = S_kv/C), replicated within
each cube (pe=replicate). PE-TP at B=1 means only PE 0 of each cube
has work; PEs 1-7 early-return (slide-11 PE-TP-at-B=1 waste). No
intra-CUBE comm; inter-CUBE 8-way reduce reuses the lrab-adapted
center-root pattern (root cube 6) — same structural cost as Case 4's
inter-CUBE phase (21 ipcq_copy). 16 tests pass (4 per case).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 15:30:00 -07:00
mukesh ae942f6959 gqa(decode-4cases): Case 3 — Cube-Repl × PE-SP (intra-CUBE AR only; 8× memory) (5C.C)
Adds the third 4-cases panel: KV replicated per cube (8× memory
waste), PEs SP on S_kv within each cube, intra-CUBE 8-way reduce on
(m, ℓ, O), and no inter-CUBE comm (every cube ends with full answer;
designated writer = cube 0). Reuses the row-chain + col-bridge
intra-CUBE pattern that anchors Case 4 (21 ipcq_copy per cube × 8
cubes = 168 total). 12 tests pass (4 Case 4 + 4 Case 2 + 4 Case 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 15:18:41 -07:00
mukesh 5672c8f3ef gqa(decode-4cases): Case 2 — Cube-Repl × PE-TP (no comm; 8× memory) (5C.B)
Second case in the GQA decode 4-cases comparative study per
GQA_full_deck.pptx slide 11. Case 2 replicates K, V across all 8
cubes × 8 PEs (the slide-11 8 KB/tok/PE memory waste) and has zero
inter-rank comm. For B=1 (single-user decode default), only PE 0
of CUBE 0 has work — the inherent PE-TP waste slide 11 calls out.

Changes:
- New kernel: src/kernbench/benches/_gqa_attention_decode_cube_repl_pe_tp.py
  Simplest of the 4 cases. Active rank loads full Q/K/V from HBM,
  computes attention via S_kv tile sweep with online-softmax merge,
  writes O. All non-(0,0) ranks early-return. No tl.send/recv.
- src/kernbench/benches/milestone_gqa_decode_4cases.py:
    - Add panel single_kv_group_decode_gqa_cube_repl_pe_tp (Case 2)
      to _PANELS and _PANEL_DISPATCH.
    - Add _run_decode_panel_cube_repl_pe_tp helper: DPPolicy K/V/Q/O
      = cube=replicate, pe=replicate (models 8× memory waste).
    - Extend _make_bench_fn to dispatch kind="decode_cube_repl_pe_tp"
      to the new runner.
- tests/attention/test_milestone_gqa_decode_4cases.py:
    4 new tests assert Case 2 contract: panel registered, smoke
    completion, zero ipcq_copy (no comm), single dma_write from cube 0.

Verification: 8/8 tests pass (4 Case 4 anchor + 4 new Case 2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 14:59:58 -07:00
mukesh 65c365f858 bench(milestone-gqa-headline): drop misleading single_user_/multi_user_ panels
The legacy panel names suggested batched serving semantics they never
had — all four modeled a single user with KV sharded differently
(C=1 single-cube; C=4 multi-cube Cube-SP), at toy dims (T_q=4, S_kv≤128).
The single-KV-group C=8 panel + the new milestone-gqa-decode-4cases
bench cover the meaningful comparisons; pytest regression already
covers C=1/C=4 configurations end-to-end at richer scale.

Changes:
- milestone_gqa_headline.py: drop the 4 legacy panels; _PANELS now
  contains only single_kv_group_prefill_gqa_c8_p8. Update docstring.
- tests/attention/test_milestone_gqa_headline.py: drop the 3 legacy-
  panel architectural tests (Ring-KV traffic, root-only decode write,
  per-CUBE distributed output) and test_decode_panels_use_real_gqa
  (no decode panels in this bench anymore). Equivalent properties
  are asserted in test_milestone_gqa_single_kv_group_prefill_panel.py
  (64 dma_writes, 896 ipcq_copy) and test_milestone_gqa_decode_4cases.py
  (1 dma_write at cube 6, 189 ipcq_copy).
- tests/attention/test_milestone_gqa_single_kv_group_prefill_panel.py:
  drop test_existing_prefill_panel_runner_backward_compat (it exercised
  multi_user_prefill_gqa which no longer exists).
- scripts/paper/paper_plot_gqa.py: replace the 4 legacy _LABELS entries
  with the single single_kv_group_prefill_gqa_c8_p8 label.
- Regenerate 1H_milestone_output/gqa_headline/sweep.json from the new
  panel set.

Verification: 9/9 tests pass across the 3 affected test files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 14:50:25 -07:00
mukesh c164645aee gqa(decode-4cases): Case 4 anchor in dedicated bench (5C.D)
First milestone of the decode 4-cases comparative study per
GQA_full_deck.pptx slides 11-17. Case 4 (Cube-SP × PE-SP, the optimal
case per slide 11) is structurally the existing _gqa_attention_decode_long
at sub_w=4 (Increment 2's lrab-adapted center-root reduce). This commit
wires it into a dedicated bench so the remaining cases land alongside.

Changes:
- New bench: src/kernbench/benches/milestone_gqa_decode_4cases.py
  Houses the 4 case panels under one milestone-gqa-decode-4cases entry
  (gated by GQA_DECODE_4CASES_RUN=1; output to
   1H_milestone_output/gqa_decode_4cases/sweep.json). Cases 1-3 are
  TBD in subsequent sub-increments (5C.A/B/C).
- New panel: single_kv_group_decode_gqa_cube_sp_pe_sp
  C=8, P=8, sub_w=4, T_q=1, S_kv=131_072, d_head=128, h_q=8, h_kv=1.
- src/kernbench/benches/milestone_gqa_headline.py: _run_decode_panel
  extended with keyword-only sub_w/T_q/d_head/h_q/h_kv overrides
  (defaults preserve existing-panel behaviour).
- tests/attention/test_milestone_gqa_decode_4cases.py: 4 new tests
  asserting registration, smoke completion, reduce-to-root at the lrab
  center cube (cube 6), and the predicted 189-ipcq Case-4 traffic
  pattern (168 intra-CUBE + 21 inter-CUBE lrab Phase 1+2).
- tests/attention/test_milestone_gqa_headline.py: rename
  test_sweep_json_has_four_panels -> test_sweep_json_has_expected_panels
  and switch hardcoded 4 to len(PANELS) (the panel set grew to 5
  with Increment 5's single_kv_group_prefill_gqa_c8_p8).

Deviation noted: slide 13 prescribes AllReduce on (m,ℓ,O); our kernel
does reduce-to-root (only the lrab center cube has the answer) per
ADR-0060 §4. Treated as the kernbench Case-4 baseline.

Verification: all 4 new tests pass; 90 regression tests pass; the
previously-failing test_sweep_json_has_four_panels now passes under
its renamed form.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 14:31:44 -07:00
mukesh 5b4d9cb597 bench(milestone-gqa-headline): scale single_kv_group prefill panel T_q=S_kv=1K (scratch budget)
The headline T_q=S_kv=32K target overflows the 1 MB per-PE scratch
pool: at T_q_local=4K and tile_s=1024 the scores matrix alone is 8 MB.
The prefill kernel's bootstrap section also leaves K_t/V_t/scores/
exp_scores persistent (outside tl.scratch_scope), inflating baseline.

Scale-down to T_q=S_kv=1K (T_q_local=128, fits comfortably) preserves
the C=8 + P=8 architecture demonstration; the true LLaMA 32K headline
awaits a future increment to add Q-axis tiling and tighten bootstrap
scratch discipline.

Verified end-to-end: kernbench run --bench milestone-gqa-headline now
produces sweep.json with all 5 panels. The new panel shows
ipcq_copy=896 (matches (C-1)·n_tiles·2·C·P = 7·1·2·8·8) and
dma_write=64 (one per PE, head-parallel + intra-CUBE PE-SP).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 14:00:30 -07:00
mukesh 9e1242039b gqa: single-KV-group LLaMA-3.1-70B prefill milestone (Increments 1-5)
End-to-end wires C=8 P=8 d_head=128 prefill with snake-ring inter-CUBE
SFR, intra-CUBE PE-SP (all 64 ranks active), and the milestone bench
panel. Decode kernel gains lrab-adapted center-root reduce for the
2×4 sub-mesh per ADR-0060 §4.2.

Increment 1 — SFR multi-row snake
  src/kernbench/ccl/sfr_config.py: configure_sfr_intercube_ring gains
  submesh_shape / submesh_origin kwargs; installs a Hamiltonian snake
  ring through a rectangular sub-mesh (every hop is 1-hop physical
  neighbour). Backward-compat: 1D-row behaviour preserved when
  submesh_shape is None.
  tests/test_intercube_snake_ring.py (12 tests)

Increment 2 — Decode lrab-adapted center-root reduce
  src/kernbench/benches/_gqa_attention_decode_long.py: new sub_w param
  (default 0 = existing 1D-chain). sub_w >= 2 selects the ADR-0060
  §4.2 prescribed lrab-adapted Phase 1+2 reduce (bidirectional row +
  bidirectional col converge to the center cube), with log-sum-exp
  _merge_running replacing the plain + of lrab.
  tests/attention/test_gqa_decode_long_2d_reduce.py (4 tests)

Increment 3 — Prefill kernel at C=8 (no production change)
  Verified by inspection that the existing prefill_long kernel +
  Increment 1's snake SFR already work at C=8 without any kernel
  edit. The kernel speaks logical W/E; the snake routes it.
  tests/attention/test_gqa_prefill_long_c8_snake.py (3 tests)

Increment 4 — Intra-CUBE PE-SP in prefill (all 64 ranks)
  src/kernbench/benches/_gqa_attention_prefill_long.py: new P param
  (default 1 = existing PE-0-only). P > 1 splits T_q query-axis-wise
  across the P PEs of each CUBE; output rows are disjoint per PE so
  no intra-CUBE reduce is needed; each PE drives its own same-lane
  ring (P parallel rings).
  tests/attention/test_gqa_prefill_long_pe_sp.py (5 tests)

Increment 5 — LLaMA-scale milestone bench panel
  src/kernbench/benches/milestone_gqa_headline.py: new panel
  single_kv_group_prefill_gqa_c8_p8 (C=8, P=8, T_q=S_kv=32K,
  d_head=128). _run_prefill_panel extended with P/T_q/d_head
  defaults; routes snake SFR when C > mesh_w.
  tests/attention/test_milestone_gqa_single_kv_group_prefill_panel.py (3 tests)

Total: 4 production files modified, 5 new test files, 27 new tests.
Followed the Phase 1/2 protocol per CLAUDE.md throughout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 13:42:31 -07:00
263 changed files with 31017 additions and 4247 deletions
@@ -252,6 +252,74 @@ composite (가장 큰 것이 decode opt2 의 `#2` ~322 bytes) 보다 훨씬 위
Decode opt2 의 `#2` composite (10 ops, ~322 bytes) 는 1024 cap 안에 Decode opt2 의 `#2` composite (10 ops, ~322 bytes) 는 1024 cap 안에
편안히 — GQA workload 에 에러 없음. 편안히 — GQA workload 에 에러 없음.
### D8. Single-op-cmd fast-path (cmd 타입별 FIXED)
모든 **single-op** cmd — DMA descriptor (`DmaReadCmd`, `DmaWriteCmd`)
*그리고* single-op compute dispatch (`GemmCmd`, `MathCmd`, `CopyCmd`)
— 는 일반 control-path 비용과 분리된, 가벼운 FIXED 를 지불한다:
**`fixed_per_single_op_cmd_cycles`** (기본 **8 cycles**) 가 이들 전부에서
`fixed_per_cmd_cycles` 를 대체한다. **`CompositeCmd` 만이 일반
control-path 비용** `fixed_per_cmd_cycles` (= 40 cycles) 를 유지하는
유일한 cmd 다.
구체적으로 D1 의 공식이 다음과 같이 확장된다:
```
dispatch_cycles(cmd) = FIXED(type(cmd)) + cmd.logical_bytes × R
FIXED(CompositeCmd) = fixed_per_cmd_cycles (= 40)
FIXED(나머지 전부, 즉 모든 single-op cmd:
DmaReadCmd / DmaWriteCmd / GemmCmd / MathCmd / CopyCmd)
= fixed_per_single_op_cmd_cycles (= 8)
```
**왜 single-op cmd 에 가벼운 FIXED 를 따로 두나.** D1 은 모든 발행 cmd 에
동일한 FIXED 를 매겼다 — 가장 단순한 모델이라서. 그러나 실제로 single-op
cmd — DMA descriptor 든, 엔진에 단일 GEMM / elementwise / copy 를
dispatch 하는 것이든 — 는 40-cycle FIXED 가 모델링하던 일반 control-path
비용보다 훨씬 가볍다. scheduler 측 plan 을 만들 필요 없이, descriptor 나
instruction 하나를 엔진 queue 에 push 하는 것뿐이다:
- NVIDIA Hopper TMA: 단일 PTX 명령 (`cp.async.bulk`) 으로 bulk async DMA
발사 — SM 측에선 ~1 ISA cycle, TMA 엔진이 fan-out / chunking 자체 처리.
- NVIDIA Ampere `cp.async`: warp-level async copy ~12 cycles.
- 단일 MMA / tensor-core 발행 (`wgmma`, `mma.sync`): math pipe 에 명령
하나, control-path round trip 아님.
- AMD AQL packet 을 HSA queue 에 write: ~515 cycles.
- 일반 descriptor-ring-push 디자인 (Synopsys/Xilinx-style DMA IP): MMIO
write 들 합쳐 ~520 cycles.
- Tenstorrent tile descriptor 발행, Habana Gaudi TPC descriptor RAM
+ start register: descriptor 당 한 자릿수 cycles.
40-cycle FIXED 는 `CompositeCmd` 에 한해 정당하다. composite 는 단일
dispatch 가 아니기 때문이다: scheduler 가 tile-feeder plan 을 만들고,
내부 `DMA_READ → FETCH → GEMM → STORE → DMA_WRITE` stage 들에 걸쳐
per-tile read/write hazard 를 추적하고, completion handle 을 배선해야
한다. single-op `GemmCmd` 는 그런 기구 없이 엔진 발행 하나일 뿐이다 —
이를 composite 와 같은 40 cycles 로 매기면 단일 명령을 스케줄된 plan
전체와 혼동하는 것이다.
**왜 이게 커널 평가에 중요한가.** 일정한 40-cycle FIXED 는 user 가 직접
orchestrate 하는 single-op-primitive 커널의 dispatch overhead 를 부풀린다.
`K = N_K · TILE_K` 의 chunked-prefetching async GEMM 은 K-tile 마다
`DmaReadCmd` (B-chunk load) **와** `GemmCmd` (per-chunk `tl.dot`) **와**
`MathCmd` (누적 `tl.add`) 를 발행하므로, per-cmd FIXED 가 dispatch
예산을 지배한다. single-op fast-path 는 D1 이 잡으려던 *구조적 cmd 수
신호* (`N` 개의 single-op cmd 를 발행하는 recipe 가 `N` tile 을 내부로
묶는 composite 하나보다 더 비싸야 한다는 점) 를 유지하면서, 40-cycle
일반 control-path 비용을 덧씌우는 모델링 과잉 청구는 분리한다.
**왜 굳이 8 cycles 인가.** 위 조사 범위 (TMA ~1, descriptor-ring ~15) 의
중간값. 의도적인 *모델링 디폴트* — 측정된 HW 수치가 아님. 이 디폴트의 역할
은 정성적 동작에서 single-op dispatch path 를 composite control path 와
구별하는 것. Topology override 가능 (D4).
**Composite 수치에 미치는 영향.** Composite tile 내부 stage (HW tile 당
`DMA_READ`, `FETCH`, `GEMM`, `STORE`, `DMA_WRITE`) 는 scheduler 의
tile-feeder loop 가 발행하므로 host-side TLContext 의 `_charge_dispatch`
를 안 거친다. 단일 `CompositeCmd` 자체는 여전히 40-cycle control-path
FIXED 를 지불한다. D8 은 user-side single-op primitive (`tl.load` /
`tl.store` / `tl.dot` / `tl.add` / …) 에 매기는 FIXED 만 바꾼다.
Composite 측정치는 안정 유지.
## Alternatives ## Alternatives
### A1. Revision 1 의 op-type calibration 표 유지 ### A1. Revision 1 의 op-type calibration 표 유지
@@ -0,0 +1,243 @@
# ADR-0070: GQA Short-Context Attention — Unified A1/A2/A4/B Mapping for Prefill and Decode
## Status
Proposed — short-context (single tile to a handful of KV tiles) GQA
attention benchmark covering **both prefill and decode** in **four
mapping modes** (A1/A2/A4/B) and **three composite tiers** (without
composite, GEMM-only composite, composite + softmax_merge fused).
Compute-only attention — weight DMA, ITL batching, and end-to-end
LLM driver scope are out of scope (separate ADRs).
## Context
A GQA layer at the LLaMA-3.1-70B headline shape (`h_q=64`, `h_kv=8`,
`d_head=128`, GQA group `G = h_q/h_kv = 8`) on a 4×4 cube SIP can be
mapped to AHBM in several ways that trade KV-cache memory, HBM read
volume per cube, and intra-cube IPCQ traffic against PE parallelism.
> **Bench-vs-headline shape.** The kernels and tests use a
> `d_head = 64` proxy (`tests/attention/test_gqa_short_context.py`
> `D_HEAD = 64`) so the per-tile working set fits comfortably in
> scratch across all four modes. The mapping decisions are
> `d_head`-independent — only the per-PE GEMM tile bytes scale.
The four candidate mappings (per *GQA-mapping-on-AHBM-short-context* note §1):
```
Mode kv_per_cube C group_size Reduce / broadcast topology
---- ----------- ------- ---------- ----------------------------------
A1 1 h_kv P (=8) row chain + col bridge (2×4 mesh)
A2 2 h_kv/2 P/2 (=4) row chain only
A4 4 h_kv/4 P/4 (=2) single intra_W / intra_E hop
B 8 1 1 no broadcast / no reduce (single PE)
```
Within each cube the 8 PEs split into `kv_per_cube` groups of
`group_size = P/kv_per_cube` PEs; each group owns one KV head.
The kernel must be able to compare these mappings at multiple context
lengths (8K..64K KV tokens) and across the three composite tiers so
the team can size the per-cube HBM / IPCQ / GEMM tradeoff empirically.
## Decision
### 1. Mapping (unified A1/A2/A4/B)
Two unified kernels select mode at launch via `kv_per_cube ∈ {1,2,4,8}`:
- **Prefill** (`gqa_attention_prefill_short_kernel`):
- Q-tile split — `T_q` rows floor-balanced across the group's
`group_size` PEs.
- FA2 head fusion — `G` Q heads fused into the M dimension of one
batched GEMM per PE per tile.
- **IPCQ KV broadcast** — group root (`pe_in_group == 0`) loads K/V
from HBM and IPCQ-broadcasts each tile to the rest of its group;
q-tiles are independent so no intra-group reduce.
- **Decode** (`gqa_attention_decode_short_kernel`):
- Sequence-shard — each PE owns `S_local = S_kv/group_size` tokens
of the group's KV head.
- FA2 head fusion — same as prefill.
- **IPCQ chain reduce** — per-PE partial `(m, , O)` chain-reduce
up to group root (PE 0), which normalizes and stores.
Geometry (2×4 mesh):
```
group_size = 8 : 2 row × 4 col (A1)
group_size = 4 : 1 row × 4 col (A2)
group_size = 2 : 1 row × 2 col (A4)
group_size = 1 : single PE (B)
```
### 2. Shard addressing (ADR-0011 D-VA1 contract)
Deploy is per-(sip, cube, pe) but `tl.load` receives a single global
VA per tensor. The kernel computes its own shard base offset from
`tl.program_id(axis=0)` (PE id) and `tl.program_id(axis=1)` (cube id):
```python
# Decode shape (pe=row_wise): K is split across PEs by sequence shard.
cube_K_base = cube_id * kv_per_cube * K_HEAD_BYTES
head_K_base = group_id_in_cube * K_HEAD_BYTES
pe_K_seq_offset = pe_in_group * n_tiles_per_pe * K_TILE_BYTES
k_shard_base = k_ptr + cube_K_base + head_K_base + pe_K_seq_offset
```
For **prefill** the K/V dp is `pe=replicate` (group root reads and
broadcasts) so the `pe_in_group` term drops out:
`k_head_shard_base = k_ptr + cube_K_base + head_K_base`.
Skipping `cube_id` collapses all cubes onto cube 0's HBM region
(observed pre-fix as an 11.5× per-cube DMA imbalance).
### 3. Three composite tiers
The same mapping is exercised against three GEMM/MATH dispatch styles
so the contribution of the composite API vs the recipe-driven fusion
can be isolated:
```
(1) without composite primitives only (tl.dot, tl.exp, …)
(2) with composite (GEMM-only) tl.composite(op="gemm")
(3) with composite + softmax_merge tl.composite(prologue=[softmax_merge],
op="gemm", out=O,
epilogue=[add])
```
File layout (`src/kernbench/benches/gqa_helpers/short_ctx/`):
```
_gqa_attention_{prefill,decode}_short.py (1)
_gqa_attention_{prefill,decode}_short_composite.py (2)
_gqa_attention_{prefill,decode}_short_composite_fused.py (3)
```
Tier (2) is **GEMM-only by definition** — no recipe-driven fusion,
so both prefill's and decode's P·V stay `tl.dot`. Recipe-driven
fusion is exactly what tier (3) adds.
Tier (3) on **multi-cube modes (A1/A2/A4) of prefill** relies on the
D4 supplement: any composite operand that is an IPCQ recv'd slot
(non-root PE's K_T in Q·Kᵀ, V in P·V) is pinned and read in place
instead of being DMA-streamed from HBM. Without the supplement every
recv slot fed to a composite — both Q·Kᵀ's `b=K_T` and P·V's `b=V`
— PageFaults on PA decode (PE scratch addresses overflow the 51-bit
PA range).
### 4. Caller contract — `_validate_config`
Each kernel module exposes a single `_validate_config(...)` helper
called by the bench wrapper before launch. The kernel itself is lean
(no inline `if` guards): the contract block is enforced caller-side,
sim cost zero, but catches every silent-shard-corruption foot-gun
(`kv_per_cube=3 → group_size=2` via integer division, non-integer
GQA group, mismatched cube count, partial-tile `S_kv`, …) before any
address arithmetic runs.
### 5. Tensor layouts (host-side, mode-invariant byte totals)
- **Q**: `(kv_per_cube·T_q, h_q·d_head/kv_per_cube)`
`dp=(cube=column_wise, pe=replicate)` over `C = h_kv/kv_per_cube`.
- **K**: `(h_kv·S_kv·d_head/TILE_S_KV, TILE_S_KV)` tile-major.
Decode: `dp=(cube=row_wise, pe=row_wise)`.
Prefill: `dp=(cube=row_wise, pe=replicate)` (broadcast model).
- **V**: `(h_kv·S_kv, d_head)` native. Same dp as K.
- **O**: same dp as Q.
Caller pre-scales Q by `1/√d_head`.
## Verification
### Smoke + regression (`tests/attention/test_gqa_short_context.py`)
- Prefill / decode smoke (all 4 modes, single-tile S_kv).
- Multi-tile coverage (all 4 modes, S_kv chosen so each PE owns
≥2 tiles).
- Op-count invariants — prefill `dma_writes = group_size·kv_per_cube·C`,
`dma_reads = P·C + 2·kv_per_cube·C·n_tiles`; decode
`dma_writes = kv_per_cube·C`.
- IPCQ topology — short-context kernel never emits inter-CUBE E/W
IPCQ.
- **ADR-0011 D-VA1 regression** — `per_cube_disjoint_src_addrs`,
`per_cube_dma_balanced` (max/min DMA busy ratio < 1.1×) per mode
per phase.
### Composite smoke
- `test_{prefill,decode}_composite_smoke[A1/A2/A4/B]` — tier (2),
4 modes × 2 phases.
- `test_{prefill,decode}_composite_fused_smoke[A1/A2/A4/B]` — tier
(3), 4 modes × 2 phases.
- `test_decode_composite_fused_multitile[A1/A2/A4/B]`
`n_tiles_per_pe == 2` per mode so the recipe-fused tile loop
actually executes.
### Mode × context sweep (`tests/attention/test_gqa_short_context_sweep_*.py`)
Six sweep files (3 tiers × {prefill, decode}) measure each
`(mode, S_kv)` cell and dump the same metric set to CSV:
```
wall_us, n_pe, gemm_util, math_count, math_pipeline_us, hbm_bw_util,
hbm_read_mb, hbm_write_kb, ipcq_kb, kv_cache_per_cube_mb
```
S_kv ∈ {8K, 16K, 32K, 64K}; outputs under `docs/sweeps/`.
Composite/fused sweeps count both `gemm_f16` (non-pipeline) **and**
`TileToken/GEMM` (pipeline composite path); fused additionally counts
`TileToken/MATH`. Baseline sweeps count `gemm_f16` only.
## Known limitations
1. **Causal mask** — kernel is non-causal. Adding causal masking is
orthogonal to mapping and is scheduled as a separate ADR.
2. **f16 online-softmax accumulator**`(m, , O)` are f16 throughout.
Long-context numerical drift will need an f32 accumulator before
correctness-grade use.
3. **End-to-end numeric validation** — current tests are op-count /
topology / per-cube-DMA invariants. No full-kernel
`np.allclose` against a torch/numpy reference yet.
4. **Skinny-M GEMM underfill** — decode (`T_q=1`) gives `M = G = 8`,
well below `PE_SCHEDULER` supertile `TILE_M = 32`. The two GEMM
paths handle this differently:
- **Primitive `tl.dot`** dispatches the GEMM at the actual
`m = 8` (no padding); GEMM time reflects the real 8-row work.
- **`tl.composite(op="gemm")`** tiles at `TILE_M = 32`, padding
`M` 4× with zeros; GEMM time reflects the padded 32-row work.
Consequence: when sweep CSVs show tier (2)/(3) `gemm_util`
higher than tier (1), most of that gap is supertile padding
overhead, **not** extra useful work or fusion savings — read it as
"composite tile shape doesn't match decode-skinny shape" rather
than "composite/fusion is more compute-intensive." This is the
**intended decode shape** for this benchmark; lifting it requires
batched-M (multi-request inference, `B_sys > 1`), tracked as a
separate batched variant — see "Future work".
5. **Multi-cube prefill composite without the ADR-0065 D4 supplement**
PageFaults. The supplement — IPCQ recv'd slots are pinned and read
in place as composite operands — ships alongside this ADR.
## Future work
- Batched-M variant (`B_sys ∈ {1,2,4,8,16}`) so composite/fused
pipeline overlap shows up in wall_clock rather than only in
per-engine utilization.
- Long-context (S_kv ≥ 128K) sweep extension for the headline LLaMA
decode target.
- Causal-mask + f32 accumulator promotion.
- 3-variant comparison plots from the sweep CSVs (wall, gemm util,
hbm bw util, ipcq, kv cache) — generated under `docs/diagrams/`.
## References
- ADR-0011 — PhysAddr + VA contract (D-VA1: kernel-computed shard
offset).
- ADR-0060 — GQA fused attention on AHBM (precursor of the unified
mapping).
- ADR-0064 — CPU issue-cost model (composite supertile motivation).
- ADR-0065 — Flat ops, composite, softmax_merge recipe (with the D4
supplement applied here).
- ADR-0070 supersedes ADR-0060 §B.split.2 short-prefill clause.
- *GQA-mapping-on-AHBM-short-context* note (research design rationale).
@@ -184,8 +184,15 @@ The empty-`ops` form is the legacy single-op path.
- `DMA_READ`: `simpy.Resource(capacity=1)`. - `DMA_READ`: `simpy.Resource(capacity=1)`.
- `DMA_WRITE`: `simpy.Resource(capacity=1)`. - `DMA_WRITE`: `simpy.Resource(capacity=1)`.
- Both channels run concurrently (READ ∥ WRITE allowed). - Both channels run concurrently (READ ∥ WRITE allowed).
- Within a channel, requests serialize (READ ∥ READ disallowed; same - Within a channel, requests serialize **at the issue path** (READ ∥ READ
for WRITE). issued out-of-order is disallowed; same for WRITE). The channel is
held only until the request is enqueued onto the next hop (router),
then released — it does **not** block until the HBM round-trip
completes. Multiple in-flight requests are therefore allowed, and
HBM-level serialization is the responsibility of the HBM controller's
per-PC `available_at` timestamps (ADR-0033 D1). This is what allows
back-to-back small-tile DMAs to amortize the per-request head latency
through the fabric.
- `vc_comm` is an orthogonal channel for IPCQ traffic defined in - `vc_comm` is an orthogonal channel for IPCQ traffic defined in
ADR-0023 D8 — out of scope for this ADR. ADR-0023 D8 — out of scope for this ADR.
@@ -270,6 +270,80 @@ kernel author, who is best placed to decide how to split the work.
Decode opt2's `#2` composite (10 ops, ~322 bytes) sits comfortably Decode opt2's `#2` composite (10 ops, ~322 bytes) sits comfortably
inside the 1024 cap — no error for the GQA workload. inside the 1024 cap — no error for the GQA workload.
### D8. Single-op-cmd fast-path (per-cmd-type FIXED)
Every **single-op** command — the DMA descriptors (`DmaReadCmd`,
`DmaWriteCmd`) *and* the single-op compute dispatches (`GemmCmd`,
`MathCmd`, `CopyCmd`) — carries a separate, lighter FIXED than the
general control-path cost: **`fixed_per_single_op_cmd_cycles`** (default
**8 cycles**) replaces `fixed_per_cmd_cycles` for all of them.
**`CompositeCmd` is the only command that stays on the general
control-path cost** `fixed_per_cmd_cycles` (= 40 cycles).
Concretely, the D1 formula becomes
```
dispatch_cycles(cmd) = FIXED(type(cmd)) + cmd.logical_bytes × R
FIXED(CompositeCmd) = fixed_per_cmd_cycles (= 40)
FIXED(everything else, i.e. every single-op cmd:
DmaReadCmd / DmaWriteCmd / GemmCmd / MathCmd / CopyCmd)
= fixed_per_single_op_cmd_cycles (= 8)
```
**Why a separate, lighter FIXED for single-op cmds.** D1 treated every
emitted command uniformly because that is the simplest defensible
model. In practice an single-op command — whether a DMA descriptor or a
single GEMM / elementwise / copy dispatch to an engine — is *much*
cheaper than the generic control-path cost the 40-cycle FIXED was
modeling. It is a single descriptor or instruction pushed to an engine
queue, with no scheduler-side plan to build:
- NVIDIA Hopper TMA: a single PTX instruction (`cp.async.bulk`) initiates
a bulk async DMA — ~1 ISA cycle on the SM side, with the TMA engine
doing fan-out and chunking itself.
- NVIDIA Ampere `cp.async`: ~12 cycles per warp-level async copy.
- A single MMA / tensor-core issue (`wgmma`, `mma.sync`): one
instruction to the math pipe, not a control-path round trip.
- AMD AQL packet write to a HSA queue: ~515 cycles.
- Generic descriptor-ring-push designs (Synopsys/Xilinx-style DMA IP):
~520 cycles for the MMIO writes.
- Tenstorrent tile descriptor emission, Habana Gaudi TPC descriptor RAM
+ start register: single-digit cycles per descriptor.
The 40-cycle FIXED is justified for `CompositeCmd` specifically, because
a composite is *not* a single dispatch: the scheduler must build a
tile-feeder plan, track per-tile read/write hazards across its internal
`DMA_READ → FETCH → GEMM → STORE → DMA_WRITE` stages, and wire a
completion handle. An single-op `GemmCmd` is one engine issue with none of
that machinery; charging it the same 40 cycles as a composite conflates
a single instruction with a whole scheduled plan.
**Why this matters for kernel evaluation.** A uniform 40-cycle FIXED
inflates the dispatch overhead of user-orchestrated single-op-primitive
kernels. A chunked-prefetching async GEMM at `K = N_K · TILE_K` emits,
per K-tile, a `DmaReadCmd` (B-chunk load) **and** a `GemmCmd` (the
per-chunk `tl.dot`) **and** a `MathCmd` (the running `tl.add`
accumulate) — so the per-command FIXED dominates its dispatch budget.
The single-op fast-path keeps the *structural* command-count signal that
D1 was designed to capture (a recipe that emits `N` single-op commands
*does* pay more than a recipe that emits one composite covering `N`
tiles internally) without conflating it with a modeling overcharge that
would be specific to a 40-cycle generic control path.
**Why 8 cycles specifically.** 8 sits at the mid-range of the survey
above (TMA ~1 to descriptor-ring ~15). It is intentionally a *modeling
default*, not a measured HW number — the role of this default is to
distinguish the single-op dispatch path from the composite control path in
qualitative behaviour. The value is overridable per topology (D4).
**Effect on composite numbers.** Composite tile-internal stages
(`DMA_READ`, `FETCH`, `GEMM`, `STORE`, `DMA_WRITE` per HW tile) are
emitted by the scheduler's tile-feeder loop, not by the host-side
TLContext, so they do **not** go through `_charge_dispatch`. The single
`CompositeCmd` itself still pays the 40-cycle control-path FIXED. D8
only changes the FIXED charged to user-side single-op primitives
(`tl.load` / `tl.store` / `tl.dot` / `tl.add` / …). Composite
measurements remain stable.
## Alternatives ## Alternatives
### A1. Keep Revision 1's op-type calibration table ### A1. Keep Revision 1's op-type calibration table
Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

@@ -1,13 +1,13 @@
buffer_kind,sip_topology,n_sips,n_elem,bytes_per_pe,latency_ns buffer_kind,sip_topology,n_sips,n_elem,bytes_per_pe,latency_ns
hbm,torus_2d,6,128,256,2120.040000000012 hbm,torus_2d,6,128,256,2345.040000000012
hbm,torus_2d,6,1024,2048,2717.2783333333473 hbm,torus_2d,6,1024,2048,2942.2783333333473
hbm,torus_2d,6,8192,16384,7315.184999999989 hbm,torus_2d,6,8192,16384,7540.184999999989
hbm,torus_2d,6,32768,65536,23081.26500000037 hbm,torus_2d,6,32768,65536,23306.26500000037
sram,torus_2d,6,128,256,2060.040000000012 sram,torus_2d,6,128,256,2285.040000000012
sram,torus_2d,6,1024,2048,2909.2783333333473 sram,torus_2d,6,1024,2048,3134.2783333333527
sram,torus_2d,6,8192,16384,9523.184999999869 sram,torus_2d,6,8192,16384,9748.184999999869
sram,torus_2d,6,32768,65536,32201.265000000385 sram,torus_2d,6,32768,65536,32426.265000000385
tcm,torus_2d,6,128,256,1964.040000000012 tcm,torus_2d,6,128,256,2189.040000000012
tcm,torus_2d,6,1024,2048,2477.2783333333473 tcm,torus_2d,6,1024,2048,2702.2783333333473
tcm,torus_2d,6,8192,16384,6403.185000000109 tcm,torus_2d,6,8192,16384,6628.18500000005
tcm,torus_2d,6,32768,65536,19865.265000000378 tcm,torus_2d,6,32768,65536,20090.265000000378
1 buffer_kind sip_topology n_sips n_elem bytes_per_pe latency_ns
2 hbm torus_2d 6 128 256 2120.040000000012 2345.040000000012
3 hbm torus_2d 6 1024 2048 2717.2783333333473 2942.2783333333473
4 hbm torus_2d 6 8192 16384 7315.184999999989 7540.184999999989
5 hbm torus_2d 6 32768 65536 23081.26500000037 23306.26500000037
6 sram torus_2d 6 128 256 2060.040000000012 2285.040000000012
7 sram torus_2d 6 1024 2048 2909.2783333333473 3134.2783333333527
8 sram torus_2d 6 8192 16384 9523.184999999869 9748.184999999869
9 sram torus_2d 6 32768 65536 32201.265000000385 32426.265000000385
10 tcm torus_2d 6 128 256 1964.040000000012 2189.040000000012
11 tcm torus_2d 6 1024 2048 2477.2783333333473 2702.2783333333473
12 tcm torus_2d 6 8192 16384 6403.185000000109 6628.18500000005
13 tcm torus_2d 6 32768 65536 19865.265000000378 20090.265000000378
Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 92 KiB

@@ -1,37 +1,37 @@
algorithm,sip_topology,n_sips,n_elem,bytes_per_pe,bytes_per_sip,latency_ns algorithm,sip_topology,n_sips,n_elem,bytes_per_pe,bytes_per_sip,latency_ns
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,8,16,256,2666.552500000015 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,8,16,256,2918.5525000000157
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,32,64,1024,2747.7400000000152 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,32,64,1024,2999.740000000016
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,64,128,2048,2855.990000000018 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,64,128,2048,3107.990000000019
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,128,256,4096,3072.490000000019 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,128,256,4096,3324.4900000000207
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,512,1024,16384,3337.1133333333582 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,512,1024,16384,3589.1133333333582
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,1024,2048,32768,3708.0333333333692 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,1024,2048,32768,3960.0333333333692
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,2048,4096,65536,4449.873333333393 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,2048,4096,65536,4701.873333333393
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,4096,8192,131072,5933.020000000124 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,4096,8192,131072,6185.020000000124
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,8192,16384,262144,8900.379999999863 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,8192,16384,262144,9152.379999999861
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,16384,32768,524288,14835.099999999224 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,16384,32768,524288,15087.099999999224
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,32768,65536,1048576,26704.540000000765 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,32768,65536,1048576,26956.540000000765
lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,49152,98304,1572864,38573.97999999701 lrab_hierarchical_allreduce,mesh_2d_no_wrap,6,49152,98304,1572864,38825.97999999701
lrab_hierarchical_allreduce,ring_1d,6,8,16,256,2365.255833333347 lrab_hierarchical_allreduce,ring_1d,6,8,16,256,2628.2558333333477
lrab_hierarchical_allreduce,ring_1d,6,32,64,1024,2436.9433333333473 lrab_hierarchical_allreduce,ring_1d,6,32,64,1024,2699.943333333348
lrab_hierarchical_allreduce,ring_1d,6,64,128,2048,2532.526666666683 lrab_hierarchical_allreduce,ring_1d,6,64,128,2048,2795.526666666683
lrab_hierarchical_allreduce,ring_1d,6,128,256,4096,2723.693333333349 lrab_hierarchical_allreduce,ring_1d,6,128,256,4096,2986.693333333351
lrab_hierarchical_allreduce,ring_1d,6,512,1024,16384,3048.635000000021 lrab_hierarchical_allreduce,ring_1d,6,512,1024,16384,3311.635000000021
lrab_hierarchical_allreduce,ring_1d,6,1024,2048,32768,3393.4016666666957 lrab_hierarchical_allreduce,ring_1d,6,1024,2048,32768,3656.4016666666957
lrab_hierarchical_allreduce,ring_1d,6,2048,4096,65536,4082.401666666714 lrab_hierarchical_allreduce,ring_1d,6,2048,4096,65536,4345.401666666714
lrab_hierarchical_allreduce,ring_1d,6,4096,8192,131072,5458.80166666677 lrab_hierarchical_allreduce,ring_1d,6,4096,8192,131072,5721.801666666768
lrab_hierarchical_allreduce,ring_1d,6,8192,16384,262144,8216.934999999943 lrab_hierarchical_allreduce,ring_1d,6,8192,16384,262144,8479.934999999887
lrab_hierarchical_allreduce,ring_1d,6,16384,32768,524288,13733.201666665835 lrab_hierarchical_allreduce,ring_1d,6,16384,32768,524288,13996.201666665835
lrab_hierarchical_allreduce,ring_1d,6,32768,65536,1048576,24765.73500000064 lrab_hierarchical_allreduce,ring_1d,6,32768,65536,1048576,25028.73500000064
lrab_hierarchical_allreduce,ring_1d,6,49152,98304,1572864,35798.268333331536 lrab_hierarchical_allreduce,ring_1d,6,49152,98304,1572864,36061.26833333154
lrab_hierarchical_allreduce,torus_2d,6,8,16,256,1700.6025000000095 lrab_hierarchical_allreduce,torus_2d,6,8,16,256,1925.6025000000104
lrab_hierarchical_allreduce,torus_2d,6,32,64,1024,1753.2900000000102 lrab_hierarchical_allreduce,torus_2d,6,32,64,1024,1978.290000000011
lrab_hierarchical_allreduce,torus_2d,6,64,128,2048,1823.540000000012 lrab_hierarchical_allreduce,torus_2d,6,64,128,2048,2048.540000000012
lrab_hierarchical_allreduce,torus_2d,6,128,256,4096,1964.040000000012 lrab_hierarchical_allreduce,torus_2d,6,128,256,4096,2189.040000000012
lrab_hierarchical_allreduce,torus_2d,6,512,1024,16384,2196.8183333333463 lrab_hierarchical_allreduce,torus_2d,6,512,1024,16384,2421.8183333333463
lrab_hierarchical_allreduce,torus_2d,6,1024,2048,32768,2477.2783333333473 lrab_hierarchical_allreduce,torus_2d,6,1024,2048,32768,2702.2783333333473
lrab_hierarchical_allreduce,torus_2d,6,2048,4096,65536,3038.1983333333583 lrab_hierarchical_allreduce,torus_2d,6,2048,4096,65536,3263.1983333333583
lrab_hierarchical_allreduce,torus_2d,6,4096,8192,131072,4159.5050000000665 lrab_hierarchical_allreduce,torus_2d,6,4096,8192,131072,4384.5050000000665
lrab_hierarchical_allreduce,torus_2d,6,8192,16384,262144,6403.185000000109 lrab_hierarchical_allreduce,torus_2d,6,8192,16384,262144,6628.18500000005
lrab_hierarchical_allreduce,torus_2d,6,16384,32768,524288,10890.5449999995 lrab_hierarchical_allreduce,torus_2d,6,16384,32768,524288,11115.5449999995
lrab_hierarchical_allreduce,torus_2d,6,32768,65536,1048576,19865.265000000378 lrab_hierarchical_allreduce,torus_2d,6,32768,65536,1048576,20090.265000000378
lrab_hierarchical_allreduce,torus_2d,6,49152,98304,1572864,28839.98500000059 lrab_hierarchical_allreduce,torus_2d,6,49152,98304,1572864,29064.985000000597
1 algorithm sip_topology n_sips n_elem bytes_per_pe bytes_per_sip latency_ns
2 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 8 16 256 2666.552500000015 2918.5525000000157
3 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 32 64 1024 2747.7400000000152 2999.740000000016
4 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 64 128 2048 2855.990000000018 3107.990000000019
5 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 128 256 4096 3072.490000000019 3324.4900000000207
6 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 512 1024 16384 3337.1133333333582 3589.1133333333582
7 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 1024 2048 32768 3708.0333333333692 3960.0333333333692
8 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 2048 4096 65536 4449.873333333393 4701.873333333393
9 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 4096 8192 131072 5933.020000000124 6185.020000000124
10 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 8192 16384 262144 8900.379999999863 9152.379999999861
11 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 16384 32768 524288 14835.099999999224 15087.099999999224
12 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 32768 65536 1048576 26704.540000000765 26956.540000000765
13 lrab_hierarchical_allreduce mesh_2d_no_wrap 6 49152 98304 1572864 38573.97999999701 38825.97999999701
14 lrab_hierarchical_allreduce ring_1d 6 8 16 256 2365.255833333347 2628.2558333333477
15 lrab_hierarchical_allreduce ring_1d 6 32 64 1024 2436.9433333333473 2699.943333333348
16 lrab_hierarchical_allreduce ring_1d 6 64 128 2048 2532.526666666683 2795.526666666683
17 lrab_hierarchical_allreduce ring_1d 6 128 256 4096 2723.693333333349 2986.693333333351
18 lrab_hierarchical_allreduce ring_1d 6 512 1024 16384 3048.635000000021 3311.635000000021
19 lrab_hierarchical_allreduce ring_1d 6 1024 2048 32768 3393.4016666666957 3656.4016666666957
20 lrab_hierarchical_allreduce ring_1d 6 2048 4096 65536 4082.401666666714 4345.401666666714
21 lrab_hierarchical_allreduce ring_1d 6 4096 8192 131072 5458.80166666677 5721.801666666768
22 lrab_hierarchical_allreduce ring_1d 6 8192 16384 262144 8216.934999999943 8479.934999999887
23 lrab_hierarchical_allreduce ring_1d 6 16384 32768 524288 13733.201666665835 13996.201666665835
24 lrab_hierarchical_allreduce ring_1d 6 32768 65536 1048576 24765.73500000064 25028.73500000064
25 lrab_hierarchical_allreduce ring_1d 6 49152 98304 1572864 35798.268333331536 36061.26833333154
26 lrab_hierarchical_allreduce torus_2d 6 8 16 256 1700.6025000000095 1925.6025000000104
27 lrab_hierarchical_allreduce torus_2d 6 32 64 1024 1753.2900000000102 1978.290000000011
28 lrab_hierarchical_allreduce torus_2d 6 64 128 2048 1823.540000000012 2048.540000000012
29 lrab_hierarchical_allreduce torus_2d 6 128 256 4096 1964.040000000012 2189.040000000012
30 lrab_hierarchical_allreduce torus_2d 6 512 1024 16384 2196.8183333333463 2421.8183333333463
31 lrab_hierarchical_allreduce torus_2d 6 1024 2048 32768 2477.2783333333473 2702.2783333333473
32 lrab_hierarchical_allreduce torus_2d 6 2048 4096 65536 3038.1983333333583 3263.1983333333583
33 lrab_hierarchical_allreduce torus_2d 6 4096 8192 131072 4159.5050000000665 4384.5050000000665
34 lrab_hierarchical_allreduce torus_2d 6 8192 16384 262144 6403.185000000109 6628.18500000005
35 lrab_hierarchical_allreduce torus_2d 6 16384 32768 524288 10890.5449999995 11115.5449999995
36 lrab_hierarchical_allreduce torus_2d 6 32768 65536 1048576 19865.265000000378 20090.265000000378
37 lrab_hierarchical_allreduce torus_2d 6 49152 98304 1572864 28839.98500000059 29064.985000000597
Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

+80 -80
View File
@@ -1,81 +1,81 @@
hop,label,size_bytes,path,total_ns hop,label,size_bytes,path,total_ns
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),128,ipcq,24.88749999999891 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),128,ipcq,42.88749999999891
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),128,raw,33.57999999999811 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),128,raw,51.57999999999811
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),256,ipcq,28.13749999999891 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),256,ipcq,46.13749999999891
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),256,raw,36.07999999999811 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),256,raw,54.07999999999811
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),384,ipcq,29.88749999999891 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),384,ipcq,47.88749999999891
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),384,raw,37.07999999999811 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),384,raw,55.07999999999811
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),512,ipcq,31.63749999999891 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),512,ipcq,49.63749999999891
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),512,raw,38.07999999999811 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),512,raw,56.07999999999811
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),768,ipcq,35.13749999999891 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),768,ipcq,53.13749999999891
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),768,raw,40.07999999999811 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),768,raw,58.07999999999811
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),1024,ipcq,38.63749999999891 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),1024,ipcq,56.63749999999891
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),1024,raw,42.07999999999811 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),1024,raw,60.07999999999811
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),2048,ipcq,52.63749999999891 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),2048,ipcq,70.63749999999891
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),2048,raw,50.07999999999811 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),2048,raw,68.07999999999811
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),4096,ipcq,80.63750000000073 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),4096,ipcq,98.63750000000073
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),4096,raw,66.08000000000175 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),4096,raw,84.08000000000175
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),8192,ipcq,136.63750000000073 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),8192,ipcq,154.63750000000073
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),8192,raw,98.08000000000175 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),8192,raw,116.08000000000175
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),10240,ipcq,164.63750000000073 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),10240,ipcq,182.63750000000073
latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),10240,raw,114.08000000000175 latency_intracube_PE0_to_PE1_horizontal,Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal),10240,raw,132.08000000000175
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),128,ipcq,38.49749999999585 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),128,ipcq,56.49749999999585
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),128,raw,47.18999999999505 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),128,raw,65.18999999999505
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),256,ipcq,43.24749999999585 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),256,ipcq,61.24749999999585
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),256,raw,51.18999999999505 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),256,raw,69.18999999999505
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),384,ipcq,44.99749999999585 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),384,ipcq,62.99749999999585
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),384,raw,52.18999999999505 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),384,raw,70.18999999999505
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),512,ipcq,46.74749999999585 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),512,ipcq,64.74749999999585
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),512,raw,53.18999999999505 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),512,raw,71.18999999999505
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),768,ipcq,50.24749999999585 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),768,ipcq,68.24749999999585
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),768,raw,55.18999999999505 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),768,raw,73.18999999999505
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),1024,ipcq,53.74749999999585 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),1024,ipcq,71.74749999999585
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),1024,raw,57.18999999999505 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),1024,raw,75.18999999999505
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),2048,ipcq,67.74749999999585 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),2048,ipcq,85.74749999999585
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),2048,raw,65.18999999999505 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),2048,raw,83.18999999999505
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),4096,ipcq,95.74750000000131 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),4096,ipcq,113.74750000000131
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),4096,raw,81.19000000000233 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),4096,raw,99.19000000000233
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),8192,ipcq,151.7475000000013 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),8192,ipcq,169.7475000000013
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),8192,raw,113.19000000000233 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),8192,raw,131.19000000000233
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),10240,ipcq,179.7475000000013 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),10240,ipcq,197.7475000000013
latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),10240,raw,129.19000000000233 latency_intracube_PE0_to_PE4_vertical,Intra-cube PE-to-PE latency: PE0 → PE4 (vertical),10240,raw,147.19000000000233
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),128,ipcq,81.15999999999804 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),128,ipcq,99.15999999999804
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),128,raw,89.28999999999724 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),128,raw,107.28999999999724
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),256,ipcq,88.65999999999804 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),256,ipcq,106.65999999999804
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),256,raw,95.53999999999724 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),256,raw,113.53999999999724
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),384,ipcq,90.90999999999804 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),384,ipcq,108.90999999999804
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),384,raw,96.53999999999724 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),384,raw,114.53999999999724
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),512,ipcq,93.15999999999804 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),512,ipcq,111.15999999999804
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),512,raw,97.53999999999724 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),512,raw,115.53999999999724
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),768,ipcq,97.65999999999804 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),768,ipcq,115.65999999999804
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),768,raw,99.53999999999724 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),768,raw,117.53999999999724
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),1024,ipcq,103.15999999999804 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),1024,ipcq,121.15999999999804
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),1024,raw,102.53999999999724 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),1024,raw,120.53999999999724
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),2048,ipcq,125.15999999999804 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),2048,ipcq,143.15999999999804
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),2048,raw,114.53999999999724 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),2048,raw,132.53999999999724
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),4096,ipcq,169.15999999999985 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),4096,ipcq,187.15999999999985
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),4096,raw,138.54000000000087 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),4096,raw,156.54000000000087
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),8192,ipcq,257.15999999999985 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),8192,ipcq,275.15999999999985
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),8192,raw,186.54000000000087 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),8192,raw,204.54000000000087
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),10240,ipcq,301.15999999999985 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),10240,ipcq,319.15999999999985
latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),10240,raw,210.54000000000087 latency_intercube_C0PE0_to_C1PE0_horizontal,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal),10240,raw,228.54000000000087
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),128,ipcq,103.15999999999804 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),128,ipcq,121.15999999999804
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),128,raw,111.28999999999724 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),128,raw,129.28999999999724
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),256,ipcq,112.65999999999804 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),256,ipcq,130.65999999999804
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),256,raw,119.53999999999724 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),256,raw,137.53999999999724
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),384,ipcq,114.90999999999804 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),384,ipcq,132.90999999999804
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),384,raw,120.53999999999724 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),384,raw,138.53999999999724
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),512,ipcq,117.15999999999804 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),512,ipcq,135.15999999999804
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),512,raw,121.53999999999724 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),512,raw,139.53999999999724
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),768,ipcq,121.65999999999804 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),768,ipcq,139.65999999999804
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),768,raw,123.53999999999724 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),768,raw,141.53999999999724
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),1024,ipcq,127.15999999999804 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),1024,ipcq,145.15999999999804
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),1024,raw,126.53999999999724 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),1024,raw,144.53999999999724
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),2048,ipcq,149.15999999999804 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),2048,ipcq,167.15999999999804
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),2048,raw,138.53999999999724 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),2048,raw,156.53999999999724
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),4096,ipcq,193.15999999999985 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),4096,ipcq,211.15999999999985
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),4096,raw,162.54000000000087 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),4096,raw,180.54000000000087
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),8192,ipcq,281.15999999999985 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),8192,ipcq,299.15999999999985
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),8192,raw,210.54000000000087 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),8192,raw,228.54000000000087
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),10240,ipcq,325.15999999999985 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),10240,ipcq,343.15999999999985
latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),10240,raw,234.54000000000087 latency_intercube_C0PE0_to_C4PE0_vertical,Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical),10240,raw,252.54000000000087
1 hop label size_bytes path total_ns
2 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 128 ipcq 24.88749999999891 42.88749999999891
3 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 128 raw 33.57999999999811 51.57999999999811
4 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 256 ipcq 28.13749999999891 46.13749999999891
5 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 256 raw 36.07999999999811 54.07999999999811
6 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 384 ipcq 29.88749999999891 47.88749999999891
7 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 384 raw 37.07999999999811 55.07999999999811
8 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 512 ipcq 31.63749999999891 49.63749999999891
9 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 512 raw 38.07999999999811 56.07999999999811
10 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 768 ipcq 35.13749999999891 53.13749999999891
11 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 768 raw 40.07999999999811 58.07999999999811
12 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 1024 ipcq 38.63749999999891 56.63749999999891
13 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 1024 raw 42.07999999999811 60.07999999999811
14 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 2048 ipcq 52.63749999999891 70.63749999999891
15 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 2048 raw 50.07999999999811 68.07999999999811
16 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 4096 ipcq 80.63750000000073 98.63750000000073
17 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 4096 raw 66.08000000000175 84.08000000000175
18 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 8192 ipcq 136.63750000000073 154.63750000000073
19 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 8192 raw 98.08000000000175 116.08000000000175
20 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 10240 ipcq 164.63750000000073 182.63750000000073
21 latency_intracube_PE0_to_PE1_horizontal Intra-cube PE-to-PE latency: PE0 → PE1 (horizontal) 10240 raw 114.08000000000175 132.08000000000175
22 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 128 ipcq 38.49749999999585 56.49749999999585
23 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 128 raw 47.18999999999505 65.18999999999505
24 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 256 ipcq 43.24749999999585 61.24749999999585
25 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 256 raw 51.18999999999505 69.18999999999505
26 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 384 ipcq 44.99749999999585 62.99749999999585
27 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 384 raw 52.18999999999505 70.18999999999505
28 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 512 ipcq 46.74749999999585 64.74749999999585
29 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 512 raw 53.18999999999505 71.18999999999505
30 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 768 ipcq 50.24749999999585 68.24749999999585
31 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 768 raw 55.18999999999505 73.18999999999505
32 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 1024 ipcq 53.74749999999585 71.74749999999585
33 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 1024 raw 57.18999999999505 75.18999999999505
34 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 2048 ipcq 67.74749999999585 85.74749999999585
35 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 2048 raw 65.18999999999505 83.18999999999505
36 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 4096 ipcq 95.74750000000131 113.74750000000131
37 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 4096 raw 81.19000000000233 99.19000000000233
38 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 8192 ipcq 151.7475000000013 169.7475000000013
39 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 8192 raw 113.19000000000233 131.19000000000233
40 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 10240 ipcq 179.7475000000013 197.7475000000013
41 latency_intracube_PE0_to_PE4_vertical Intra-cube PE-to-PE latency: PE0 → PE4 (vertical) 10240 raw 129.19000000000233 147.19000000000233
42 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 128 ipcq 81.15999999999804 99.15999999999804
43 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 128 raw 89.28999999999724 107.28999999999724
44 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 256 ipcq 88.65999999999804 106.65999999999804
45 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 256 raw 95.53999999999724 113.53999999999724
46 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 384 ipcq 90.90999999999804 108.90999999999804
47 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 384 raw 96.53999999999724 114.53999999999724
48 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 512 ipcq 93.15999999999804 111.15999999999804
49 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 512 raw 97.53999999999724 115.53999999999724
50 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 768 ipcq 97.65999999999804 115.65999999999804
51 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 768 raw 99.53999999999724 117.53999999999724
52 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 1024 ipcq 103.15999999999804 121.15999999999804
53 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 1024 raw 102.53999999999724 120.53999999999724
54 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 2048 ipcq 125.15999999999804 143.15999999999804
55 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 2048 raw 114.53999999999724 132.53999999999724
56 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 4096 ipcq 169.15999999999985 187.15999999999985
57 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 4096 raw 138.54000000000087 156.54000000000087
58 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 8192 ipcq 257.15999999999985 275.15999999999985
59 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 8192 raw 186.54000000000087 204.54000000000087
60 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 10240 ipcq 301.15999999999985 319.15999999999985
61 latency_intercube_C0PE0_to_C1PE0_horizontal Inter-cube PE-to-PE latency: Cube0.PE0 → Cube1.PE0 (horizontal) 10240 raw 210.54000000000087 228.54000000000087
62 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 128 ipcq 103.15999999999804 121.15999999999804
63 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 128 raw 111.28999999999724 129.28999999999724
64 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 256 ipcq 112.65999999999804 130.65999999999804
65 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 256 raw 119.53999999999724 137.53999999999724
66 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 384 ipcq 114.90999999999804 132.90999999999804
67 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 384 raw 120.53999999999724 138.53999999999724
68 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 512 ipcq 117.15999999999804 135.15999999999804
69 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 512 raw 121.53999999999724 139.53999999999724
70 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 768 ipcq 121.65999999999804 139.65999999999804
71 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 768 raw 123.53999999999724 141.53999999999724
72 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 1024 ipcq 127.15999999999804 145.15999999999804
73 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 1024 raw 126.53999999999724 144.53999999999724
74 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 2048 ipcq 149.15999999999804 167.15999999999804
75 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 2048 raw 138.53999999999724 156.53999999999724
76 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 4096 ipcq 193.15999999999985 211.15999999999985
77 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 4096 raw 162.54000000000087 180.54000000000087
78 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 8192 ipcq 281.15999999999985 299.15999999999985
79 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 8192 raw 210.54000000000087 228.54000000000087
80 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 10240 ipcq 325.15999999999985 343.15999999999985
81 latency_intercube_C0PE0_to_C4PE0_vertical Inter-cube PE-to-PE latency: Cube0.PE0 → Cube4.PE0 (vertical) 10240 raw 234.54000000000087 252.54000000000087
@@ -0,0 +1,848 @@
# AHBM Agentic Runtime Architecture
## Scope
This note organizes the current design discussion for executing agentic workloads on a memory-centric AHBM-style architecture.
The scope of this version is limited to:
1. Motivation
2. AHBM hardware assumptions
3. Attention execution
4. Attention execution policies
5. Fan-in and join
MoE execution and whole-model layer-aware scheduling are intentionally deferred to a later section.
---
# 1. Motivation
Agentic workload patterns: loop, fan-out / fan-in. Loop 은 일반적인 LLM execution 과 다르지 않다. Sub-agent 가 만들어질 때 발생하는 Fan out / Fan in 이 주로 고려되어야 할 대상이다.
## 1.1 Agentic fan-out
An agentic workload often starts from one shared conversation or task context and then forks into several specialized sub-agents.
```text
Shared prefix
├─ Agent A: Analyze performance
├─ Agent B: Check correctness
└─ Agent C: Find alternatives
```
Each sub-agent sees:
```text
Shared prefix
+
Private role or instruction
+
Private generated continuation
```
The shared prefix may be long, while each private branch may initially contain only a small number of new tokens.
This creates two important execution properties:
1. All branches reuse the same prefix KV cache.
2. New query tokens from multiple branches can potentially be batched.
## 1.2 Why the workload is different from ordinary batching
Ordinary batching groups unrelated requests that happen to arrive at similar times.
Agentic fan-out is different because the branches are structurally related:
```text
Agent A context = Shared prefix + A suffix
Agent B context = Shared prefix + B suffix
Agent C context = Shared prefix + C suffix
```
The requests therefore have:
- identical prefix KV,
- different private suffix KV,
- potentially synchronized execution points,
- a later fan-in stage that combines their results.
This structure creates opportunities that are not available in unrelated-request batching.
## 1.3 Main optimization opportunity
For attention, the key operation is:
```text
Q × Kᵀ
```
Multiple sub-agents can have different query rows while reading the same shared prefix KV.
Instead of executing:
```text
Q_A × K_shared
Q_B × K_shared
Q_C × K_shared
```
independently, the runtime can combine the query rows:
```text
Q_combined =
[
Q_A
Q_B
Q_C
]
```
and execute:
```text
Q_combined × K_shared
```
The arithmetic is still row-independent, but the larger GEMM can improve utilization and amortize scheduling overhead.
## 1.4 Main design questions
The architecture must answer:
- How should shared KV be distributed across CUBEs and PEs?
- How should query rows from multiple sub-agents be grouped?
- How should online softmax state be reduced across sequence shards?
- Should Q be replicated or partitioned?
- How should large fan-out results be joined back into the main agent?
- Which data should be reused, recomputed, summarized, or materialized on demand?
---
# 2. AHBM Hardware Assumptions
## 2.2 Sequence parallelism
The sequence dimension of one KV head is distributed across the 32 PEs.
```text
KV head sequence
├─ PE0 owns sequence shard 0
├─ PE1 owns sequence shard 1
├─ ...
└─ PE31 owns sequence shard 31
```
Each PE stores a different part of the sequence for the same KV head. A query row must attend to all 32 sequence shards, so every PE computes a partial attention result for its local KV shard.
## 2.3 Q placement
The baseline follows an AHBM-style replicated-Q execution model.
Logically:
```text
The same Q rows are visible to all 32 PEs.
```
This does not require 32 independent physical copies. A possible implementation is:
```text
Chip-level Q source
CUBE multicast
Shared Q buffer within each CUBE
8 PEs consume the same Q tile
```
Thus Q is logically replicated across PEs while physical traffic is reduced through multicast and shared buffering.
## 2.4 KV placement
KV remains stationary near the owning PE.
```text
PE0 → KV shard 0
PE1 → KV shard 1
...
PE31 → KV shard 31
```
The baseline avoids:
- KV remapping,
- KV replication,
- remote KV reads,
- page-table reconstruction for every query group.
## 2.5 GEMM engine assumptions
Representative PE GEMM tile shapes include:
```text
16 × 16 × 16
```
and:
```text
8 × 64 × 8
```
For:
```text
8 sub-agents
20 query tokens per sub-agent
```
the combined number of Q rows is:
```text
M = 8 × 20 = 160
```
If each PE owns 256 KV columns, a representative local GEMM is:
```text
Q_local: 160 × d
K_local: d × 256
```
Both `M = 160` and `N = 256` align well with the assumed tile shapes.
---
# 3. Attention Execution
## 3.1 Fan-out context structure
Assume the parent agent has already prefetched a shared prefix.
```text
Shared prefix KV pages
```
The runtime forks the logical context:
```text
Branch A page table:
[Shared prefix pages] + [A private pages]
Branch B page table:
[Shared prefix pages] + [B private pages]
Branch C page table:
[Shared prefix pages] + [C private pages]
```
The shared pages are referenced by multiple branches without being copied. Only private suffix pages are branch-specific.
## 3.2 Sub-agent query batching
Assume:
```text
8 sub-agents
20 new tokens per sub-agent
```
The runtime forms:
```text
Q_A: 20 × d
Q_B: 20 × d
...
Q_H: 20 × d
```
and concatenates them along the row dimension:
```text
Q_combined: 160 × d
```
The combined operation is:
```text
(160 × d) × (d × S)
```
where `S` is the total sequence length represented by all KV shards.
Each query row remains logically independent. Batching changes the execution shape, not the attention semantics.
## 3.3 Per-PE local attention
Each PE owns only a local sequence shard.
For PE `p`:
```text
Scores_p = Q_combined × K_pᵀ
```
The PE then computes local online-softmax state:
```text
m_p[row]
l_p[row]
o_p[row, :]
```
For 160 rows, each PE conceptually produces:
```text
m_p[160]
l_p[160]
o_p[160, d_v]
```
These may be processed in smaller row tiles for pipelining.
## 3.4 Online softmax merge
Each row has an independent online-softmax state.
The reduction is always:
```text
same row index across different sequence shards
```
It is never:
```text
different query rows reduced together
```
Therefore 160 query rows do not imply 160 serialized communication rounds. The implementation exchanges vector or tiled payloads such as:
```text
m[160]
l[160]
o[160, d_v]
```
or:
```text
m[16]
l[16]
o[16, d_v]
```
The row states can be communicated and merged in parallel.
## 3.5 Hierarchical reduction
Because one KV head spans four CUBEs and 32 PEs, reduction is hierarchical.
```text
32 PE partial states
8-PE reduction inside each CUBE
4 CUBE-level states
4-CUBE reduction
Final attention outputs
```
The same online-softmax merge primitive is used at both levels.
## 3.6 Prefill versus decode
### Decode
Decode typically has a very small number of Q rows.
```text
small Q
↓ multicast
32 PEs read local KV shards
hierarchical reduction
```
The dominant concerns are usually KV bandwidth and reduction overhead.
### Prefill
Fan-out can make the query dimension much larger.
```text
Per-agent Q rows: 20
Number of agents: 8
Combined Q rows: 160
```
The larger `M` dimension can produce a much better GEMM shape. Agentic batching is therefore especially attractive for prefill or multi-token private suffix processing.
## 3.7 Compute-cost clarification
Replicating Q across 32 PEs does not multiply total attention FLOPs by 32. Each PE computes a different KV-column region.
```text
32 PEs × 160 rows × 256 local columns
=
160 rows × 8192 total columns
```
If Q is temporally tiled into four groups of 40 rows:
```text
4 × 32 PEs × 40 rows × 256 columns
=
160 rows × 8192 columns
```
The total arithmetic is identical. The policy changes Q traffic, reduction traffic, scheduling granularity, utilization, and buffering—not the mathematical amount of attention work.
---
# 4. Attention Execution Policies
## 4.1 Policy A: Replicated Q, stationary KV
Policy A keeps the existing KV placement unchanged.
```text
Q_combined
↓ multicast
PE0 computes against KV shard 0
PE1 computes against KV shard 1
...
PE31 computes against KV shard 31
```
Each PE executes a local GEMM such as:
```text
(160 × d) × (d × 256)
```
### Advantages
- No KV movement
- No KV replication
- No remote KV access
- No page-table regrouping
- Natural compatibility with sequence-parallel attention
- Large local GEMM shapes
- Simple hierarchical softmax reduction
### Costs
- Q must be distributed to all CUBEs
- Every row requires a 32-way logical reduction
- Large Q batches increase multicast and softmax-state traffic
Policy A is the natural baseline for the assumed AHBM mapping.
## 4.2 Policy B: Partitioned Q groups
A possible alternative is to divide query rows among PE groups.
```text
Q group 0 → PE group 0
Q group 1 → PE group 1
...
```
However, every query row must still attend to the complete KV sequence. Because the 32 PEs already represent 32 sequence shards, spatially partitioning Q means each Q group must somehow access all KV shards.
This requires one of the following:
1. Regroup KV shards for each Q group.
2. Read remote KV through symmetric memory.
3. Replicate KV across Q-processing groups.
4. Time-multiplex the same PEs over Q groups.
The first three add memory-system complexity. The fourth is mainly temporal tiling and does not provide true spatial Q partitioning.
## 4.3 Why Policy B is not automatically better
The comparison is:
```text
Policy A cost
=
Q multicast
+
hierarchical reduction
```
versus:
```text
Policy B cost
=
KV regrouping, replication, or remote access
+
additional scheduling complexity
```
Policy B becomes attractive only if:
```text
Q distribution cost + reduction cost
>
remote or regrouped KV cost
```
Relevant factors include Q size, multicast bandwidth, reduction bandwidth, symmetric-memory bandwidth, remote-KV latency, KV replication capacity, page-table overhead, and GEMM utilization.
## 4.4 Recommended baseline
For:
```text
1 KV head = 4 CUBEs = 32 PEs
```
use:
```text
Policy A:
replicated Q + stationary sequence-sharded KV
```
Policy B should be treated as an adaptive or future policy for cases where Q batches become extremely large, multicast becomes a bottleneck, reduction traffic dominates, or remote KV access becomes inexpensive.
## 4.5 Runtime decision model
A future runtime can estimate:
```text
T_A =
T_Q_multicast
+
T_local_GEMM
+
T_hierarchical_reduce
```
and:
```text
T_B =
T_Q_partition
+
T_remote_or_replicated_KV
+
T_local_GEMM
+
T_group_reduce
+
T_remap
```
The runtime selects the lower-cost policy for the current sequence length, agent count, Q size, KV-head mapping, bandwidth state, and interconnect congestion.
---
# 5. Fan-In and Join
## 5.1 Fan-in problem
After fan-out, each sub-agent produces a private result.
```text
Agent A → result A
Agent B → result B
Agent C → result C
```
The main agent must synthesize these results into a final continuation.
## 5.2 Why branch KV cannot be concatenated
Each branch has a different causal token history.
```text
A history:
Shared prefix + A instruction + A reasoning
B history:
Shared prefix + B instruction + B reasoning
C history:
Shared prefix + C instruction + C reasoning
```
The K/V tensors of a token depend on token content, position, preceding causal context, and every transformer layer.
Therefore:
```text
A private KV
+
B private KV
+
C private KV
```
does not form the KV cache of any valid single token sequence.
## 5.3 Baseline text join
The standard framework behavior is:
```text
Sub-agent result text
Framework gathers and formats results
Main-agent join prompt
Continuation prefill
New main-agent KV suffix
```
The framework normally aggregates the inputs, while the main LLM performs final synthesis.
## 5.4 Main-agent KV after join
The main-agent page table becomes:
```text
[Shared prefix KV pages]
+
[Join-input KV pages]
+
[Main continuation KV pages]
```
The private A/B/C pages remain separate and are not attached directly.
## 5.5 Cost of full-text gather
Assume:
```text
8 sub-agents
1000 output tokens per agent
```
A full-text join creates:
```text
8000 join-input tokens
```
These tokens must pass through all transformer layers during continuation prefill. This increases prefill work, KV allocation, future decode KV reads, and context-window occupancy.
## 5.6 Schema-constrained join
A practical optimization is to constrain each sub-agent to a compact result schema.
```json
{
"claim": "memory_bandwidth_bottleneck",
"confidence": 0.91,
"evidence_ids": [17, 24]
}
```
The framework can serialize it compactly:
```text
Finding: memory bandwidth bottleneck
Confidence: 0.91
Evidence: E17, E24
```
Example:
```text
Before:
8 × 1000 = 8000 tokens
After:
8 × 50 = 400 tokens
```
The main LLM still performs final reasoning, but the continuation prefill is much shorter.
## 5.7 Deduplication and aggregation
Different sub-agents may produce overlapping findings.
```json
[
{"claim": "memory_bw", "confidence": 0.91},
{"claim": "memory_bw", "confidence": 0.87},
{"claim": "compute", "confidence": 0.42}
]
```
The framework can combine duplicates:
```text
Primary finding:
- Memory-bandwidth bottleneck
- Supported by 2 agents
- Maximum confidence: 0.91
Alternative:
- Compute bottleneck
- Confidence: 0.42
```
This is preprocessing, not final reasoning.
## 5.8 Pointer-based join
Detailed evidence can remain outside the initial main-agent context.
```text
Agent A:
- Finding: memory-bandwidth bottleneck
- Evidence handle: E17
Agent B:
- Finding: reduction error
- Evidence handle: E24
```
The main agent initially receives only summaries and handles. The framework retrieves and appends evidence only when requested.
```text
Effective input
=
summary tokens
+
tokens for evidence actually used
```
The transformer does not directly dereference the handle; the framework resolves it through retrieval or a tool call.
## 5.9 Hierarchical reduction
For large fan-out width, local reducer agents can summarize groups of branches.
```text
16 sub-agents
4 local reducers
4 summaries
Main agent
```
Benefits:
- Reducers operate in parallel.
- The final join prompt is shorter.
- Main-agent KV growth is smaller.
- Fan-in traffic is organized hierarchically.
Costs:
- Additional reducer inference
- Possible loss of detail
- Need for fallback evidence retrieval
## 5.10 Latent-state join
A more aggressive approach is to replace long text outputs with learned latent representations.
```text
Sub-agent hidden states
Compression or projection
Small latent-token set
Main model
```
Example:
```text
1000 text tokens
16 latent tokens
```
This could reduce join prefill and KV growth, but it requires training the main model to consume latent tokens, aligning branch representations, defining causal and positional semantics, and validating accuracy. It is a model-system co-design direction rather than a drop-in runtime optimization.
## 5.11 KV and compute impact
The shared parent prefix KV is already present. The primary optimization target is the newly created join suffix.
```text
New main KV size
number of join-input tokens
```
Reducing join input reduces continuation-prefill work, new KV allocation, Q rows processed during join, later decode-time KV reads, and context-window consumption.
A practical flow is:
```text
Sub-agent full outputs
Schema-constrained results
Deduplication and ranking
Summaries + evidence handles
Selective evidence materialization
Main-agent continuation prefill
```
## 5.12 Recommended practical join design
For an unchanged LLaMA-style model on AHBM:
1. Reuse shared prefix KV through page-table references.
2. Keep branch-private KV isolated.
3. Require schema-constrained sub-agent outputs.
4. Deduplicate repeated claims in the framework.
5. Pass compact summaries, confidence values, and evidence handles.
6. Retrieve detailed evidence only when requested.
7. Introduce hierarchical reducer agents for wide fan-out.
8. Keep the main LLM responsible for final synthesis.
---
# Current Baseline Summary
```text
Shared prefix prefill
Shared KV page reuse
Agentic fan-out
Combine private Q rows
Replicated-Q attention over stationary sequence-sharded KV
Hierarchical online-softmax reduction
Independent private branch continuations
Schema/pointer-based fan-in
Main-agent continuation prefill
```
Main design principles:
- Reuse shared prefix KV without copying it.
- Batch sub-agent Q rows when they read the same KV.
- Keep KV stationary and multicast Q.
- Reduce softmax state hierarchically by matching row index.
- Do not directly merge branch-private KV.
- Reduce fan-in cost by shortening and selectively materializing join inputs.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 92 KiB

@@ -0,0 +1,312 @@
<svg xmlns="http://www.w3.org/2000/svg" width="860" height="798" viewBox="55 2 860 798">
<title>cube</title>
<rect width="970" height="900" fill="#ffffff"/>
<text x="485" y="22" text-anchor="middle" font-family="monospace" font-size="18" font-weight="bold" fill="#1f2937">CUBE TOPOLOGY — 17.0×14.0mm | 6×6 Router Mesh | n_to_one mode | 64 pseudo-ch</text>
<text x="485" y="40" text-anchor="middle" font-family="monospace" font-size="15" fill="#ffffff">Per-PE: 8 ch × 32.0 GB/s = 256.0 GB/s | Cube total: 64 × 32.0 = 2048.0 GB/s</text>
<rect x="60" y="60" width="850.0" height="700.0" rx="6" fill="none" stroke="#94a3b8" stroke-width="2" stroke-dasharray="8,4"/>
<rect x="260" y="285" width="450" height="250" rx="6" fill="#ecfdf5" stroke="#047857" stroke-width="2" opacity="0.6"/>
<text x="485" y="395" text-anchor="middle" font-family="monospace" font-size="16" font-weight="bold" fill="#047857">HBM_CTRL | 64 pseudo channels</text>
<text x="485" y="412" text-anchor="middle" font-family="monospace" font-size="14" fill="#059669">Total BW: 2048 GB/s</text>
<rect x="270.0" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="283.4" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="296.9" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="310.3" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="323.8" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="337.2" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="350.6" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="364.1" y="289" width="12.9" height="8" rx="1" fill="#3b82f6" opacity="0.8"/>
<rect x="377.5" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="390.9" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="404.4" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="417.8" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="431.2" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="444.7" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="458.1" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="471.6" y="289" width="12.9" height="8" rx="1" fill="#60a5fa" opacity="0.8"/>
<rect x="485.0" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="498.4" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="511.9" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="525.3" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="538.8" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="552.2" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="565.6" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="579.1" y="289" width="12.9" height="8" rx="1" fill="#475569" opacity="0.8"/>
<rect x="592.5" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<rect x="605.9" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<rect x="619.4" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<rect x="632.8" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<rect x="646.2" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<rect x="659.7" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<rect x="673.1" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<rect x="686.6" y="289" width="12.9" height="8" rx="1" fill="#94a3b8" opacity="0.8"/>
<text x="324" y="286" text-anchor="middle" font-family="monospace" font-size="12" fill="#3b82f6">PE0×8ch</text>
<text x="431" y="286" text-anchor="middle" font-family="monospace" font-size="12" fill="#60a5fa">PE1×8ch</text>
<text x="539" y="286" text-anchor="middle" font-family="monospace" font-size="12" fill="#475569">PE2×8ch</text>
<text x="646" y="286" text-anchor="middle" font-family="monospace" font-size="12" fill="#94a3b8">PE3×8ch</text>
<rect x="270.0" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="283.4" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="296.9" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="310.3" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="323.8" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="337.2" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="350.6" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="364.1" y="523" width="12.9" height="8" rx="1" fill="#f59e0b" opacity="0.8"/>
<rect x="377.5" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="390.9" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="404.4" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="417.8" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="431.2" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="444.7" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="458.1" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="471.6" y="523" width="12.9" height="8" rx="1" fill="#fbbf24" opacity="0.8"/>
<rect x="485.0" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="498.4" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="511.9" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="525.3" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="538.8" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="552.2" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="565.6" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="579.1" y="523" width="12.9" height="8" rx="1" fill="#ef4444" opacity="0.8"/>
<rect x="592.5" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<rect x="605.9" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<rect x="619.4" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<rect x="632.8" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<rect x="646.2" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<rect x="659.7" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<rect x="673.1" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<rect x="686.6" y="523" width="12.9" height="8" rx="1" fill="#f87171" opacity="0.8"/>
<text x="324" y="539" text-anchor="middle" font-family="monospace" font-size="12" fill="#f59e0b">PE4×8ch</text>
<text x="431" y="539" text-anchor="middle" font-family="monospace" font-size="12" fill="#fbbf24">PE5×8ch</text>
<text x="539" y="539" text-anchor="middle" font-family="monospace" font-size="12" fill="#ef4444">PE6×8ch</text>
<text x="646" y="539" text-anchor="middle" font-family="monospace" font-size="12" fill="#f87171">PE7×8ch</text>
<line x1="135" y1="135" x2="285" y2="135" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="135" x2="135" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="135" x2="435" y2="135" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="135" x2="285" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="435" y1="135" x2="585" y2="135" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="435" y1="135" x2="435" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="585" y1="135" x2="685" y2="135" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="585" y1="135" x2="585" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="135" x2="835" y2="135" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="135" x2="685" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="835" y1="135" x2="835" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="260" x2="285" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="260" x2="135" y2="335" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="260" x2="435" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="260" x2="285" y2="335" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="435" y1="260" x2="585" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="435" y1="260" x2="435" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="585" y1="260" x2="685" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="585" y1="260" x2="585" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="260" x2="835" y2="260" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="260" x2="685" y2="335" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="835" y1="260" x2="835" y2="335" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="335" x2="285" y2="335" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="335" x2="135" y2="485" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="335" x2="685" y2="335" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="335" x2="285" y2="485" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="335" x2="835" y2="335" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="335" x2="685" y2="485" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="835" y1="335" x2="835" y2="485" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="485" x2="285" y2="485" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="485" x2="135" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="485" x2="685" y2="485" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="485" x2="285" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="485" x2="835" y2="485" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="485" x2="685" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="835" y1="485" x2="835" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="560" x2="285" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="560" x2="135" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="560" x2="435" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="560" x2="285" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="435" y1="560" x2="585" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="435" y1="560" x2="435" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="585" y1="560" x2="685" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="585" y1="560" x2="585" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="560" x2="835" y2="560" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="560" x2="685" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="835" y1="560" x2="835" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="135" y1="685" x2="285" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="285" y1="685" x2="435" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="435" y1="685" x2="585" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="585" y1="685" x2="685" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<line x1="685" y1="685" x2="835" y2="685" stroke="#94a3b8" stroke-width="1" opacity="0.4"/>
<circle cx="135" cy="135" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="135" y="138" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r0c0</text>
<rect x="119" y="81" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="135" y="92" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE0</text>
<line x1="135" y1="127" x2="149" y2="97" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<circle cx="285" cy="135" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="285" y="138" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r0c1</text>
<rect x="269" y="81" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="285" y="92" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE1</text>
<line x1="285" y1="127" x2="299" y2="97" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<circle cx="435" cy="135" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="435" y="138" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r0c2</text>
<circle cx="585" cy="135" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="585" y="138" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r0c3</text>
<circle cx="685" cy="135" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="685" y="138" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r0c4</text>
<circle cx="835" cy="135" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="835" y="138" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r0c5</text>
<circle cx="135" cy="260" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="135" y="263" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r1c0</text>
<circle cx="285" cy="260" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="285" y="263" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r1c1</text>
<circle cx="435" cy="260" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="435" y="263" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r1c2</text>
<rect x="419" y="206" width="32" height="16" rx="3" fill="#ffffff" stroke="#f59e0b" stroke-width="1"/>
<text x="435" y="217" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#f59e0b">M_CPU</text>
<line x1="435" y1="252" x2="449" y2="222" stroke="#f59e0b" stroke-width="1" opacity="0.6"/>
<circle cx="585" cy="260" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="585" y="263" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r1c3</text>
<circle cx="685" cy="260" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="685" y="263" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r1c4</text>
<rect x="669" y="206" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="685" y="217" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE2</text>
<line x1="685" y1="252" x2="699" y2="222" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<circle cx="835" cy="260" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="835" y="263" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r1c5</text>
<rect x="819" y="206" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="835" y="217" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE3</text>
<line x1="835" y1="252" x2="849" y2="222" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<circle cx="135" cy="335" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="135" y="338" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r2c0</text>
<circle cx="285" cy="335" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="285" y="338" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r2c1</text>
<circle cx="685" cy="335" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="685" y="338" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r2c4</text>
<circle cx="835" cy="335" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="835" y="338" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r2c5</text>
<circle cx="135" cy="485" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="135" y="488" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r3c0</text>
<rect x="119" y="523" width="32" height="16" rx="3" fill="#ffffff" stroke="#d97706" stroke-width="1"/>
<text x="135" y="534" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#d97706">SRAM</text>
<line x1="135" y1="493" x2="149" y2="523" stroke="#d97706" stroke-width="1" opacity="0.6"/>
<circle cx="285" cy="485" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="285" y="488" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r3c1</text>
<circle cx="685" cy="485" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="685" y="488" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r3c4</text>
<circle cx="835" cy="485" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="835" y="488" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r3c5</text>
<circle cx="135" cy="560" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="135" y="563" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r4c0</text>
<rect x="119" y="598" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="135" y="609" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE4</text>
<line x1="135" y1="568" x2="149" y2="598" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<circle cx="285" cy="560" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="285" y="563" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r4c1</text>
<rect x="269" y="598" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="285" y="609" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE5</text>
<line x1="285" y1="568" x2="299" y2="598" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<circle cx="435" cy="560" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="435" y="563" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r4c2</text>
<circle cx="585" cy="560" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="585" y="563" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r4c3</text>
<circle cx="685" cy="560" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="685" y="563" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r4c4</text>
<circle cx="835" cy="560" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="835" y="563" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r4c5</text>
<circle cx="135" cy="685" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="135" y="688" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r5c0</text>
<circle cx="285" cy="685" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="285" y="688" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r5c1</text>
<circle cx="435" cy="685" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="435" y="688" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r5c2</text>
<circle cx="585" cy="685" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="585" y="688" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r5c3</text>
<circle cx="685" cy="685" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="685" y="688" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r5c4</text>
<rect x="669" y="723" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="685" y="734" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE6</text>
<line x1="685" y1="693" x2="699" y2="723" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<circle cx="835" cy="685" r="17" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<text x="835" y="688" text-anchor="middle" font-family="monospace" font-size="12" fill="#1f2937">r5c5</text>
<rect x="819" y="723" width="32" height="16" rx="3" fill="#ffffff" stroke="#a855f7" stroke-width="1"/>
<text x="835" y="734" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#a855f7">PE7</text>
<line x1="835" y1="693" x2="849" y2="723" stroke="#a855f7" stroke-width="1" opacity="0.6"/>
<polyline points="135,143 208,216 251,216 324,289" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="239" y="216" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<polyline points="285,143 358,216 358,216 431,289" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="368" y="216" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<polyline points="685,268 674,278 549,278 539,289" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="622" y="278" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<polyline points="835,268 824,278 657,278 646,289" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="751" y="278" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<polyline points="135,552 146,542 313,542 324,531" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="239" y="542" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<polyline points="285,552 296,542 421,542 431,531" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="368" y="542" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<polyline points="685,677 612,604 612,604 539,531" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="622" y="604" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<polyline points="835,677 762,604 719,604 646,531" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.6" stroke-dasharray="4,3"/>
<text x="751" y="604" font-family="monospace" font-size="12" fill="#047857">256GB/s</text>
<rect x="65" y="360" width="50" height="100" rx="3" fill="#ffffff" stroke="#475569" stroke-width="1.5" opacity="0.9"/>
<text x="90" y="357" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#475569">UCIe-W</text>
<rect x="67" y="362" width="46" height="23" rx="2" fill="#cbd5e1" opacity="0.7"/>
<text x="90" y="376" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c0</text>
<polyline points="127,135 120,142 120,366 113,374" fill="none" stroke="#cbd5e1" stroke-width="1" opacity="0.5"/>
<rect x="67" y="386" width="46" height="23" rx="2" fill="#94a3b8" opacity="0.7"/>
<text x="90" y="400" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c1</text>
<polyline points="127,260 120,267 120,390 113,398" fill="none" stroke="#94a3b8" stroke-width="1" opacity="0.5"/>
<rect x="67" y="410" width="46" height="23" rx="2" fill="#64748b" opacity="0.7"/>
<text x="90" y="424" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c2</text>
<polyline points="127,560 120,553 120,428 113,422" fill="none" stroke="#64748b" stroke-width="1" opacity="0.5"/>
<rect x="67" y="434" width="46" height="23" rx="2" fill="#374151" opacity="0.7"/>
<text x="90" y="448" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c3</text>
<polyline points="127,685 120,678 120,452 113,446" fill="none" stroke="#374151" stroke-width="1" opacity="0.5"/>
<rect x="435" y="65" width="100" height="50" rx="3" fill="#ffffff" stroke="#475569" stroke-width="1.5" opacity="0.9"/>
<text x="485" y="62" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#475569">UCIe-N</text>
<rect x="437" y="67" width="23" height="46" rx="2" fill="#cbd5e1" opacity="0.7"/>
<text x="448" y="93" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c0</text>
<polyline points="135,127 142,120 442,120 448,113" fill="none" stroke="#cbd5e1" stroke-width="1" opacity="0.5"/>
<rect x="461" y="67" width="23" height="46" rx="2" fill="#94a3b8" opacity="0.7"/>
<text x="472" y="93" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c1</text>
<polyline points="285,127 292,120 466,120 472,113" fill="none" stroke="#94a3b8" stroke-width="1" opacity="0.5"/>
<rect x="485" y="67" width="23" height="46" rx="2" fill="#64748b" opacity="0.7"/>
<text x="496" y="93" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c2</text>
<polyline points="685,127 678,120 504,120 496,113" fill="none" stroke="#64748b" stroke-width="1" opacity="0.5"/>
<rect x="509" y="67" width="23" height="46" rx="2" fill="#374151" opacity="0.7"/>
<text x="520" y="93" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c3</text>
<polyline points="835,127 828,120 528,120 520,113" fill="none" stroke="#374151" stroke-width="1" opacity="0.5"/>
<rect x="855" y="360" width="50" height="100" rx="3" fill="#ffffff" stroke="#475569" stroke-width="1.5" opacity="0.9"/>
<text x="880" y="357" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#475569">UCIe-E</text>
<rect x="857" y="362" width="46" height="23" rx="2" fill="#cbd5e1" opacity="0.7"/>
<text x="880" y="376" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c0</text>
<polyline points="843,135 850,142 850,367 857,374" fill="none" stroke="#cbd5e1" stroke-width="1" opacity="0.5"/>
<rect x="857" y="386" width="46" height="23" rx="2" fill="#94a3b8" opacity="0.7"/>
<text x="880" y="400" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c1</text>
<polyline points="843,260 850,267 850,391 857,398" fill="none" stroke="#94a3b8" stroke-width="1" opacity="0.5"/>
<rect x="857" y="410" width="46" height="23" rx="2" fill="#64748b" opacity="0.7"/>
<text x="880" y="424" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c2</text>
<polyline points="843,560 850,553 850,428 857,422" fill="none" stroke="#64748b" stroke-width="1" opacity="0.5"/>
<rect x="857" y="434" width="46" height="23" rx="2" fill="#374151" opacity="0.7"/>
<text x="880" y="448" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c3</text>
<polyline points="843,685 850,678 850,452 857,446" fill="none" stroke="#374151" stroke-width="1" opacity="0.5"/>
<rect x="435" y="705" width="100" height="50" rx="3" fill="#ffffff" stroke="#475569" stroke-width="1.5" opacity="0.9"/>
<text x="485" y="702" text-anchor="middle" font-family="monospace" font-size="14" font-weight="bold" fill="#475569">UCIe-S</text>
<rect x="437" y="707" width="23" height="46" rx="2" fill="#cbd5e1" opacity="0.7"/>
<text x="448" y="733" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c0</text>
<polyline points="135,693 142,700 442,700 448,707" fill="none" stroke="#cbd5e1" stroke-width="1" opacity="0.5"/>
<rect x="461" y="707" width="23" height="46" rx="2" fill="#94a3b8" opacity="0.7"/>
<text x="472" y="733" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c1</text>
<polyline points="285,693 292,700 466,700 472,707" fill="none" stroke="#94a3b8" stroke-width="1" opacity="0.5"/>
<rect x="485" y="707" width="23" height="46" rx="2" fill="#64748b" opacity="0.7"/>
<text x="496" y="733" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c2</text>
<polyline points="685,693 678,700 504,700 496,707" fill="none" stroke="#64748b" stroke-width="1" opacity="0.5"/>
<rect x="509" y="707" width="23" height="46" rx="2" fill="#374151" opacity="0.7"/>
<text x="520" y="733" text-anchor="middle" font-family="monospace" font-size="10" fill="#1f2937">c3</text>
<polyline points="835,693 828,700 528,700 520,707" fill="none" stroke="#374151" stroke-width="1" opacity="0.5"/>
<rect x="60" y="775" width="10" height="10" rx="2" fill="#3b82f6" stroke="#94a3b8" stroke-width="0.5"/>
<text x="74" y="784" font-family="monospace" font-size="13" fill="#1f2937">PE Router</text>
<rect x="147" y="775" width="10" height="10" rx="2" fill="#f59e0b" stroke="#94a3b8" stroke-width="0.5"/>
<text x="161" y="784" font-family="monospace" font-size="13" fill="#1f2937">M_CPU / SRAM</text>
<rect x="255" y="775" width="10" height="10" rx="2" fill="#475569" stroke="#94a3b8" stroke-width="0.5"/>
<text x="269" y="784" font-family="monospace" font-size="13" fill="#1f2937">UCIe</text>
<rect x="307" y="775" width="10" height="10" rx="2" fill="#1f2937" stroke="#94a3b8" stroke-width="0.5"/>
<text x="321" y="784" font-family="monospace" font-size="13" fill="#1f2937">Relay</text>
<rect x="366" y="775" width="10" height="10" rx="2" fill="#10b981" stroke="#94a3b8" stroke-width="0.5"/>
<text x="380" y="784" font-family="monospace" font-size="13" fill="#1f2937">HBM Link</text>
<rect x="446" y="775" width="10" height="10" rx="2" fill="#1f2937" stroke="#94a3b8" stroke-width="0.5"/>
<text x="460" y="784" font-family="monospace" font-size="13" fill="#1f2937">Mesh Link</text>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 KiB

@@ -0,0 +1,57 @@
=== H2D Write Latency (IO->HBM, data=32768B) ===
Case Target Hops Actual Ovhd Drain Wire Ovhd% Drain% Eff.BW BN.BW Util%
-------------------------------------------------------------------------------------------------------------------
h2d-1hop cube0.pe0 1 289.53 50.0 256.0 0.18 17.3% 88.4% 113.17 128.0 88.4%
h2d-2hop cube4.pe0 2 326.04 78.0 256.0 0.20 23.9% 78.5% 100.50 128.0 78.5%
h2d-3hop cube8.pe0 3 362.56 106.0 256.0 0.20 29.2% 70.6% 90.38 128.0 70.6%
h2d-4hop cube12.pe0 4 399.06 134.0 256.0 0.21 33.6% 64.1% 82.11 128.0 64.1%
[v] Monotonic increase: PASS
BW Saturation (Util% by data size):
Case 4KB 16KB 64KB 256KB 1MB
------------------------------------------------------------------
h2d-1hop 38.9% 71.8% 91.1% 97.6% 99.4%
h2d-2hop 29.0% 62.1% 86.8% 96.3% 99.1%
h2d-3hop 23.2% 54.7% 82.8% 95.1% 98.7%
h2d-4hop 19.3% 48.8% 79.2% 93.8% 98.4%
=== D2H Read Latency (HBM->IO, data=32768B) ===
Case Source Hops Actual Ovhd Drain Wire Ovhd% Drain% Eff.BW BN.BW Util%
-------------------------------------------------------------------------------------------------------------------
d2h-1hop cube0.pe0 1 571.20 23.0 256.0 0.04 4.0% 44.8% 57.37 128.0 44.8%
d2h-2hop cube4.pe0 2 635.72 51.0 256.0 0.05 8.0% 40.3% 51.54 128.0 40.3%
d2h-3hop cube8.pe0 3 700.24 79.0 256.0 0.06 11.3% 36.6% 46.80 128.0 36.6%
d2h-4hop cube12.pe0 4 764.76 107.0 256.0 0.07 14.0% 33.5% 42.85 128.0 33.5%
[v] Monotonic increase: PASS
BW Saturation (Util% by data size):
Case 4KB 16KB 64KB 256KB 1MB
------------------------------------------------------------------
d2h-1hop 9.2% 28.9% 61.9% 86.7% 96.3%
d2h-2hop 7.8% 25.2% 57.4% 84.4% 95.6%
d2h-3hop 6.7% 22.4% 53.5% 82.2% 94.9%
d2h-4hop 5.9% 20.1% 50.2% 80.1% 94.2%
[v] D2H >= H2D (reverse data path): PASS
=== PE DMA Latency (pe_dma -> router -> HBM, data=32768B) ===
Case Target Actual Ovhd Drain Wire Ovhd% Drain% Eff.BW BN.BW Util%
----------------------------------------------------------------------------------------------------------------------------
pe-local-hbm c0.pe0->c0.slice0 141.00 2.0 128.0 0.00 1.4% 90.8% 232.40 256.0 90.8%
pe-same-half-hbm c0.pe0->c0.slice1 147.87 4.0 128.0 0.03 2.7% 86.6% 221.60 256.0 86.6%
pe-cross-half-hbm c0.pe0->c0.slice4 161.17 10.0 128.0 0.09 6.2% 79.4% 203.31 256.0 79.4%
pe-cross-cube-hbm-best c0.pe0->c1.slice0 330.52 30.0 256.0 0.01 9.1% 77.5% 99.14 128.0 77.5%
pe-cross-cube-hbm-worst c0.pe0->c15.slice0 677.12 180.0 256.0 0.06 26.6% 37.8% 48.39 128.0 37.8%
* Local BN: 256.0 GB/s, Remote BN: 256.0 GB/s
[v] Cross-cube best < worst: PASS (330.52ns < 677.12ns)
BW Saturation (Util% by data size):
Case 4KB 16KB 64KB 256KB 1MB
------------------------------------------------------------------
pe-local-hbm 88.9% 97.0% 99.2% 99.8% 100.0%
pe-same-half-hbm 79.9% 94.1% 98.5% 99.6% 99.9%
pe-cross-half-hbm 61.3% 86.4% 96.2% 99.0% 99.8%
pe-cross-cube-hbm-best 51.6% 81.0% 94.5% 98.6% 99.6%
pe-cross-cube-hbm-worst 15.1% 41.6% 74.0% 91.9% 97.8%
============================================================
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

@@ -0,0 +1,94 @@
<svg xmlns="http://www.w3.org/2000/svg" width="508" height="495" viewBox="70 40 508 495">
<title>sip</title>
<rect width="648" height="648" fill="#ffffff"/>
<line x1="108.0" y1="144.0" x2="252.0" y2="144.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="180.0" y="140.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="108.0" y1="144.0" x2="108.0" y2="264.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="108.0" y="200.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="252.0" y1="144.0" x2="396.0" y2="144.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="324.0" y="140.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="252.0" y1="144.0" x2="252.0" y2="264.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="252.0" y="200.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="396.0" y1="144.0" x2="540.0" y2="144.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="468.0" y="140.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="396.0" y1="144.0" x2="396.0" y2="264.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="396.0" y="200.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="540.0" y1="144.0" x2="540.0" y2="264.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="540.0" y="200.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="108.0" y1="264.0" x2="252.0" y2="264.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="180.0" y="260.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="108.0" y1="264.0" x2="108.0" y2="384.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="108.0" y="320.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="252.0" y1="264.0" x2="396.0" y2="264.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="324.0" y="260.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="252.0" y1="264.0" x2="252.0" y2="384.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="252.0" y="320.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="396.0" y1="264.0" x2="540.0" y2="264.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="468.0" y="260.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="396.0" y1="264.0" x2="396.0" y2="384.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="396.0" y="320.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="540.0" y1="264.0" x2="540.0" y2="384.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="540.0" y="320.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="108.0" y1="384.0" x2="252.0" y2="384.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="180.0" y="380.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="108.0" y1="384.0" x2="108.0" y2="504.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="108.0" y="440.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="252.0" y1="384.0" x2="396.0" y2="384.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="324.0" y="380.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="252.0" y1="384.0" x2="252.0" y2="504.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="252.0" y="440.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="396.0" y1="384.0" x2="540.0" y2="384.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="468.0" y="380.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="396.0" y1="384.0" x2="396.0" y2="504.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="396.0" y="440.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="540.0" y1="384.0" x2="540.0" y2="504.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="540.0" y="440.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="108.0" y1="504.0" x2="252.0" y2="504.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="180.0" y="500.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="252.0" y1="504.0" x2="396.0" y2="504.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="324.0" y="500.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<line x1="396.0" y1="504.0" x2="540.0" y2="504.0" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="468.0" y="500.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">1.0mm 512GB/s</text>
<polyline points="324.0,56.0 108.0,56.0 108.0,144.0" fill="none" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="216.0" y="96.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">2.5mm 512GB/s</text>
<polyline points="324.0,56.0 252.0,56.0 252.0,144.0" fill="none" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="288.0" y="96.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">2.5mm 512GB/s</text>
<polyline points="324.0,56.0 396.0,56.0 396.0,144.0" fill="none" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="360.0" y="96.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">2.5mm 512GB/s</text>
<polyline points="324.0,56.0 540.0,56.0 540.0,144.0" fill="none" stroke="#000000" stroke-width="1" opacity="0.8"/>
<text x="432.0" y="96.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#000000">2.5mm 512GB/s</text>
<rect x="84.0" y="128.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="108.0" y="148.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (0,0)</text>
<rect x="228.0" y="128.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="252.0" y="148.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (1,0)</text>
<rect x="372.0" y="128.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="396.0" y="148.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (2,0)</text>
<rect x="516.0" y="128.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="540.0" y="148.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (3,0)</text>
<rect x="84.0" y="248.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="108.0" y="268.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (0,1)</text>
<rect x="228.0" y="248.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="252.0" y="268.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (1,1)</text>
<rect x="372.0" y="248.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="396.0" y="268.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (2,1)</text>
<rect x="516.0" y="248.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="540.0" y="268.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (3,1)</text>
<rect x="84.0" y="368.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="108.0" y="388.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (0,2)</text>
<rect x="228.0" y="368.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="252.0" y="388.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (1,2)</text>
<rect x="372.0" y="368.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="396.0" y="388.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (2,2)</text>
<rect x="516.0" y="368.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="540.0" y="388.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (3,2)</text>
<rect x="84.0" y="488.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="108.0" y="508.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (0,3)</text>
<rect x="228.0" y="488.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="252.0" y="508.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (1,3)</text>
<rect x="372.0" y="488.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="396.0" y="508.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (2,3)</text>
<rect x="516.0" y="488.0" width="48.0" height="32.0" rx="4" fill="#cbd5e1" stroke="#000000" stroke-width="1"/>
<text x="540.0" y="508.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#1e293b">CUBE (3,3)</text>
<rect x="308.0" y="50.0" width="32.0" height="12.0" rx="4" fill="#0ea5e9" stroke="#000000" stroke-width="1"/>
<text x="324.0" y="60.0" text-anchor="middle" font-family="monospace" font-size="10" fill="#ffffff">IO io0</text>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

+16 -5
View File
@@ -13,8 +13,12 @@
\usepackage{hyperref} \usepackage{hyperref}
\hypersetup{colorlinks=true,linkcolor=blue!50!black,citecolor=blue!50!black,urlcolor=blue!50!black} \hypersetup{colorlinks=true,linkcolor=blue!50!black,citecolor=blue!50!black,urlcolor=blue!50!black}
\usepackage{caption} \usepackage{caption}
\usepackage{subcaption}
\captionsetup{font=small,labelfont=bf} \captionsetup{font=small,labelfont=bf}
\usepackage{lmodern} % scalable Latin Modern fonts (required by microtype expansion)
\usepackage{microtype} \usepackage{microtype}
\usepackage{tikz}
\usetikzlibrary{arrows.meta,positioning,calc,fit}
\graphicspath{{figures/}} \graphicspath{{figures/}}
@@ -25,7 +29,7 @@
\date{ \date{
\small \small
AGI Computing Lab, System Technology Group\\ AGI Computing Lab, System Technology Group\\
2026 H1 Report 2026 Q1-Q3 Report
} }
\begin{document} \begin{document}
@@ -36,9 +40,16 @@ AGI Computing Lab, System Technology Group\\
\input{sections/02-platform} \input{sections/02-platform}
\input{sections/03-gemm} \input{sections/03-gemm}
\input{sections/04-allreduce} \input{sections/04-allreduce}
\input{sections/05-gqa} \input{sections/05-gqa} % section header + intro
\input{sections/06-discussion} \input{sections/05a-roofline} % 5.1 Roofline Analysis
\input{sections/07-conclusion} \input{sections/05b-capacity-planning} % 5.2 Capacity Planning
\input{sections/08-future-work} \input{sections/05c-parallelism-selection} % 5.3 Parallelism Selection
\input{sections/05x-fused-kernel} % 5.4-5.7 Placement + short/long ctx + composite
\input{sections/05z-summary} % 5.8 Summary
\input{sections/06-agentic}
\input{sections/07-hw-spec-search}
\input{sections/08-discussion}
\input{sections/09-conclusion}
\input{sections/10-future-work}
\end{document} \end{document}
@@ -25,9 +25,12 @@ serves as the common evaluation platform for all mechanisms and kernels
discussed in this study. discussed in this study.
This report focuses on Grouped-Query Attention (GQA), one of the most This report focuses on Grouped-Query Attention (GQA), one of the most
performance- and bandwidth-critical components of LLM inference. Modern performance- and bandwidth-critical components of LLM inference. GQA
decoder-only models such as Llama~3 and Mistral have largely transitioned dominates inference-time memory traffic and KV-cache capacity in modern
from GPT-3-style multi-head attention (MHA) to GQA, in which multiple LLM serving, making it the primary bandwidth bottleneck on memory-centric
architectures such as AHBM. Modern decoder-only models such as Llama~3
and Mistral have largely transitioned from GPT-3-style multi-head
attention (MHA) to GQA, in which multiple
query heads share a single KV head to reduce KV cache capacity and query heads share a single KV head to reduce KV cache capacity and
memory-bandwidth requirements. While GQA improves system efficiency at memory-bandwidth requirements. While GQA improves system efficiency at
the model level, mapping it efficiently onto AHBM introduces three the model level, mapping it efficiently onto AHBM introduces three
@@ -0,0 +1,73 @@
\section{서론}
\label{sec:intro}
AHBM은 연산 유닛을 HBM 스택 내부에 직접 통합한 메모리 중심 가속기
아키텍처이다. 각 처리 요소(PE)는 전용 HBM 슬라이스와 짝지어지며,
TCM과 SRAM을 거쳐 메모리와 MAC 어레이 사이의 데이터를 단계적으로
이동시킨다. 이러한 메모리 중심 구조에서 커널 성능은 연산 처리량만이
아니라, 메모리 계층과 PE 사이에 데이터를 얼마나 효율적으로 배치하고,
이동시키며, 공유하는가에 의해 결정된다. 따라서 AHBM에서의 AI 커널
최적화는 커널 알고리즘과 아키텍처 메커니즘을 함께 발전시키는
하드웨어--소프트웨어 코디자인을 필요로 한다.
상세한 성능 분석과 신속한 설계 탐색을 위해, 우리는
\textbf{KernBench}---AHBM을 위한 소스 수준 이산 사건 시뮬레이션
플랫폼---을 개발하였다. KernBench는 메모리 시스템 지연, PE 실행
모델, PE 간 통신, 호스트 측 오케스트레이션을 포함한 AHBM 실행 모델을
구현하면서, 커널과 호스트 소프트웨어를 소스 코드로부터 직접 실행한다.
이를 통해 실행 거동을 세밀하게 관찰할 수 있을 뿐만 아니라, 컴파일러나
런타임과 같은 상위 소프트웨어 스택과 독립적으로 하드웨어--소프트웨어
코디자인 선택을 체계적으로 평가할 수 있다. 본 보고서에 제시된 모든
결과는 KernBench를 사용하여 얻은 것이며, KernBench는 본 연구에서
다루는 모든 메커니즘과 커널의 공통 평가 플랫폼 역할을 한다.
본 보고서는 LLM 추론에서 가장 성능 및 대역폭 임계 구성요소 중 하나인
Grouped-Query Attention(GQA)에 초점을 맞춘다. Llama~3, Mistral과
같은 최신 디코더 전용 모델은 GPT-3 형태의 다중-헤드 어텐션(MHA)으로
부터 GQA---여러 질의 헤드가 하나의 KV 헤드를 공유함으로써 KV 캐시
용량과 메모리 대역폭 요구량을 줄이는 방식---로 대거 전환되었다. GQA는
모델 수준에서 시스템 효율을 개선하지만, 이를 AHBM에 효율적으로
매핑하기 위해서는 세 가지 아키텍처 요구사항이 발생한다: PE 간 통신을
최소화하기 위한 KV 캐시와 가중치의 최적 배치, 불가피한 PE 간 트래픽에
대한 저오버헤드 지원, 그리고 각 PE 내에서 메모리 접근과 연산의
효율적인 파이프라이닝이다.
이러한 요구사항을 충족하기 위해, 본 보고서는 세 가지 하드웨어--
소프트웨어 코디자인 메커니즘을 제안한다. 첫째, GQA-aware 데이터
배치는 TCM/SRAM/HBM 계층에 KV 캐시와 가중치를 분산 배치하여 통신
오버헤드를 줄이고 데이터 지역성을 향상시킨다. 둘째, PE\_IPCQ는 리덕션
등 통신 집약적 연산을 위한 효율적인 온디바이스 집합 통신 프리미티브를
제공한다. 셋째, composite-command GEMM 파이프라인은 PE\_SCHEDULER의
제어 하에 각 PE 내부에서 메모리 이동과 연산을 긴밀하게 파이프라이닝
하여, 명령 오버헤드를 줄이면서도 MAC 어레이의 가동률을 효율적으로
유지한다.
연산 측 인에이블러(composite-command GEMM 파이프라인,
\S\ref{sec:gemm})와 통신 측 인에이블러(PE\_IPCQ,
\S\ref{sec:allreduce})는 우선 독립적으로 개발 및 평가된다. 이후 융합
GQA 커널(\S\ref{sec:gqa})은 이들을 GQA-aware 데이터 배치와 결합하여
AHBM 상에서 종단간 어텐션 구현을 시연한다. 대응 관계는 직접적이다:
어텐션의 $QK^{\top}$$PV$ 곱은 정확히 composite-command 파이프라인이
이득을 주는 GEMM이며, KV 리덕션은 정확히 PE\_IPCQ가 이득을 주는 집합
연산이다. 이러한 메커니즘들이 결합되어, 융합 GQA 커널은 AHBM의 HBM
대역폭을 효율적으로 활용할 수 있게 된다.
본 연구에서 GQA가 주된 동기 부여 워크로드 역할을 하지만, 도출된
메커니즘들은 훨씬 광범위한 AI 커널 분류군에서 재사용 가능한 구성
요소로 의도되었다. PE\_IPCQ는 분산 및 통신 집약적 워크로드 전반에
걸쳐 집합 통신을 지원할 수 있으며, composite-command 실행은 GEMM
기반 커널, 피드포워드 네트워크(FFN), 정규화, 그 외 융합 연산자
파이프라인에 적용 가능하다. 계층적 데이터 배치 프레임워크와 함께 이
메커니즘들은 AHBM에서의 향후 AI 커널과 통신 라이브러리의 기반을
형성한다. 2026년 하반기에는 이 기반을 FFN 및 MoE 주도 워크로드와
완전한 LLM 실행의 종단간 최적화로 확장할 예정이다.
본 보고서의 나머지 부분은 다음과 같이 구성된다.
\S\ref{sec:platform}에서는 KernBench 플랫폼과 본 연구 전반에 걸쳐
사용된 AHBM 구성을 기술한다. \S\ref{sec:gemm},
\S\ref{sec:allreduce}, \S\ref{sec:gqa}에서는 각각 composite-command
GEMM 파이프라인, PE\_IPCQ 집합 통신, 융합 GQA 커널을 다룬다.
\S\ref{sec:discussion}에서는 이러한 결과의 광범위한 아키텍처적 함의를
논의한다. 마지막으로 \S\ref{sec:conclusion}\S\ref{sec:future}에서는
주요 결과를 요약하고 FFN, MoE, 전체 모델 최적화에 관한 향후 과제를
제시한다.
@@ -1,15 +1,75 @@
\begin{figure*}[t]
\centering
\begin{subfigure}[b]{0.495\textwidth}
\centering
\includegraphics[width=\linewidth,height=0.7\linewidth,keepaspectratio]{sip_architecture.pdf}
\caption{SIP Architecture}
\label{fig:sip-arch}
\end{subfigure}\hfill
\begin{subfigure}[b]{0.42\textwidth}
\centering
\includegraphics[width=\linewidth,height=0.84\linewidth]{cube_architecture.pdf}
\caption{CUBE Architecture}
\label{fig:cube-arch}
\end{subfigure}
\vspace{0.6em}
\begin{subfigure}[t]{\textwidth}
\centering
\includegraphics[width=\linewidth]{pe_architecture.png}
\caption{PE level (zoom-in of one PE in (b)): \textsf{PE\_CPU}
dispatches commands to \textsf{PE\_SCHED}, which routes tile-token
streams through \textsf{PE\_DMA}, \textsf{PE\_FETCH\_STORE}, and
\textsf{GEMM}/\textsf{MATH} engines; \textsf{PE\_IPCQ} is the
on-device collective control plane.}
\label{fig:pe-arch}
\end{subfigure}
\caption{Modeled hardware graph at the SIP, CUBE, and PE levels.
This figure is an \emph{illustrative topology} chosen for
readability; the \emph{experimental configuration} used throughout
this report (port counts, slice counts, and link parameters) is
specified in \S\ref{sec:hw} / Table~\ref{tab:hw}, and numbers in the
two may differ. KernBench is not tied to this particular
arrangement: each box is a modeled component node, each line a
directed link with bandwidth and propagation attributes, and any
topology that respects those attributes is supported.}
\label{fig:hw-arch}
\end{figure*}
\begin{figure*}[t]
\centering
\includegraphics[width=\textwidth]{latency_model.png}
\caption{Conceptual schematic of the latency model. Two source nodes
(Requester A/B) inject flits through a chain of routers into a
destination node; on the shared edge between routers, flits from the
two transactions are interleaved flit-by-flit by the wire's FIFO
arrival order. End-to-end latency is the sum of four contributions:
\textbf{per-node overhead} (the switch's fixed processing cost,
shown in light yellow---the same colour as the destination's
processing-logic block), \textbf{per-edge transmission}
($\textit{flit\_size}/\textit{BW}$ on each wire),
\textbf{drain} (per-flit service occupancy at the destination's
channel), and \textbf{queuing delay} (waiting in a FIFO when a
shared resource is busy). The places where queuing actually
accumulates are highlighted in green: the router output queue and
the destination's input queue. This is the model, not a
measurement; specific bandwidths and overheads are listed in
\S\ref{sec:hw} (Table~\ref{tab:hw}).}
\label{fig:latency-model}
\end{figure*}
\section{The KernBench Platform} \section{The KernBench Platform}
\label{sec:platform} \label{sec:platform}
All results in this report are produced on \emph{KernBench}, a All results in this report are produced on \emph{KernBench}, a
system-level discrete-event simulator for LLM kernels running on AHBM. system-level discrete-event simulator for LLM kernels running on AHBM.
This section explains why the platform exists, how it executes a This section explains why the platform exists, the device and
kernel, how its latency model works---specifically how the hardware is execution model it presents to a kernel writer, how its latency model
viewed as a graph, how that graph is driven by a discrete-event engine, turns that execution into a number, and the concrete hardware
and how congestion is captured---and the concrete hardware configuration configuration used for every experiment that follows.
used for every experiment that follows.
\subsection{Why KernBench: source-level kernels without a software stack} \subsection{Why KernBench}
\label{sec:why} \label{sec:why}
In a production end-to-end (E2E) stack, kernel performance is entangled In a production end-to-end (E2E) stack, kernel performance is entangled
@@ -33,51 +93,100 @@ without the confound of compiler maturity or framework overhead. The
cost is that KernBench numbers are \emph{not} E2E latencies; they are cost is that KernBench numbers are \emph{not} E2E latencies; they are
the achievable-kernel latencies an ideal software stack would expose. the achievable-kernel latencies an ideal software stack would expose.
\subsection{Execution model} \subsection{Device and execution model}
\label{sec:exec} \label{sec:exec}
KernBench models AHBM as a hierarchy of SIPs, CUBEs, and processing
elements (PEs). At the system level, multiple SIPs are connected
through inter-package links, while each SIP contains a collection of
CUBEs joined by an on-package interconnect
(Fig.~\ref{fig:hw-arch}\subref{fig:sip-arch}).
Each CUBE contains eight PEs, shared SRAM, HBM controllers, an
\textsf{M\_CPU} control processor, and an intra-CUBE router mesh
(Fig.~\ref{fig:hw-arch}\subref{fig:cube-arch}). Together these
components form the execution substrate for all kernels evaluated in
this report. This organization reflects the memory-centric nature of
AHBM: each PE is paired with a dedicated slice of HBM bandwidth and
local on-PE storage (TCM), so compute lives next to the data it
consumes rather than fetching it through a far-away memory
controller. The platform's performance question is therefore not
``how many FLOPs can the chip do'' but ``how well can a kernel keep
each compute engine fed from its locally-attached memory while
moving the unavoidable traffic between PEs efficiently.''
Within a PE, commands are dispatched by \textsf{PE\_CPU} to
\textsf{PE\_SCHED}, which routes work to specialized execution
engines---DMA, FETCH/STORE, GEMM, vector-math, and IPCQ
(Fig.~\ref{fig:hw-arch}\subref{fig:pe-arch}). Commands come in two
flavours. \emph{Atomic} commands target a single engine---a plain
DMA read, a GEMM tile, a vector-math op, an IPCQ send/receive---and
are the natural unit for short or special-purpose work; the
\textsf{PE\_CPU} itself also runs control-plane work directly.
\emph{Composite} commands, by contrast, carry an ordered pipeline of
operations across multiple engines (\textsf{DMA\_READ} $\rightarrow$
\textsf{FETCH} $\rightarrow$ \textsf{GEMM}/\textsf{MATH} $\rightarrow$
\textsf{STORE} $\rightarrow$ \textsf{DMA\_WRITE}) that the
\textsf{PE\_SCHED} tiles and streams without per-tile redispatch. The
composite form is the substrate for the GEMM optimization
(\S\ref{sec:gemm}) and, combined with on-PE collectives, for fused
attention (\S\ref{sec:gqa}).
KernBench is layered along the flow of a request: KernBench is layered along the flow of a request:
\begin{itemize} \begin{itemize}
\item The \textbf{runtime API} is host-facing and \item The \textbf{runtime API} is host-facing and
topology-agnostic---it deploys tensors and launches kernels but knows topology-agnostic---it deploys tensors and launches kernels but knows
nothing about routing or interconnect. nothing about routing or interconnect.
\item The \textbf{simulation engine} schedules discrete events, routes \item The \textbf{simulation engine} schedules discrete events and
every request through the modeled graph, and tracks completion via routes every request through the modeled graph.
per-request correlation IDs.
\item The \textbf{components} are device-side nodes that model \item The \textbf{components} are device-side nodes that model
hardware behavior: the per-PE blocks (scheduler, DMA, GEMM and hardware behaviour: the per-PE blocks (scheduler, DMA, GEMM and
vector-math engines, TCM, IPCQ), the NoC routers, the HBM vector-math engines, TCM, IPCQ), the NoC routers, the HBM
controllers, and the inter-chiplet links. controllers, and the inter-chiplet links.
\end{itemize} \end{itemize}
Within a PE, work is expressed as \emph{composite commands}: a single Data and timing are handled in two passes, so that a kernel's
command carries an ordered pipeline of operations numeric results and its latency are computed consistently but
(\textsf{DMA\_READ} $\rightarrow$ \textsf{FETCH} $\rightarrow$ independently. \textbf{Pass~1 (timing)} runs the kernel under
\textsf{GEMM}/\textsf{MATH} $\rightarrow$ \textsf{STORE} $\rightarrow$ the discrete-event engine: memory ops (\textsf{tl.load},
\textsf{DMA\_WRITE}) that the PE scheduler tiles and streams. This \textsf{tl.store}) execute against a host-side \textsf{MemoryStore}
composite mechanism is the substrate for the GEMM optimization and return real tensor data, while compute ops (GEMM, vector-math)
(\S\ref{sec:gemm}) and, combined with on-PE collectives, for fused only emit records into an op-log carrying their operands, shapes,
attention (\S\ref{sec:gqa}). Data and timing are handled in two passes, dtypes, and scheduled time. \textbf{Pass~2 (data)} replays that
so that a kernel's numeric results and its latency are computed op-log offline in numpy, producing the actual numeric outputs and
consistently but independently. comparing them against a reference computation under per-dtype
tolerances (e.g.\ \texttt{rtol}/\texttt{atol} $=10^{-3}$ for f16).
Pass~2 is optional---runs that need only latency skip it---but when
enabled it guarantees that every reported timing number corresponds
to a kernel whose numeric output has been verified end-to-end.
\subsection{Latency model: a graph traversed by events} \subsection{Latency model: graph traversal and contention}
\label{sec:latency} \label{sec:latency}
\paragraph{The hardware as a graph.} KernBench views the modeled The modeled hardware hierarchy described above is represented
hardware as a directed graph. \emph{Nodes} are the components listed internally as a directed graph. Nodes correspond to hardware
above; \emph{edges} are the interconnect links between them, each components---PEs, routers, memory controllers, SRAM blocks, IO
carrying bandwidth (\si{\giga\byte\per\second}) and propagation chiplets---while edges represent communication links with associated
(\si{\nano\second}) attributes. The topology is compiled once at bandwidth (\si{\giga\byte\per\second}) and propagation
(\si{\nano\second}) attributes. Every operation in KernBench---DMA
transfers, remote memory accesses, collective communication, command
dispatch---is modelled as a traversal through this graph. End-to-end
latency is decomposed into four contributions accumulated along the
traversal path (Fig.~\ref{fig:latency-model}): \textbf{per-node
overhead} at each component, \textbf{per-edge transmission} on each
wire, \textbf{drain} (per-flit service occupancy) at the destination,
and \textbf{queuing delay} at the shared FIFOs that the wire and the
destination share between concurrent transactions.
\paragraph{The hardware as a graph.} The topology is compiled once at
configuration time into this graph and is never mutated during a run. configuration time into this graph and is never mutated during a run.
Every routed request---a DMA, a remote read, an IPCQ message, a There are no hidden shortcuts, implicit bypasses, or magic paths: if a
kernel-launch command---is a \emph{traversal} of this graph from a request reaches its destination, the path it took is explicit in the
source node to a destination service, hopping through routers and graph, and the latency it incurred is the sum of the per-node and
links along the way. There are no hidden shortcuts, implicit bypasses, per-edge costs paid along that path. The same graph representation
or magic paths: if a request reaches its destination, the path it took applies recursively at every hierarchy level---system, SIP, CUBE, and
is explicit in the graph, and the latency it incurred is the sum of PE (Fig.~\ref{fig:hw-arch}).
the per-node and per-edge costs paid along that path.
\paragraph{From graph to discrete-event simulation.} The graph is \paragraph{From graph to discrete-event simulation.} The graph is
driven by a discrete-event engine. Two kinds of events advance driven by a discrete-event engine. Two kinds of events advance
@@ -85,14 +194,14 @@ simulation time: \emph{node events} (component switching overhead,
service completions such as an HBM channel commit or a GEMM tile service completions such as an HBM channel commit or a GEMM tile
finish) and \emph{edge events} (the flit-by-flit serialization of a finish) and \emph{edge events} (the flit-by-flit serialization of a
payload across a bandwidth-limited link). The engine maintains a payload across a bandwidth-limited link). The engine maintains a
priority queue of pending events ordered by their scheduled time, priority queue of pending events ordered by their scheduled time and
fires them one at a time, and treats ties under a deterministic fires them one at a time, with ties broken under a deterministic
ordering policy so that the same kernel on the same topology always policy so that the same kernel on the same topology always yields the
yields the same trace. Per-request correlation IDs are stamped at same trace. Per-request correlation IDs are stamped at injection and
injection and carried through every hop, so the trace is recoverable carried through every hop, so the path from injection to completion
from injection to completion. Every nanosecond in a reported latency is fully traceable. Every modeled latency contribution
traces back to exactly one of these events on exactly one node or corresponds to exactly one of these events on exactly one node or
edge. edge---there is no slack in the budget.
\paragraph{Latency contributions.} Three kinds of latency accumulate \paragraph{Latency contributions.} Three kinds of latency accumulate
along a traversal: (i) \emph{per-node fixed overhead}---each component along a traversal: (i) \emph{per-node fixed overhead}---each component
@@ -107,17 +216,83 @@ collective engines hold the request for their service time before
releasing it downstream. Each of these is attached to a specific node releasing it downstream. Each of these is attached to a specific node
or edge in the graph; together they make up the entire latency budget. or edge in the graph; together they make up the entire latency budget.
\paragraph{Congestion: where bottlenecks emerge.} The simulator's Beyond data movement and execution latency, KernBench
also models the control-plane cost required to issue work
to accelerator engines. Since every DMA, GEMM, vector-
math, and IPCQ operation is initiated through the
\textsf{PE\_CPU} $\rightarrow$ \textsf{PE\_SCHED} path,
command dispatch latency contributes to the end-to-end
execution time of all kernels.
\paragraph{Command dispatch overhead model.} The cost incurred by
the \textsf{PE\_CPU} when it dispatches a command to one of the
accelerator engines is modelled structurally rather than with a
per-operation calibration table. The \textsf{PE\_CPU} charges, per
command,
\[
d_{\text{cmd}} = \textsf{FIXED} + b_{\text{logical}} \cdot R,
\]
This linear-in-size form reflects the serialization cost of writing a
command descriptor into the scheduler queue: a fixed per-command
bookkeeping cost plus a byte-wise descriptor-transfer cost.
Concretely, $b_{\text{logical}}$ is the command's hardware-logical byte
size, \textsf{FIXED} captures the fixed per-command cost (queue-tail
update, completion registration) and $R$ captures the per-byte cost of
serializing the command descriptor into the scheduler queue. This
\textsf{FIXED} is distinct from Table~\ref{tab:hw}'s
\SI{2}{\nano\second} / \SI{1}{\nano\second} \textsf{PE\_CPU} /
\textsf{PE\_SCHED} fixed costs: the latter are per-component
traversal overheads each command pays when it transits those nodes,
whereas the \textsf{FIXED} term here is the command-descriptor
issue cost. \textsf{FIXED} depends on the command class: a single-op
command --- one engine operation, i.e.\ a single DMA descriptor, GEMM,
or elementwise issue --- carries a lighter 8-cycle \textsf{FIXED},
while the composite command, which the scheduler expands into a
multi-stage tile-feeder plan, carries 40 cycles
(\S\ref{sec:gemm-vs-async}). With the composite \textsf{FIXED} $= 40$
cycles and $R = 0.0625$ cycles/byte (i.e.\ \SI{16}{\byte\per\cycle},
at \SI{1}{\giga\hertz}) a typical composite lands at roughly
\SI{43}{\nano\second}, and a hard cap on a composite's descriptor size
prevents the model from rewarding arbitrarily large fused commands
beyond what real descriptor queues accept. In the configurations measured here, command issue is
not the bottleneck---data movement is---so this term stays small
relative to DMA and collective time.
\begin{table*}[t]
\centering
\caption{PE\,$\to$\,HBM DMA latency probe at varying hop distances
(\SI{32}{\kibi\byte} transfer; output captured from \texttt{kernbench
probe}). \emph{Util\%} is the achieved bandwidth as a fraction of the
path's bottleneck-edge bandwidth.}
\label{tab:probe-pe-dma}
\small
\begin{tabular}{@{}lrrr@{}}
\toprule
Case & Latency~(\si{\nano\second}) & Util\% (\,32\,KiB) & Util\% (\,1\,MiB) \\
\midrule
PE\,$\to$\,local HBM & 141.0 & 90.8 & 100.0 \\
PE\,$\to$\,same-half HBM & 147.9 & 86.6 & ~99.9 \\
PE\,$\to$\,cross-half HBM & 161.2 & 79.4 & ~99.8 \\
PE\,$\to$\,cross-CUBE (best) & 330.5 & 77.5 & ~99.6 \\
PE\,$\to$\,cross-CUBE (worst) & 677.1 & 37.8 & ~97.8 \\
\bottomrule
\end{tabular}
\end{table*}
\subsubsection{Congestion and contention modeling}
\label{sec:congestion}
The simulator's
sharpness comes from how it models contention for those nodes and sharpness comes from how it models contention for those nodes and
edges. \emph{Every directed edge has a FIFO}: an arriving flit takes edges. \emph{Every directed edge has a FIFO}: an arriving flit takes
its bandwidth-limited transfer time on top of whatever earlier flits its bandwidth-limited transfer time on top of whatever earlier flits
are still being served, so a busy link queues later traffic behind are still being served, so a busy link queues later traffic behind
earlier traffic rather than transferring everything at peak BW. earlier traffic rather than transferring everything at peak BW.
\emph{HBM is modeled with per-pseudo-channel parallelism}: a stateless \emph{HBM is modelled with per-pseudo-channel parallelism}: a
array of channel-availability timestamps with address-based channel stateless array of channel-availability timestamps with address-based
selection captures the bank-level concurrency that real HBM exposes, channel selection captures the bank-level concurrency that real HBM
so 64 channels per CUBE deliver real parallelism on uniform addresses exposes, so 64 channels per CUBE deliver real parallelism on uniform
but a hot channel surfaces as the bottleneck on skewed ones. addresses but a hot channel surfaces as the bottleneck on skewed ones.
\emph{Every component has a serial worker}: a router carrying two \emph{Every component has a serial worker}: a router carrying two
heavy streams interleaves them at flit granularity in arrival order heavy streams interleaves them at flit granularity in arrival order
rather than fanning out for free, so two concurrent collectives sharing rather than fanning out for free, so two concurrent collectives sharing
@@ -127,67 +302,127 @@ it reveals where the real bottlenecks form and which hardware levers
actually relieve them---which is exactly the question the codesign actually relieve them---which is exactly the question the codesign
work in this report turns on. work in this report turns on.
\paragraph{Control-plane (issue) cost model.} The cost of \emph{issuing}
a command is modeled structurally rather than with a per-operation
calibration table. The PE control processor charges, per command,
\[
d_{\text{cmd}} = \textsf{FIXED} + b_{\text{logical}} \cdot R,
\]
where $b_{\text{logical}}$ is the command's hardware-logical byte size,
\textsf{FIXED} captures the fixed per-command cost (queue-tail update,
completion registration) and $R$ captures the per-byte cost of
serializing the command descriptor into the scheduler queue. The
default anchoring (\textsf{FIXED} $= 40$ cycles, $R = 0.0625$
cycles/byte, i.e.\ \SI{16}{\byte\per\cycle}, at \SI{1}{\giga\hertz})
places a typical composite at roughly \SI{43}{\nano\second}, and a
hard cap on a composite's descriptor size prevents the model from
rewarding arbitrarily large fused commands beyond what real descriptor
queues accept. In the configurations measured here, command issue is
not the bottleneck---data movement is---so this term stays small
relative to DMA and collective time.
\paragraph{Accuracy.} The model is precise about the effects that \subsection{Accuracy}
\label{sec:accuracy}
The model is precise about the effects that
dominate kernel latency on this class of hardware: per-edge bandwidth dominate kernel latency on this class of hardware: per-edge bandwidth
occupancy and flit-level serialization, HBM pseudo-channel parallelism, occupancy and flit-level serialization, HBM pseudo-channel parallelism,
and per-component switching overhead. Two independent cross-checks and per-component switching overhead. Two independent cross-checks
drawn from the experiments in this report confirm that this precision drawn from the experiments in this report confirm that this precision
translates into physically reasonable kernel latencies. First, in the translates into physically reasonable kernel latencies.
GEMM study (\S\ref{sec:gemm}), simulator-measured MAC efficiency
tracks an analytic ideal-pipeline model within roughly First, in the GEMM study (\S\ref{sec:gemm}), simulator-measured MAC
\SIrange{10}{20}{\percent} across a wide range of tile counts; the efficiency tracks an analytic ideal-pipeline model within
residual gap is attributable to pipeline-fill and DMA effects the \SI{1.4}{ppt} across every swept shape---from a single-tile
analytic model omits. Second, in the all-reduce study $M{=}K{=}N{=}32$ at $\sim\SI{7.7}{\percent}$ up to the deep-$K$
(\S\ref{sec:allreduce}, Fig.~\ref{fig:allreduce-cmp}), simulator $K{=}3072$ case at $\sim\SI{90}{\percent}$. The residual gap is
latency for a 2D-torus over six devices follows the expected fill/tail overhead the closed-form pipeline model omits.
startup-plus-per-packet shape across the entire payload sweep---tight
at small payloads where startup dominates, and within a single-digit Second, in the all-reduce study (\S\ref{sec:allreduce},
multiplicative factor at the largest payloads, where the residual gap Fig.~\ref{fig:allreduce-cmp}), simulator latency for a 2D-torus over
is explained by per-router switching the analytic shape elides. A six devices follows the expected startup-plus-per-packet shape across
single-device point from an external full-system simulator (FSIM) at the entire payload sweep---tight at small payloads where startup
the largest payload sits an order of magnitude above the KernBench dominates, and within a single-digit multiplicative factor at the
multi-device torus, illustrating the well-known gap between an largest payloads, where the residual gap is explained by per-router
achievable-kernel number and a full end-to-end-stack number rather switching the analytic shape elides.
than a model error. The known simplifications---FIFO router
arbitration (instead of round-robin), HBM scheduler without Third, the simulator's per-traversal behaviour can be probed
write-buffer reordering, no bank conflict, no refresh or thermal directly. Running \texttt{kernbench probe} on the modelled topology
effects, and no upstream backpressure---are the price of a issues a sequence of PE-to-HBM DMA reads at progressively greater
deterministic, inspectable model. They bound the absolute accuracy but hop distances and reports the per-component overhead, per-edge
do not distort the \emph{relative} comparisons (tiling A vs.\ B, serialization, and per-PC drain that the model charges
topology X vs.\ Y, with vs.\ without composite command, mesh vs.\ (Table~\ref{tab:probe-pe-dma}). Three properties stand out. First,
torus) that this report is built on. the reported latency increases \emph{monotonically} with hop
count---from \SI{141}{\nano\second} at the local HBM slice to
\SI{677}{\nano\second} at the worst-case remote-CUBE slice---with the
increment per added hop matching the per-router overhead and the
UCIe-link cost in Table~\ref{tab:hw}. Second, the bandwidth-saturation
curves track the wire's serialization model: at \SI{1}{\mebi\byte}
transfers the local PE DMA reaches \SI{100}{\percent} of the per-edge
bandwidth limit, while the longest cross-CUBE path saturates at
\SI{97.8}{\percent}---exactly the gap that the per-edge propagation
and per-router overhead model would predict. Third, the simulator
passes the internal-consistency invariants the probe builds in
(monotonic hop progression; D2H reads at least as long as the
equivalent H2D writes because of the reverse-path acknowledgement;
cross-CUBE best less than cross-CUBE worst). These are properties
that hold \emph{only} when the underlying graph traversal, FIFO
contention, and propagation models are internally
self-consistent---a model error in any of them would surface as a
non-monotonic or under-utilising curve here long before it polluted a
kernel-level measurement.
The known simplifications---FIFO router arbitration (instead of
round-robin), HBM scheduler without write-buffer reordering, no bank
conflict, no refresh or thermal effects, and no upstream
backpressure---are the price of a deterministic, inspectable model.
These simplifications bound the absolute accuracy, but the agreement
with both analytic models and the probe's internal-consistency
checks above indicates that KernBench is sufficiently accurate for
evaluating the \emph{relative} hardware--software design trade-offs
(tiling A vs.\ B, topology X vs.\ Y, with vs.\ without composite
command, mesh vs.\ torus) that are the primary objective of this
work.
\subsection{Modeled hardware configuration} \subsection{Modeled hardware configuration}
\label{sec:hw} \label{sec:hw}
Table~\ref{tab:hw} summarizes the hardware configuration used for Table~\ref{tab:hw} summarizes the hardware configuration
every experiment in this report. It is read directly from the used throughout this report. The intent of this
simulator's topology description; per-experiment workload parameters configuration is not to model a specific product, but to
(matrix shapes, collective sizes, sequence lengths) are stated in represent a realistic memory-centric accelerator and to
their respective sections rather than here. provide a consistent baseline for evaluating hardware--
software co-design mechanisms.
The compute capability within each CUBE is provisioned
such that inference workloads can effectively saturate
the available HBM bandwidth. The aggregate compute
throughput is therefore balanced against memory-system
bandwidth rather than being intentionally over- or
under-provisioned. Concretely, 8 PEs $\times$
\SI{8}{\tera\flop\per\second} give roughly
\SI{64}{\tera\flop\per\second} of f16 compute per CUBE
against \SI{2048}{\giga\byte\per\second} of HBM bandwidth,
which places the balanced point at about
$\sim$31~FLOP/byte; inference decode kernels typically
operate well below that, so HBM bandwidth---not compute---is
the natural ceiling on this configuration. For reference, the
GQA decode kernels evaluated in \S\ref{sec:gqa} operate at
arithmetic intensity well below this balance point, so their
ceiling is set by HBM and inter-PE traffic rather than by GEMM
throughput. HBM bandwidth
is distributed evenly across the PEs within a CUBE, with
each PE responsible for servicing approximately one-eighth
of the CUBE's memory bandwidth through its dedicated HBM
channels.
The on-chip interconnect is configured using bandwidth
and latency parameters representative of commercially
available mesh-network IPs. The goal is not to study a
particular NoC implementation, but rather to evaluate
kernel behavior under a realistic baseline communication
fabric.
For die-to-die communication, UCIe-A bandwidth
characteristics are used as the reference point for
inter-CUBE links. Inter-SIP communication is modeled
using PCIe-class links. Together, these assumptions
provide a representative communication hierarchy for
evaluating collective communication and distributed
kernel execution.
Unless otherwise stated, all experiments use this
configuration. Workload-specific parameters such as
matrix dimensions, sequence lengths, and collective
payload sizes are introduced in their respective sections.
\begin{table}[t] \begin{table}[t]
\centering \centering
\caption{Modeled hardware configuration (shared by all experiments).} \caption{Modeled hardware configuration}
\label{tab:hw} \label{tab:hw}
\small \small
\begin{tabular}{@{}ll@{}} \begin{tabular}{@{}ll@{}}
@@ -205,11 +440,12 @@ GEMM engine peak & \SI{8}{\tera\flop\per\second} (f16) \\
TCM (on-PE) & \SI{16}{\mega\byte}, \SI{512}{\giga\byte\per\second} R/W \\ TCM (on-PE) & \SI{16}{\mega\byte}, \SI{512}{\giga\byte\per\second} R/W \\
\quad kernel scratch & \SI{1}{\mega\byte} \\ \quad kernel scratch & \SI{1}{\mega\byte} \\
DMA engines & 1 read + 1 write \\ DMA engines & 1 read + 1 write \\
CPU / scheduler overhead & \SI{2}{\nano\second} / \SI{1}{\nano\second} \\ \textsf{PE\_CPU} fixed cost & \SI{2}{\nano\second} \\
\textsf{PE\_SCHED} fixed cost & \SI{1}{\nano\second} \\
\midrule \midrule
\multicolumn{2}{@{}l}{\emph{Memory (per CUBE)}} \\ \multicolumn{2}{@{}l}{\emph{Memory (per CUBE)}} \\
HBM capacity & \SI{48}{\giga\byte} (8 slices) \\ HBM capacity & \SI{48}{\giga\byte} (8 slices) \\
HBM aggregate BW & \SI{1024}{\giga\byte\per\second} \\ HBM aggregate BW & \SI{2048}{\giga\byte\per\second} \\
HBM pseudo-channels & 64 (8 per PE), \SI{32}{\giga\byte\per\second} each \\ HBM pseudo-channels & 64 (8 per PE), \SI{32}{\giga\byte\per\second} each \\
SRAM (shared) & \SI{32}{\mega\byte}, \SI{128}{\giga\byte\per\second} link \\ SRAM (shared) & \SI{32}{\mega\byte}, \SI{128}{\giga\byte\per\second} link \\
HBM burst & \SI{256}{\byte} \\ HBM burst & \SI{256}{\byte} \\
@@ -220,7 +456,8 @@ Inter-CUBE (UCIe PHY) & \SI{512}{\giga\byte\per\second}, \SI{8}{\nano\second}, X
Inter-SIP (PCIe) & \SI{768}{\giga\byte\per\second} per endpoint \\ Inter-SIP (PCIe) & \SI{768}{\giga\byte\per\second} per endpoint \\
\midrule \midrule
\multicolumn{2}{@{}l}{\emph{Command-issue cost model (defaults)}} \\ \multicolumn{2}{@{}l}{\emph{Command-issue cost model (defaults)}} \\
FIXED per command & 40 cycles \\ FIXED per single-op command & 8 cycles \\
FIXED per composite command & 40 cycles \\
per-byte rate $R$ & 0.0625 cycles/byte (\SI{16}{\byte\per\cycle}) \\ per-byte rate $R$ & 0.0625 cycles/byte (\SI{16}{\byte\per\cycle}) \\
composite size cap & \SI{1024}{\byte} \\ composite size cap & \SI{1024}{\byte} \\
\bottomrule \bottomrule
@@ -1,8 +1,6 @@
\section{GEMM Acceleration via the Composite Command} \section{GEMM Acceleration via the Composite Command}
\label{sec:gemm} \label{sec:gemm}
\subsection{Why it is needed}
GEMM is the compute core of every transformer block---the QKV GEMM is the compute core of every transformer block---the QKV
projections, the attention score and context products, and the projections, the attention score and context products, and the
feed-forward matrices are all matrix multiplications. On a tiled feed-forward matrices are all matrix multiplications. On a tiled
@@ -21,20 +19,17 @@ in command overhead?
\subsection{Design} \subsection{Design}
The answer is the \emph{composite command}. A single command carries the The answer is the \emph{composite command}. A single command carries
ordered tile pipeline the ordered five-stage tile pipeline (\textsf{DMA\_READ},
\[ \textsf{FETCH}, \textsf{GEMM}, \textsf{STORE}, \textsf{DMA\_WRITE}),
\textsf{DMA\_READ}\rightarrow\textsf{FETCH}\rightarrow\textsf{GEMM}
\rightarrow\textsf{STORE}\rightarrow\textsf{DMA\_WRITE},
\]
and the PE scheduler splits the payload into hardware tiles and the PE scheduler splits the payload into hardware tiles
(here $32\times64\times32$), emitting one tile token per tile. Subsequent (here $32\times64\times32$), emitting one tile token per tile.
stages are reached by \emph{token self-routing} between the on-PE engines, Subsequent stages are reached by \emph{token self-routing} between
so a tile flows DMA\,$\rightarrow$\,fetch\,$\rightarrow$\,GEMM\,$% the on-PE engines, so a tile flows through the chain without
\rightarrow$\,store without returning to the scheduler between stages. returning to the scheduler between stages. Because the whole
Because the whole pipeline is described by one command, the issue cost is pipeline is described by one command, the issue cost is paid once
paid once per GEMM rather than once per tile-stage, and the scheduler is per GEMM rather than once per tile-stage, and the scheduler is free
free to keep every stage busy on different tiles simultaneously---tile to keep every stage busy on different tiles simultaneously---tile
$i$'s GEMM overlaps tile $i{+}1$'s DMA read. A multi-operation composite $i$'s GEMM overlaps tile $i{+}1$'s DMA read. A multi-operation composite
additionally lets an epilogue (for example a vector-math step) ride the additionally lets an epilogue (for example a vector-math step) ride the
same tile loop, firing per $K$-tile, per output tile, or once per kernel same tile loop, firing per $K$-tile, per output tile, or once per kernel
@@ -45,66 +40,362 @@ kernel to fuse its softmax work into the GEMM pipeline
\subsection{Results} \subsection{Results}
We sweep eight GEMM shapes spanning square, tall, wide, and deep-$K$ We sweep eight GEMM shapes spanning square, tall, wide, and deep-$K$
geometries, under three operand-staging variants geometries under two operand-staging variants that bracket the
(\textsf{ref\_ref}, both operands streamed from HBM; \textsf{load\_ref}, realistic LLM cases. In \textsf{load\_ref}, the activation $A$ is
one operand resident in TCM; \textsf{load\_load}, both resident). pre-staged on chip and only the weight $W$ streams from HBM during
Figure~\ref{fig:gemm-util} reports MAC utilization and efficiency, and the composite (the ``activation in TCM, weights from HBM'' case
Figure~\ref{fig:gemm-stages} breaks the kernel into per-stage engine typical of decoding with a small batch). In \textsf{ref\_ref}, both
busy time. operands stream from HBM during the composite (the ``cold both
sides'' case, where the activation does not fit on-chip or is itself
the output of a producer composite that wrote back to HBM). Both
variants run the same composite command; only the up-front staging
of $A$ differs.
We evaluate the composite GEMM along two axes that together describe
how well it lands on the hardware: \emph{HBM bandwidth utilization}
(does the workload saturate the per-PE HBM bandwidth, i.e.\ is it
memory-bound on this configuration?) and \emph{achieved GEMM
throughput} (what fraction of the \SI{8}{\tera\flop\per\second}
per-PE peak does the kernel actually deliver). Both metrics use the
composite window as the denominator, so the up-front \textsf{tl.load}
of $A$ in \textsf{load\_ref} is excluded — only HBM traffic and
compute that happen \emph{inside} the composite count.
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gemm_hbm_bw_util.png}
\caption{Per-PE HBM bandwidth utilization during the composite window,
for the two staging variants. The ceiling is the per-PE HBM bandwidth
(\SI{256}{\giga\byte\per\second}; dashed line at \SI{100}{\percent}).
\textsf{ref\_ref} streams both $A$ and $W$ and so always sits at a
higher BW-utilization than \textsf{load\_ref} on the same shape;
both variants saturate ($>\SI{85}{\percent}$) once the kernel
has enough total bytes to keep the link busy (deep-$K$ $K{=}3072$,
or low-reuse output-dominated $M{=}128,K{=}8,N{=}128$). Small or
single-tile shapes do not have enough total traffic to saturate the
link and are bottlenecked by pipeline fill rather than HBM.}
\label{fig:gemm-bw}
\end{figure}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gemm_per_pe_tflops.png}
\caption{Per-PE GEMM throughput delivered during the composite window
(reference line at the \SI{8}{\tera\flop\per\second} per-PE engine
peak). At the deep-$K$ corner $M{=}32,K{=}3072,N{=}32$ the
activation-pre-staged kernel reaches
\SI{7.18}{\tera\flop\per\second} ($\sim\SI{90}{\percent}$ of peak);
the both-from-HBM variant drops to
\SI{3.83}{\tera\flop\per\second} ($\sim\SI{48}{\percent}$) at the
same shape because the second operand doubles HBM pressure and the
kernel hits the BW ceiling shown in Fig.~\ref{fig:gemm-bw}.
Under-tile shapes ($M{=}K{=}N{=}32$, $M{=}8$, $K{=}8$) are
hard-capped by tile-fill at \SI{50}{\percent}, \SI{25}{\percent},
\SI{12.5}{\percent} of peak regardless of staging.}
\label{fig:gemm-tflops}
\end{figure}
The composite reaches the hardware roofline in two distinct regimes.
\emph{Memory-bound}: $K{=}3072$ deep-$K$ saturates HBM (\SI{88}{\percent}
load\_ref, \SI{94}{\percent} ref\_ref) and the low-reuse
$M{=}128,K{=}8,N{=}128$ corner saturates even harder
($>\SI{96}{\percent}$ in both), because back-to-back output
writes dominate. \emph{Compute-rich}: at the same deep-$K$ shape,
\textsf{load\_ref} delivers \SI{7.18}{\tera\flop\per\second}
($\sim\SI{90}{\percent}$ of the per-PE peak) — the configuration's
clean win. The \emph{distance between the two variants} at the same
shape is the cost of \emph{not} pre-staging $A$: at $K{=}3072$ the
throughput halves (\SI{7.18}{\tera\flop\per\second} $\to$
\SI{3.83}{\tera\flop\per\second}) because the second operand doubles
HBM pressure
and the kernel hits the BW ceiling. This is the operational
take-away — when the activation can live on-chip the composite
delivers near-peak GEMM; when it cannot the BW ceiling dominates and
throughput halves.
Figure~\ref{fig:gemm-util} validates that simulator-measured
efficiency tracks an analytic ideal-pipeline model (within
\SI{1.4}{ppt} across every swept shape), and
Figure~\ref{fig:gemm-stages} resolves the per-stage engine wall-clock
that backs the above interpretation.
\begin{figure}[t] \begin{figure}[t]
\centering \centering
\includegraphics[width=\linewidth]{gemm_mac_utilization_theoretical_vs_measured.png} \includegraphics[width=\linewidth]{gemm_mac_utilization_theoretical_vs_measured.png}
\caption{GEMM MAC utilization and efficiency, theoretical vs.\ measured. \caption{GEMM MAC utilization and efficiency, theoretical vs.\ measured
Tile-fill sets the ceiling: under-tile shapes (marked $\ast$) such as (\textsf{load\_ref} staging). Tile-fill sets the ceiling: under-tile
$M{=}K{=}N{=}32$, $M{=}8$, and $K{=}8$ cannot fill the MAC tile and cap at shapes (marked $\ast$) such as $M{=}K{=}N{=}32$, $M{=}8$, and $K{=}8$
\SI{50}{\percent}, \SI{25}{\percent}, \SI{12.5}{\percent}. For cannot fill the MAC tile and cap at \SI{50}{\percent},
tile-filling shapes, efficiency climbs with tile count---from \SI{25}{\percent}, \SI{12.5}{\percent}. For tile-filling shapes,
\textasciitilde\SI{23}{\percent} at one tile to \textasciitilde% efficiency climbs with tile count---from
\SI{78}{\percent} measured at 48 tiles ($K{=}3072$)---and the measured \textasciitilde\SI{15}{\percent} at one tile to \textasciitilde%
bars track the analytic prediction within \SI{90}{\percent} measured at 48 tiles ($K{=}3072$)---and the
\SIrange{10}{20}{\percent}.} measured bars track the analytic ideal-pipeline prediction within
\SI{1.4}{ppt} across every shape.}
\label{fig:gemm-util} \label{fig:gemm-util}
\end{figure} \end{figure}
\begin{figure}[t] \begin{figure}[t]
\centering \centering
\includegraphics[width=\linewidth]{gemm_stage_breakdown.png} \includegraphics[width=\linewidth]{gemm_stage_breakdown.png}
\caption{Per-stage engine wall-clock (DMA in, Fetch, GEMM, DMA out) under \caption{Per-stage engine wall-clock (DMA in, Fetch, GEMM, DMA out)
\textsf{load\_ref} staging. For the deep-$K$ shape ($K{=}3072$, 48 tiles) under \textsf{load\_ref} staging. For the deep-$K$ shape ($K{=}3072$,
the Fetch and GEMM stages are large and comparable 48 tiles) DMA in, Fetch, and GEMM are all close to
(\textasciitilde\SI{770}{} and \SI{785}{\nano\second}) while DMA-out is \textasciitilde\SI{785}{\nano\second}, running concurrently in a
negligible---a compute-rich, well-pipelined regime. For the low-reuse tightly pipelined regime while DMA-out is negligible. For the
shape ($M{=}128,K{=}8,N{=}128$) DMA-out grows to low-reuse shape ($M{=}128,K{=}8,N{=}128$, 16 output tiles) DMA-out
\textasciitilde\SI{350}{\nano\second} and compute is small---a grows to \textasciitilde\SI{336}{\nano\second} while GEMM compute is
data-movement-bound regime.} \textasciitilde\SI{262}{\nano\second}---a data-movement-bound regime
where the output write becomes the largest stage.}
\label{fig:gemm-stages} \label{fig:gemm-stages}
\end{figure} \end{figure}
Two regularities stand out. (i) Utilization is governed by how completely
the problem fills the MAC tile: the three under-tile shapes are hard-capped
well below \SI{100}{\percent}, independent of how the kernel is issued.
(ii) Among tile-filling shapes, efficiency is governed by tile count---more
tiles amortize the one-time pipeline fill and issue cost, so the deep-$K$
shape reaches \textasciitilde\SI{78}{\percent} of peak while a single-tile
shape reaches only \textasciitilde\SI{23}{\percent}. The stage breakdown
explains why: with 48 tiles the GEMM and Fetch stages overlap and stay
busy, whereas the low-reuse shape spends most of its time moving data in
and out.
\subsection{Analysis and meaning} \subsection{Analysis and meaning}
The composite command does not manufacture bandwidth or MAC throughput; it The composite command does not manufacture bandwidth or MAC
removes the two software-shaped obstacles between a GEMM and its hardware throughput; it removes the two software-shaped obstacles between a
roofline. By paying issue cost once per GEMM and chaining tile-stages GEMM and its hardware roofline. By paying the issue cost once per
through token self-routing, it lets the scheduler keep the pipeline full, GEMM rather than once per tile-stage, and by chaining tile-stages
so compute-rich shapes actually reach the efficiency their arithmetic through token self-routing, it lets the scheduler keep every stage
intensity allows ($\sim$\SI{78}{\percent} measured at 48 tiles), and busy on a different tile, so compute-rich shapes actually reach the
data-bound shapes actually reach their DMA bound instead of stalling on efficiency their arithmetic intensity allows
command overhead. The close agreement between measured and theoretical ($\sim$\SI{90}{\percent} of GEMM peak at the deep-$K$
efficiency (Figure~\ref{fig:gemm-util}) is also the report's primary \textsf{load\_ref} corner) and data-bound shapes actually reach
validation that KernBench's latency model is faithful in the regime that their HBM-BW ceiling instead of stalling on command overhead.
matters. The hardware implication is concrete: a single-command,
self-routing tile pipeline is the issue mechanism that makes the MAC array The split between \textsf{load\_ref} and \textsf{ref\_ref} also makes
usable, and it is a prerequisite---not a luxury---for the fused attention the hardware-software boundary explicit: when the activation fits in
kernel of \S\ref{sec:gqa}. on-chip storage the composite is BW-headroom-rich and saturates the
GEMM engine; when it does not, both operands compete for the same
HBM port and the kernel is BW-bound. This is the lever the GQA
kernel of \S\ref{sec:gqa} reaches for next — keeping the right
working set on-chip so the composite pipeline lands in the
compute-rich regime rather than the BW-bound one.
\subsection{Why composite, and not kernel-orchestrated async loading?}
\label{sec:gemm-vs-async}
A reader familiar with double-buffered GEMM kernels on conventional
hardware may ask: why a hardware-side composite command at all? Why
isn't the obvious kernel-level pattern --- async-load each operand,
overlap with compute, accumulate --- sufficient?
To answer this concretely we contrast composite against two
kernel-orchestrated baselines that have access to the same single-op
primitives the platform exposes (\textsf{tl.load} for async DMA into
TCM, \textsf{tl.dot} for a single-op GEMM command on TCM-resident
operands, \textsf{tl.store} for a DMA write-back). Both baselines
pre-stage activation $A$ identically to \textsf{load\_ref}, so the
window measured is the engine-pipeline window with $A$'s up-front DMA
excluded for all three kernels.
\paragraph{Async-full (naive).} A single \textsf{tl.load(A)} followed by
a single \textsf{tl.load(B)} (async, queued behind $A$),
\textsf{tl.dot(A, B)}, and \textsf{tl.store(out)}. The kernel issues
four commands total. The decisive constraint is that
\textsf{tl.dot}'s \textsf{\_await\_pending(b)} blocks the GEMM
command until the \emph{entire} $B$ has landed in TCM --- there is no
way at the runtime API surface to express ``start computing on
tile 0 of $B$ while tile 1 is still in flight.'' Load-of-$B$ and
GEMM therefore serialize.
\paragraph{Async-tiled (chunked prefetch).} The kernel-level workaround is to
split $B$ along $K$ into \textsf{TILE\_K}-sized chunks, issue async
\textsf{tl.load}s for those chunks, issue one \textsf{tl.dot} per
chunk (each blocking only on its own $b_i$), and accumulate via
\textsf{out = out + tl.dot(...)}. This is the standard
double-buffered-GEMM pattern transcribed to the single-op primitives.
We measure two versions of this kernel that differ only in
\emph{prefetch depth}:
\textbf{depth-2 (TCM-bounded)} keeps at most two B-chunks in flight
at any time by issuing the next \textsf{tl.load} just before each
\textsf{tl.dot}; \textbf{depth-$\infty$ (queue-all)} issues all $N_K$
B-chunk loads up front so the DMA engine has the deepest possible
request queue. For a $K{=}3072$ shape both versions emit
48 $A$-chunk loads + 48 $B$-chunk loads + 48 \textsf{tl.dot}s + 47
elementwise adds + 1 store = 192 host-side commands; the only
difference is the temporal interleaving of B-load and dot dispatches.
\paragraph{Why two depths.} The depth distinction matters because the
async-full kernel and the depth-$\infty$ async-tiled kernel both pin the
\emph{entire} $B$ in TCM simultaneously --- $K \cdot N \cdot 2$
bytes. The on-PE scratch is capped at \SI{1}{\mebi\byte}
(\texttt{topology.yaml: pe\_tcm.kernel\_scratch\_mb=1}), so an LLM-scale
attention $B = K_{\text{KV}} \times d_{\text{head}}$ at
$K_{\text{KV}}=4096, d_{\text{head}}=128$ already needs
\SI{1}{\mebi\byte}, and at $K_{\text{KV}}=8192$ it needs \SI{2}{\mebi\byte}
--- past the cap. async-full and queue-all async-tiled are therefore
not just slower than composite but \emph{architecturally infeasible}
at LLM context length. The depth-2 async-tiled kernel is the only
kernel-level
variant whose peak TCM footprint stays
$O(2 \cdot \textsf{TILE\_K} \cdot N)$ regardless of $K$, the same
order as composite's per-tile streaming buffer. It is the apples-to-apples
comparison.
\paragraph{Per-PE throughput.} Figure~\ref{fig:gemm-async} reports
per-PE TFLOP/s for all four kernels side-by-side. The single-op
fast-path in the dispatch cost model (\S\ref{sec:congestion}) is
enabled, so every single-op command the async kernels emit --- DMA
descriptors and \textsf{tl.dot}/\textsf{tl.add} alike --- is charged the
light 8-cycle \textsf{FIXED}, not the 40-cycle composite control-path
cost; neither async-tiled variant carries an inflated per-command cost.
\paragraph{The $K{=}3072$ corner, concretely.} We take this shape
($M{=}32, K{=}3072, N{=}32$) as the running example throughout the
mechanism discussion because it is the regime where the four
kernels spread the widest --- the deepest $K$ in the sweep maps
onto $K / \textsf{TILE\_K} = 48$ hardware tiles, so per-tile costs
amplify into the largest measurable gap. The work content is
identical for all four kernels: $\sim$6.3 M f16 MACs and
$\sim$386 KiB of $B$ traffic from HBM, which together require
$\sim$786 ns of GEMM-engine compute and $\sim$781 ns of DMA on a
saturated per-PE link. What differs is the number of host commands
the same work is decomposed into --- 2 for composite (one
\textsf{tl.load(A)} plus one composite), 4 for async-full, and 192
for either async-tiled variant (48 $A$-loads + 48 $B$-loads + 48
\textsf{tl.dot}s + 47 elementwise adds + 1 store). The
engine-pipeline-window throughput tracks that decomposition closely:
composite reaches \SI{7.18}{\tera\flop\per\second} (post-overlap
limit, only \SI{10}{\percent} below the \SI{8}{\tera\flop\per\second}
per-PE GEMM peak), async-full \SI{3.91}{\tera\flop\per\second}
(DMA and compute serialize on a single big dot), and both async-tiled
variants $\sim$\SI{2.53}{\tera\flop\per\second} (192 commands' worth of
structural dispatch cost --- even at the light per-command rate ---
accumulates on the wall). The next paragraph attributes those gaps to
specific simulator mechanisms.
\begin{figure*}[t]
\centering
\includegraphics[width=\linewidth]{gemm_composite_vs_async_tflops.png}
\caption{Per-PE achieved TFLOP/s for the same shape sweep run under
four issuance patterns: composite (one command, scheduler streams
per-tile internally), async-full (one \textsf{tl.dot} on
fully-loaded $B$), async-tiled with depth-2 double-buffer
(TCM-bounded; the only kernel-level variant that scales to LLM
context length), and async-tiled with depth-$\infty$
(all B-tiles queued up front; included as a sanity check that the
prefetch depth is \emph{not} what separates the async-tiled kernel from
composite). All curves exclude $A$'s up-front DMA from the
measurement window. Composite wins at every full-tile shape; the
depth-2 and depth-$\infty$ async-tiled kernels deliver
\emph{indistinguishable} throughput (e.g. \SI{2.522}{} vs.\
\SI{2.544}{\tera\flop\per\second} at $K{=}3072$), confirming that
prefetch depth is not the lever --- the structural per-command
dispatch cost is. The gap between composite and the async-tiled
kernels grows with $K_{\text{useful}}$
(\textbf{$\sim$$2.8\times$} at $K{=}3072$, where the async-tiled
kernels emit 192 host commands while composite emits one).
The under-tile corner $M{=}128,K{=}8,N{=}128$ inverts: composite's
per-tile orchestration overhead exceeds the per-tile useful work,
and all three async kernels beat it.}
\label{fig:gemm-async}
\end{figure*}
\paragraph{Decomposing the gap.} Three structural mechanisms separate
composite from the kernel-level baselines, and they layer.
\emph{1. Inter-engine token routing happens below the host-side
dispatch path.} The composite encodes the full
\textsf{DMA\_READ}$\to$\textsf{FETCH}$\to$\textsf{GEMM}$\to$\textsf{STORE}$\to$\textsf{DMA\_WRITE}
pipeline once. The scheduler's tile-feeder loop then emits one
\emph{tile token} per HW tile inside that one composite, and each
token self-routes between engines after each stage finishes. The
per-tile token routing is a scheduler-internal event, not a fresh
host command, so it does not pay the structural CPU dispatch cost.
At $K{=}3072$ the composite emits 48 tile tokens that flow
fully-pipelined through five stages each --- 240 inter-engine
hand-offs total --- behind a single command from the host's point of
view.
\emph{2. \textsf{tl.dot} cannot replicate that per-tile pipeline at the
kernel level.} A single-op GEMM command is handled on the GEMM engine as
a single monolithic compute timeout for the supplied $M{\times}K{\times}N$;
there is no internal token loop that would let a streaming DMA of
$B[i{+}1]$ overlap with the GEMM of $B[i]$ inside one
\textsf{tl.dot}. The user can only recover inter-tile overlap by
emitting one \textsf{tl.dot} per chunk --- which is exactly what the
async-tiled baseline does, at the price of $N$ host-side commands.
\emph{3. The host-side dispatch cost the async-tiled baseline pays is
structural, not modelling slack.} KernBench charges every host-emitted
command a structural CPU dispatch cost $d_{\text{cmd}} = \textsf{FIXED} +
b_{\text{logical}} \cdot R$ (\S\ref{sec:congestion}). The cost model is
deliberately charitable to the async kernels here: only a
\textsf{CompositeCmd} pays the 40-cycle control-path \textsf{FIXED} (it
alone drives a scheduler-built tile-feeder plan), while \emph{every}
single-op command --- the 96 DMA descriptors, the 48 \textsf{tl.dot}s,
and the 47 elementwise adds alike --- pays only the light 8-cycle
\textsf{FIXED}, calibrated to descriptor-ring-push / single-instruction
issue patterns in modern accelerators (NVIDIA Hopper TMA $\sim$1 ISA
cycle, a single \textsf{mma.sync} one instruction, AMD AQL packet writes
$\sim$5--15 cycles). So the async-tiled baseline is \emph{not} penalized
by an inflated per-command cost on \emph{any} of its operations. Yet it
still emits 192 host commands against composite's one, so $\sim$192
light dispatches accumulate to $\sim$\SI{1.5}{\micro\second} of
structural PE\_CPU time that composite never pays --- composite hides
its 48 tiles' 240 inter-engine hand-offs as scheduler-internal events
(mechanism 1). Layered on top, the dependency chain on the running
accumulator serializes the 47 adds on the math engine. The net result
is that the async-tiled kernel runs $\sim$2.8$\times$ slower than
composite at $K{=}3072$ despite getting the inter-chunk overlap right
--- down from $\sim$6.3$\times$ under the earlier uniform-40 cost model,
because D8 removed the per-command overcharge, but \emph{not} closed:
the residual gap is the command-count structure (one composite vs.\ 192
single-ops), not a modelling artifact.
\emph{4. Prefetch depth is not the lever, command count is.} The
depth-2 and depth-$\infty$ async-tiled kernels land within \SI{1}{\percent}
of each other at every measured shape (Figure~\ref{fig:gemm-async}).
This is the diagnostic against a natural objection: ``surely the
async-tiled kernel was just under-prefetching; deepen the queue and the
DMA$\to$GEMM overlap recovers.'' Deepening the prefetch queue
changes \emph{when} DMA descriptors hit the engine but not their
total number or the structural dispatch cost they each pay. The
$\sim$2.8$\times$ gap to composite is not a prefetch-depth gap; it
is the gap between
``one composite command with internal per-tile token routing'' and
``$N_K$ host-side dot/add commands, each charged separately.''
The depth-2 kernel additionally constrains peak TCM occupancy to
$O(2 \cdot \textsf{TILE\_K} \cdot N)$, matching composite's per-tile
streaming buffer; the depth-$\infty$ kernel needs the full $B$ in
TCM, which makes it infeasible at LLM context length even if the
throughput were competitive.
\paragraph{Where the composite advantage doesn't apply.} The shape
$M{=}128,K{=}8,N{=}128$ inverts the picture: composite delivers
\SI{0.66}{\tera\flop\per\second} and both async kernels reach
\SI{1.21}{\tera\flop\per\second}. The reason is consistent with the
analysis above and explicit in the simulator state. Composite emits
16 tile tokens (one per output tile) for this shape, each carrying
$K_{\text{useful}}{=}8$ MACs across a TILE\_K$=64$ pipeline ---
$12.5\%$ of the hardware tile's MAC slots are useful, the rest is
K-padding. The per-tile inter-engine hand-off cost stays the same
regardless. When the per-tile useful work is small enough that
hand-off overhead exceeds the GEMM work itself, a single-op
\textsf{tl.dot} --- which submits one monolithic GEMM command with no
per-tile orchestration --- wins. The take-away is the bound on
composite's value: it amortizes useful per-tile compute, not
padding-dominated under-tile shapes. Real kernels at this corner are
better served by reshape-into-batched-GEMM transforms that move
under-tile $K$ into a tile-filling dimension before reaching the GEMM
engine, which is exactly what the GQA decode kernel of
\S\ref{sec:gqa} does for its $K_{\text{useful}}=\text{head\_dim}=128$
inner reduction.
\paragraph{Summary of the comparison.} Composite is the only one of
the four kernels to combine (a) macro-command dispatch at the host
boundary (amortizing the structural CPU cost across all the work a
single GEMM does), (b) scheduler-internal per-HW-tile streaming of
DMA$\rightleftarrows$compute, and (c) TCM-bounded streaming buffer.
Kernel-orchestrated async kernels can have any two of those, not all
three: async-full pays one host dispatch (a) but forfeits per-tile
overlap (b) and pins all of $B$ in TCM (c); depth-$\infty$ async-tiled
achieves inter-chunk overlap but at $N_K$ host dispatches and
full-$B$ TCM occupancy; depth-2 async-tiled fixes the TCM
footprint (c) but still pays $N_K$ host dispatches. The two corners
where async catches up
(small-$K$ where composite has nothing useful to amortize;
under-tile shapes where per-tile useful work is sub-token) are
diagnostic of \emph{where composite is the wrong tool}, not of a
slack the user kernel could close.
@@ -1,46 +1,215 @@
\section{PE\_IPCQ and Collective Communication} \section{PE\_IPCQ and Collective Communication}
\label{sec:allreduce} \label{sec:allreduce}
\subsection{Why it is needed}
Distributing a transformer across devices turns every tensor-parallel Distributing a transformer across devices turns every tensor-parallel
layer into a collective: partial results computed on different PEs, CUBEs, layer into a collective: partial results computed on different PEs, CUBEs,
and SIPs must be summed and redistributed with an all-reduce. If that and SIPs must be summed and redistributed with an all-reduce. Underneath
collective is handled by the host or by a generic DMA path, three problems the algorithm this is fundamentally a PE-to-PE problem---many short
appear. The reduction traffic competes with the kernel's own compute DMA messages flowing between neighbors as the reduction proceeds. The natural
on the same links, causing head-of-line blocking; there is no efficient software realization is a per-direction ring buffer whose head and tail
peer-to-peer ring primitive, so data takes extra hops; and the ordering is pointers the producer and consumer update atomically and poll, but our
hard to make deterministic. The hardware question is how to perform H2 2025 report measured this scheme end-to-end and found that the
collectives \emph{on the device}, overlapped with compute and reproducible atomic-pointer traffic together with the consumer's polling loop dominate
run-to-run. the per-message cost: the queue itself becomes the bottleneck well before
the link runs out of bandwidth, and a pure-SW collective spends most of
its time on metadata rather than on actually moving partials. A second,
orthogonal problem is link sharing---if the collective rides the kernel's
generic DMA path it competes with the GEMM's compute traffic on the same
wires, so a large tile transfer head-of-line-blocks a pending reduction.
This section asks how a dedicated hardware primitive can lift queue
management off the software path entirely and, in the same design, stop
collective traffic from stalling behind compute traffic, so the all-reduce
runs overlapped with compute and at the interconnect's physical limit.
\subsection{Design} \subsection{Design}
KernBench models a dedicated per-PE collective engine, \textbf{PE\_IPCQ} The proposed block is \textbf{PE\_IPCQ} (inter-PE communication queue),
(inter-PE communication queue). It is a control-plane block: it owns the a small controller dropped into every PE next to PE\_DMA, PE\_GEMM,
ring-buffer address arithmetic, head/tail pointers, peer-pointer caches, PE\_MATH, and PE\_TCM. It is a control-plane block---it holds no
backpressure, and the four-direction (N/S/E/W) neighbor map, with eight payload data---and consists of three pieces: a per-direction
ring buffers per PE (four directions $\times$ \{tx, rx\}). Crucially, \emph{QPair register file} (\textasciitilde\SI{576}{\byte} of flip-flops
PE\_IPCQ does \emph{not} move data itself---it delegates the actual covering up to eight directions), a combinational slot-address
transfer to PE\_DMA, keeping a clean control/data split. To stop generator and backpressure comparator, and a credit injector/receiver
collective traffic from blocking compute, PE\_DMA is extended into a wired to the NoC. Each QPair holds the local pointers
two-channel virtual-channel model: \texttt{vc\_compute} carries tile (\texttt{my\_head}, \texttt{my\_tail}), shadowed views of the peer's
load/store for GEMM and vector math, \texttt{vc\_comm} carries IPCQ sends, (\texttt{peer\_head\_cache}, \texttt{peer\_tail\_cache}), the local and
each with an independent state machine. The same physical link is shared peer rx-buffer physical bases, ring depth and slot size (both
but progresses in chunks (\SI{256}{\byte}), so a large GEMM DMA does not power-of-two), and the peer's credit-target address. The ring data
lock the link end-to-end against a pending reduction. On top of this itself lives in a reserved slot region of TCM (or PE-local HBM, or
substrate the collective runs a hierarchical local-reduce / global cube-shared SRAM), addressed by the QPair registers; PE\_IPCQ never
all-reduce-broadcast schedule across whatever inter-device topology the touches the bytes, only the pointers. The PE\_CPU sees the controller
configuration specifies. as an MMIO peripheral and the N/S/E/W direction labels as logical
ports---one kernel image runs across a 1D ring, 2D mesh, or 2D torus
because the topology only changes which peers the QPair registers
point at.
\emph{Initialization.} The host CCL backend brings the whole machine
to a usable state before any kernel runs.
\texttt{init\_process\_group(backend="ahbm")} loads \texttt{ccl.yaml},
resolves the algorithm + topology + buffer\_kind + slot configuration,
allocates an rx ring-buffer region on every participating PE so every
rank now knows every other rank's \texttt{rx\_base\_pa}, and fans out
an \texttt{IpcqInitMsg} that writes the QPair register file on each
PE\_IPCQ over MMIO. The same fan-out wires the per-direction
credit-return channel: each PE\_IPCQ records its peer's credit-target
address and a back-pointer that the credit receiver will use to update
the right QPair. After init, every register the runtime needs is
preloaded; nothing in the kernel path allocates memory, walks a table,
or talks to the host.
\emph{Send.} When the kernel executes \texttt{tl.send(dir="E",
src\_addr, nbytes)}, the PE\_CPU performs a single MMIO write into
PE\_IPCQ describing the request (direction, source address/space,
length, sender handle). The controller evaluates backpressure in one
combinational compare---\texttt{(my\_head $-$ peer\_tail\_cache) $<$
n\_slots}---and, on a hit, the slot-address generator returns
\texttt{dst = peer\_rx\_base\_pa + (my\_head \% n\_slots) $\times$
slot\_size} in one to two cycles. PE\_IPCQ then emits one
\texttt{IpcqDmaToken} on its dedicated port to PE\_DMA's
\texttt{vc\_comm} channel, carrying the data descriptor (\texttt{src,
dst, nbytes}) plus a small piggyback header (\texttt{sender\_seq =
my\_head, src\_coord, direction}). \texttt{my\_head} is incremented in
the same cycle---a local flip-flop bump, not a cross-PE atomic---and
\texttt{tl.send} returns fire-and-forget. If the backpressure compare
fails, the controller stalls the CPU in either of two modes selected
at init: \texttt{poll} (CPU re-reads a status CSR) or \texttt{sleep}
(controller asserts a wake event when a credit arrives). Both modes
are benchmarked in the results.
\emph{Transport.} PE\_DMA is the only block that touches the fabric,
and it has been extended for IPCQ in two ways. First, it now exposes
two virtual channels---\texttt{vc\_compute} for GEMM/Math
\texttt{TileToken}s and \texttt{vc\_comm} for \texttt{IpcqDmaToken}s
---with independent state machines and a chunk-level
(\SI{256}{\byte}) weighted round-robin arbiter on the shared physical
link. A large GEMM tile DMA can no longer monopolize the link against
a pending IPCQ send, enabling compute and communication to make
progress independently as an architectural property of the design. Second, on the sender side PE\_DMA packs the piggyback
header into the same flit train as the data, and on the receiver side
it runs the I6 \emph{atomic terminal handler}: pay the per-flit
bottleneck-BW drain, then, in one indivisible block, (i) write the
payload into \texttt{MemoryStore} at \texttt{dst\_addr} (the
receiver's ring slot) and (ii) forward an \texttt{IpcqMetaArrival}
$\{\texttt{sender\_seq, dst\_addr}\}$ to the local PE\_IPCQ over a
PE-internal wire. No yield, no PE\_CPU interaction, no cache-coherence
round trip---the bytes land in TCM and the metadata reaches the
controller in the same cycle.
\emph{Receive and credit return.} PE\_IPCQ's Meta Extractor
range-matches the incoming \texttt{dst\_addr} against each direction's
$[\texttt{rx\_base}, \texttt{rx\_base} + \texttt{n\_slots} \times
\texttt{slot\_size})$ window---unambiguous even when two directions
share a peer, as in a 2-rank bidirectional ring---and updates
\texttt{peer\_head\_cache[d] := max(prev, sender\_seq + 1)}, releasing
any \texttt{tl.recv} blocked on direction $d$. When the kernel
eventually consumes the slot, the controller increments
\texttt{my\_tail} and emits a \SI{16}{\byte} credit packet on the
dedicated fast-path channel preloaded at init; the latency is the
real fabric path (per-node overhead + edge propagation + 16 B /
bottleneck BW), so an in-cube credit returns faster than a cross-SIP
credit and the model carries no magic constants. The sender's PE\_IPCQ
absorbs the credit, advances \texttt{peer\_tail\_cache}, and
de-asserts backpressure if it was stalled. The pointer-synchronization
problem the software baseline solved with explicit atomic RMWs and
polling loops has been split in two and dissolved into existing
traffic: \emph{head} updates ride on the DMA payload itself, with the
receiver's PE\_DMA performing the data + metadata write in a single
atomic step, so the sender never blocks waiting for the receiver to
see it; \emph{tail} updates ride on a dedicated 16 B credit on a
side-channel, with no software in the loop. Send becomes a single
MMIO write, receive becomes a flip-flop read, backpressure becomes
one 64-bit subtract, and the per-message cost in IPCQ is dominated by
the link traversal rather than by metadata bookkeeping---which is
what the H2 2025 measurement showed the pure-software queue could
not achieve. On top of this substrate the collective runs a
hierarchical local-reduce / global all-reduce-broadcast schedule
across whatever inter-device topology the configuration specifies,
with the IPCQ ring buffer placed in on-PE TCM, PE-local HBM, or
cube-shared SRAM---the third knob the results section sweeps.
\subsection{Design alternatives}
\label{sec:ipcq-alternatives}
PE\_IPCQ is one point in a small space of hardware mechanisms for
moving a short message from one PE to a neighbor and signalling its
arrival. Three established alternatives anchor the space, each the
HW realization of a familiar host-networking idea: a \emph{doorbell +
polling} scheme (the classic MMIO doorbell---write the payload by DMA,
write a doorbell, let the peer poll or take an interrupt); a
\emph{hardware message queue} (HMQ, the NVLink-style descriptor engine
that pushes a queue entry to the peer, with large payloads still
riding a second DMA); and a \emph{completion-queue} design (RDMA-CQ,
the InfiniBand/RoCE pattern where a DMA write auto-posts a completion
entry the peer's CQ polls). PE\_IPCQ is the fourth: a hardware ring
with credit return, splitting the control plane into PE\_IPCQ and the
data plane into PE\_DMA, with head updates riding the payload and tail
updates riding a 16\,B side-channel credit (\S\ref{sec:allreduce}).
\begin{figure*}[t]
\centering
\includegraphics[width=0.78\linewidth]{ipcq_alternatives_architecture_flow.png}
\caption{Per-send data and control flow for the four PE-to-PE
signalling mechanisms (sender\,$\rightarrow$\,NoC\,$\rightarrow$\,receiver).
Doorbell and RDMA-CQ each issue two fabric transactions (payload then
doorbell / completion) and leave the peer polling or taking an
interrupt; HMQ adds a dedicated descriptor engine but still moves large
payloads on a second DMA; PE\_IPCQ folds head-pointer signalling into
the payload flit train and returns the tail credit on a side channel,
so a send is one MMIO write and a receive is a flip-flop read. This is
a \emph{design schematic}, not a measured comparison.}
\label{fig:ipcq-arch}
\end{figure*}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{ipcq_alternatives_decision_matrix.png}
\caption{Why the ring+credit design was chosen, across five criteria:
single-send latency, whether the host CPU sits on the critical path,
whether the receiver must poll or take a wake-up interrupt, whether the
control and data datapaths are duplicated, and whether the mechanism is
right-sized for single-owner PE-to-PE traffic (rather than a
multi-tenant fabric). PE\_IPCQ is the only design that clears every
criterion. The accompanying per-send step-count tally
($\sim$28 control events for IPCQ versus $\sim$38 for HMQ, $\sim$53 for
RDMA-CQ, and $\sim$56 for doorbell+polling) is an \emph{illustrative}
order-of-magnitude comparator over hand-counted pipeline steps---not a
simulator measurement. The measured, simulator-grounded results follow
in the next subsection.}
\label{fig:ipcq-decision}
\end{figure}
The qualitative comparison motivates the design but is not a
quantitative claim: the cycle-step tallies above are hand-counted
control events, deliberately separated from the measured latencies that
follow. Everything in the results subsection runs on the PE\_IPCQ
substrate and is simulator-grounded.
\subsection{Results} \subsection{Results}
Following the milestone-evaluation convention, the collective sweep builds All measurements in this section run on the PE\_IPCQ substrate
its own six-device (six-SIP, $2\times3$) configurations---distinct from described above; the topology sweep is intended to characterize how
the two-SIP default of Table~\ref{tab:hw}---and measures all-reduce effectively the proposed mechanism exposes the underlying interconnect's
latency as a function of payload size for three inter-device topologies: properties, not to compare PE\_IPCQ against an alternative
a 1D ring, a 2D mesh (no wrap), and a 2D torus. Table~\ref{tab:allreduce} communication primitive. Following the milestone-evaluation convention,
and Figure~\ref{fig:allreduce-cmp} report the result. the collective sweep builds its own six-device (six-SIP, $2\times3$)
configurations---distinct from the two-SIP default of
Table~\ref{tab:hw}---and measures all-reduce latency as a function of
payload size for three inter-device topologies: a 1D ring, a 2D mesh
(no wrap), and a 2D torus (Figure~\ref{fig:allreduce-topo}).
Table~\ref{tab:allreduce} and Figure~\ref{fig:allreduce-cmp} report the
result.
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{allreduce_topology.png}
\caption{The three six-device ($2\times3$) inter-device topologies the
collective sweep runs over, and the hierarchical local-reduce /
global all-reduce-broadcast schedule mapped onto each: a 1D ring, a 2D
mesh (no wrap-around), and a 2D torus (wrap-around links on both axes).
The torus's wrap links shorten the worst-case reduction path, which is
what the latency sweep below rewards.}
\label{fig:allreduce-topo}
\end{figure}
\begin{table}[t] \begin{table}[t]
\centering \centering
@@ -52,11 +221,11 @@ per-PE payload. Lower is better; the torus wins at every size.}
\toprule \toprule
\textbf{Bytes/PE} & \textbf{2D mesh} & \textbf{Ring 1D} & \textbf{2D torus} \\ \textbf{Bytes/PE} & \textbf{2D mesh} & \textbf{Ring 1D} & \textbf{2D torus} \\
\midrule \midrule
256 & 2667 & 2365 & 1701 \\ 256 & 4189 & 3883 & 2957 \\
4{,}096 & 4450 & 4082 & 3038 \\ 4{,}096 & 5566 & 5240 & 4031 \\
16{,}384 & 8900 & 8217 & 6403 \\ 16{,}384 & 10016 & 9376 & 7396 \\
65{,}536 & 26705 & 24766 & 19865 \\ 65{,}536 & 27821 & 25925 & 20858 \\
98{,}304 & 38574 & 35798 & 28840 \\ 98{,}304 & 39690 & 36957 & 29833 \\
\bottomrule \bottomrule
\end{tabular} \end{tabular}
\end{table} \end{table}
@@ -64,13 +233,19 @@ per-PE payload. Lower is better; the torus wins at every size.}
\begin{figure}[t] \begin{figure}[t]
\centering \centering
\includegraphics[width=\linewidth]{allreduce_comparison.png} \includegraphics[width=\linewidth]{allreduce_comparison.png}
\caption{All-reduce latency vs.\ per-PE payload for the three topologies, \caption{All-reduce latency vs.\ per-PE payload for the three PE\_IPCQ
against the analytic torus model and an external full-system simulator topologies, against the analytic torus model. The isolated point in
(FSIM) reference. The measured torus tracks the analytic curve within a the top panel (\SI{366}{\micro\second}) is the only data point
small constant factor; the FSIM single-device point available from the H2 2025 software-queue measurement campaign---an
(\SI{366}{\micro\second}) sits an order of magnitude above the intra-device all-reduce across 16 CUBEs on a single device. A true
KernBench algorithmic latency, illustrating the difference between an 6-device inter-SIP measurement under the same software queue was not
achievable-kernel number and a full end-to-end-stack number.} collected, so this point in fact \emph{understates} the SW-queue cost
for the multidevice configuration KernBench measures here; even so the
proposed PE\_IPCQ inter-device curves sit roughly an order of
magnitude below it, which is the headline SW-vs-HW comparison this
section makes. The measured torus tracks the analytic curve within a
small constant factor, the gap reflecting real link serialization that
the analytic model idealizes away.}
\label{fig:allreduce-cmp} \label{fig:allreduce-cmp}
\end{figure} \end{figure}
@@ -78,9 +253,9 @@ achievable-kernel number and a full end-to-end-stack number.}
\centering \centering
\includegraphics[width=\linewidth]{allreduce_buffer_kind.png} \includegraphics[width=\linewidth]{allreduce_buffer_kind.png}
\caption{Effect of IPCQ staging-buffer placement (2D torus). At \caption{Effect of IPCQ staging-buffer placement (2D torus). At
\SI{64}{\kibi\byte}/PE, TCM staging (\SI{19865}{\nano\second}) beats HBM \SI{64}{\kibi\byte}/PE, TCM staging (\SI{20858}{\nano\second}) beats HBM
(\SI{23081}{\nano\second}) by \textasciitilde\SI{14}{\percent} and SRAM (\SI{24074}{\nano\second}) by \textasciitilde\SI{13}{\percent} and SRAM
(\SI{32201}{\nano\second}) by \textasciitilde\SI{38}{\percent}; at small (\SI{33194}{\nano\second}) by \textasciitilde\SI{37}{\percent}; at small
payloads the three are indistinguishable.} payloads the three are indistinguishable.}
\label{fig:allreduce-buf} \label{fig:allreduce-buf}
\end{figure} \end{figure}
@@ -1,8 +1,6 @@
\section{Fused Grouped-Query Attention} \section{Fused Grouped-Query Attention}
\label{sec:gqa} \label{sec:gqa}
\subsection{Why it is needed}
Attention is the 1H focus, and it is where the two preceding optimizations Attention is the 1H focus, and it is where the two preceding optimizations
have to come together. Grouped-Query Attention (GQA) shrinks the KV cache have to come together. Grouped-Query Attention (GQA) shrinks the KV cache
by sharing each KV head across a group of query heads (here $h_q=8$ query by sharing each KV head across a group of query heads (here $h_q=8$ query
@@ -16,108 +14,13 @@ realizing it as a fast \emph{fused} kernel needs both building blocks from
this report: efficient GEMM issue (\S\ref{sec:gemm}) for the this report: efficient GEMM issue (\S\ref{sec:gemm}) for the
$Q\!\cdot\!K^{\top}$ and $P\!\cdot\!V$ products, and an efficient on-device $Q\!\cdot\!K^{\top}$ and $P\!\cdot\!V$ products, and an efficient on-device
reduction (\S\ref{sec:allreduce}) for the multi-user and reduction (\S\ref{sec:allreduce}) for the multi-user and
sequence-parallel KV reductions. This section is the capstone: the fused sequence-parallel KV reductions. \emph{Fused} here is meant in the
FlashAttention sense---$Q\!\cdot\!K^{\top}$, the online softmax, and
$P\!\cdot\!V$ collapse into a single kernel that never materializes the
score matrix---and, beyond that, the cross-device KV reduction is absorbed
into the same kernel (on PE\_IPCQ) rather than issued as a separate
all-reduce. This section is the capstone: the fused
kernel that uses the composite command and PE\_IPCQ at the same time. kernel that uses the composite command and PE\_IPCQ at the same time.
Multi-head attention (MHA) was studied in prior work and serves here as Multi-head attention (MHA) was studied in prior work and serves here as
the established baseline rather than being re-derived. the established baseline rather than being re-derived.
\subsection{Design}
The fused GQA kernel issues its matrix products as scheduler-managed
composite commands and keeps the online-softmax merge and the cross-device
KV reduction inside the kernel, on PE\_IPCQ. Two kernel families cover the
two phases. The \emph{prefill} kernel is head-parallel and rotates the KV
shards around an inter-CUBE ring (``Ring KV''). The \emph{decode} kernel
is head-replicated with a statically sharded KV cache and reduces partial
attention outputs through an M-fold intra-CUBE chain and, for multiple
users, a two-level reduce-to-root. Two further primitives make long
context practical: a \emph{lazy load} that issues the KV \textsf{DMA\_READ}
and returns immediately, auto-waiting only at first use so that KV load
overlaps score computation; and per-tile \emph{scratch recycling} that
keeps the running softmax accumulators ($m,\ell,O$) in a persistent arena
while freeing per-tile temporaries, so the kernel fits the
\SI{1}{\mebi\byte} scratch budget across many tiles. A further refinement
that restructures the decode step into two stateful composites (a named
\textsf{softmax\_merge} recipe) is designed but not yet wired into the
measured path; results below reflect the implemented kernel only.
\subsection{Results}
We measure four headline panels that vary the user count $C$ and the phase:
single- and multi-user prefill ($T_q=4$, $S_{kv}=16$), and single- and
multi-user decode ($P=8$ PEs, $S_{kv}=64$ and $128$), all at $d_{\text{head}}=64$
and $G=8$. For each panel we harvest end-to-end latency
(max event end minus min event start, the same window convention as the
GEMM study) together with the per-engine busy time and the operation mix.
Figure~\ref{fig:gqa-lat} and Figure~\ref{fig:gqa-break} report the result;
the underlying numbers are in Table~\ref{tab:gqa}.
\begin{table}[t]
\centering
\caption{Fused GQA per-panel latency and operation mix. Compute (GEMM,
MATH) is a tiny fraction of DMA occupancy; IPCQ copies grow with users and
PEs.}
\label{tab:gqa}
\small
\begin{tabular}{@{}lrrrr@{}}
\toprule
\textbf{Panel} & \textbf{Lat.\ (ns)} & \textbf{GEMM} & \textbf{IPCQ} & \textbf{DMA rd} \\
\midrule
prefill C=1 & 445 & 2 & 0 & 3 \\
prefill C=4 (Ring) & 4630 & 32 & 24 & 12 \\
decode C=1, P=8 & 3632 & 16 & 21 & 24 \\
decode C=4, P=8 & 6693 & 64 & 93 & 96 \\
\bottomrule
\end{tabular}
\end{table}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_latency_by_panel.png}
\caption{Fused GQA end-to-end latency. Latency grows from
\SI{445}{\nano\second} (single-user prefill) to \SI{6693}{\nano\second}
(four-user decode) as the KV history and the number of participating
devices grow.}
\label{fig:gqa-lat}
\end{figure}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_op_engine_breakdown.png}
\caption{Where the work goes. Left: operation counts---GEMM and IPCQ-copy
volume both scale with users and PEs. Right: summed engine occupancy on a
log scale---the DMA engine dominates by two to three orders of magnitude
over the GEMM and MATH engines in every panel.}
\label{fig:gqa-break}
\end{figure}
The dominant observation is in Figure~\ref{fig:gqa-break}: the compute
engines are almost idle. The GEMM engine accumulates only
\SIrange{2}{33}{\nano\second} of busy time across the panels and the
vector-math engine \SIrange{5}{688}{\nano\second}, while the DMA engine
accumulates \SIrange{72}{15920}{\nano\second}. Fused GQA, as modeled here,
is overwhelmingly data-movement bound. The operation mix shows why the
collective machinery matters: IPCQ-copy count rises from zero (single-user
prefill) to 93 (four-user decode) as the kernel reduces partial outputs
across more PEs and CUBEs, and DMA-read count rises in step as more KV
shards are streamed. The PE control-processor dispatch cost registered as
zero in this configuration---command issue is simply not on the critical
path when data movement is this dominant.
\subsection{Analysis and meaning}
These panels are the clearest statement of the codesign thesis in the
report. Because the composite command keeps GEMM issue cheap and the MAC
array barely occupied, the fused attention kernel's latency is set almost
entirely by data movement: streaming the KV cache and reducing partials
across devices. That is precisely the cost that the communication-side
work targets---PE\_IPCQ for the on-device reduction, the lazy load for
load/compute overlap, fast TCM staging and torus links for the reduction
itself. In other words, the two enablers are not independent features that
happen to appear in the same kernel; the GEMM optimization is what
\emph{exposes} the data-movement bottleneck (by removing the compute and
issue overhead that would otherwise hide it), and the communication
optimization is what \emph{attacks} it. For an attention-dominated decoder
the meaningful hardware investments are therefore the ones that move data
faster and reduce it on-device---not additional MAC throughput, which this
workload cannot use.
@@ -0,0 +1,85 @@
\subsection{Roofline Analysis: Batch and Context Regimes}
\label{sec:roofline}
Before deploying any sharding strategy, the workload's position on the
decode roofline sets what \emph{can} be improved and what \emph{cannot}.
Two quantities matter: the machine's \textbf{critical batch}
$B^\ast = C \cdot b / (2 W)$ (where $C$ is per-PE FLOPs, $W$ is per-PE
HBM bandwidth, $b$ is bytes per parameter), and its \textbf{balance
context} $L^\ast = 2 N / (\mathrm{AI} \cdot \mathrm{kv\_bpt})$ (where
$N$ is active parameters and $\mathrm{AI} = C/W$ is arithmetic
intensity). Above $B^\ast$ the deployment leaves the memory-bound
regime; above $L^\ast$ per-user KV streaming dominates and no amount
of batching hides it. For Llama-3.1-70B on the default AHBM machine
($C \!=\! \SI{8}{TFLOPS}$/PE, $W\!=\! \SI{256}{GB/s}$/PE), we get
$B^\ast \!\approx\! 31$ and $L^\ast \!\approx\! \SI{13.4}{K}$ tokens.
\paragraph{Short-context regime ($S_{kv} \!<\! L^\ast$).}
Figure~\ref{fig:roofline-short} shows one decode step at
$S_{kv}\!=\! \SI{8}{K}$. The step-latency panel (left) makes it
obvious that weight fetch dominates at low $B$: at $B\!=\!1$, one
step spends $\sim\!\SI{535}{ms}$ streaming weights and only
$\SI{28}{ms}$ on per-sequence compute and KV combined. The
cost-per-token panel (right) is where the batch story lives: dividing
step latency by $B$ shrinks the weight term as $1/B$, so per-token
cost drops from $\SI{562}{ms}$ at $B\!=\!1$ to $\SI{29.7}{ms}$ at
$B\!=\!256$ --- a $19\times$ reduction. This is the memory-bound-to-
compute-bound crossover: past $B^\ast$, weight cost is fully
amortized and per-token time asymptotes to the compute + KV floor
($\sim\!\SI{27}{ms}$). \textbf{At short context, batching directly
lowers cost per token.}
\begin{figure*}[h]
\centering
\includegraphics[width=\textwidth]{roofline_short_context.png}
\caption{Roofline decomposition for Llama-3.1-70B decode at
$S_{kv}\!=\! \SI{8}{K}$ (short, well below $L^\ast\!\approx\! \SI{13.4}{K}$).
\textbf{Left}: step latency vs.\ batch $B$; weight fetch is flat
(one HBM sweep per step), compute and KV grow linearly with $B$.
\textbf{Right}: per-token cost ($=$ step $\div B$); the $1/B$
weight-fetch term amortizes rapidly, dropping total per-token time
from \SI{562}{ms} at $B\!=\!1$ to \SI{29.7}{ms} at $B\!=\!256$
--- a $19\times$ throughput gain from batching alone.}
\label{fig:roofline-short}
\end{figure*}
\paragraph{Long-context regime ($S_{kv} \!\gg\! L^\ast$).}
Figure~\ref{fig:roofline-long} shows the same decomposition at
$S_{kv}\!=\! \SI{1}{M}$ ($\sim\!78 \times L^\ast$). The step-latency
panel shows KV fetch has swelled by two orders of magnitude:
per-token KV cost is now $\sim\!\SI{1342}{ms}$, dwarfing both
weight ($\SI{535}{ms}$ at $B\!=\!1$) and compute ($\SI{17}{ms}$).
The cost-per-token panel is the punchline: increasing $B$ still
shrinks the weight term but leaves the giant KV floor untouched,
because \textbf{KV fetch is per-sequence} --- adding another user
adds a full extra copy of their KV read. Total per-token cost falls
only from \SI{1894}{ms} at $B\!=\!1$ to \SI{1361}{ms} at $B\!=\!256$
--- a mere $28\%$ reduction despite $256\times$ the batch.
\textbf{At long context, batching stops paying because per-user KV
streaming dominates.}
\begin{figure*}[h]
\centering
\includegraphics[width=\textwidth]{roofline_long_context.png}
\caption{Same decomposition at $S_{kv}\!=\! \SI{1}{M}$ (long,
$\sim\!78\times L^\ast$). KV fetch has grown to \SI{1342}{ms}/token
and is now the dominant term at every $B$. Batching only shrinks
the weight component; the KV floor is unmovable because each new
user brings a full per-sequence KV read. Total per-token cost falls
just $28\%$ from $B\!=\!1$ to $B\!=\!256$, versus $19\times$ at
short context.}
\label{fig:roofline-long}
\end{figure*}
\paragraph{Implication for deployment.} The two regimes call for
opposite strategies. In the short-context regime, the operator packs
$B$ as high as HBM allows to sit on the compute floor --- this is
where cost-per-token is minimized and hardware utilization is
highest. In the long-context regime, per-user KV is the binding
resource; batching offers little benefit, so the operator instead
shrinks $\mathrm{kv\_bpt}$ (GQA / MQA / MLA, INT4 KV, sparse
attention) and shards the sequence dimension itself (CP), routing
long-context requests to a dedicated pool with more chips per user.
The next two subsections (\S\ref{sec:capacity-planning},
\S\ref{sec:parallelism-selection}) turn these regime observations
into concrete sizing and sharding rules.
@@ -0,0 +1,118 @@
\subsection{Capacity Planning: LLM Serving on AHBM}
\label{sec:capacity-planning}
Once a fused kernel is chosen, the deployment question is orthogonal:
\emph{how many cubes (or SIPs) does a given model, context length, and
latency SLO require?} Hyperscalers decide this along three axes that
must all be satisfied simultaneously. The total PE count is
$\max(A, B, C) \times N_{\text{replicas}}$, where $A$ is the capacity
floor, $B$ the KV-cache headroom, and $C$ the throughput SLO
(Table~\ref{tab:capacity-axes}).
\begin{table}[h]
\centering
\small
\caption{Three-axis sizing. $N$ = active params, $b$ = bytes per param,
$\mathrm{HBM}_{\mathrm{PE}}$ = per-PE HBM budget, $S_{kv}$ = context
length, $\mathrm{kv\_bpt}$ = KV cache bytes per token per user
($=2 \cdot h_{kv} \cdot d_{\text{head}} \cdot b \cdot L$).}
\label{tab:capacity-axes}
\begin{tabular}{@{}l l@{}}
\toprule
Axis & Formula \\
\midrule
A. Capacity floor & $\lceil N b / \mathrm{HBM}_{\mathrm{PE}} \rceil$ \\
B. KV headroom & $\lceil (N b + u \cdot S_{kv} \cdot \mathrm{kv\_bpt}) / \mathrm{HBM}_{\mathrm{PE}} \rceil$ \\
C. Throughput SLO & $N_{\text{replicas}} = \lceil n_{\text{users}} / B_{\mathrm{SLO}} \rceil$ \\
\bottomrule
\end{tabular}
\end{table}
\paragraph{SLO targets.} The per-token latency budget $B_{\mathrm{SLO}}$
follows the use case. TTFT (Time To First Token) is dominated by
prefill; TPOT (Time Per Output Token) by one decode step
(Table~\ref{tab:slo-targets}).
\begin{table}[h]
\centering
\small
\caption{Typical SLO targets. Voice needs a tight per-token cadence;
chat balances both; batch is priced on throughput not latency.}
\label{tab:slo-targets}
\begin{tabular}{@{}l l l@{}}
\toprule
Workload & TTFT & TPOT \\
\midrule
Voice / real-time & 200--300 ms & 10--25 ms \\
Interactive chat & 300 ms -- 1 s & 20--50 ms \\
Batch / offline & seconds -- minutes & N/A \\
\bottomrule
\end{tabular}
\end{table}
\paragraph{Regime-aware rules of thumb.} Context length relative to the
machine's balance point $L^*=2N/(\mathrm{AI}\cdot\mathrm{kv\_bpt})$
determines which strategy applies. Below $L^*$ the deployment is
compute-bound and batch scales to $\sim\!2B^*$; above, KV streaming
dominates and $B$ must shrink (Table~\ref{tab:regime-rules}).
\begin{table*}[t]
\centering
\small
\caption{Deployment strategy by context regime.}
\label{tab:regime-rules}
\begin{tabular}{@{}l l l@{}}
\toprule
Regime & Batch & Cost/token \\
\midrule
Short ($S_{kv} < L^*$) & $B \approx 2 B^*$ & low \\
Long ($S_{kv} > L^*$) & smaller $B$, more CP & higher \\
Extreme ($S_{kv} \gg L^*$) & dedicated pool, disagg.\ prefill & much higher \\
\bottomrule
\end{tabular}
\end{table*}
\paragraph{Sample deployment templates.} A single base model is
typically routed to one of several sharding tiers depending on the
request's expected context length; the API gateway dispatches to the
appropriate replica pool (Table~\ref{tab:deploy-templates}).
\begin{table*}[t]
\centering
\small
\caption{Sample deployment tiers for one base model. TP is fixed to
match $h_{kv}$; CP grows with context; $B$ shrinks to hold TPOT.}
\label{tab:deploy-templates}
\begin{tabular}{@{}l c c c c@{}}
\toprule
Tier & CP$\times$TP & Cubes/repl. & Max $S_{kv}$ & Typical $B$ \\
\midrule
Small & $1 \times 8$ & 1 & 32k & 64 \\
Medium & $4 \times 8$ & 4 & 128k & 32 \\
Large & $32 \times 8$ & 32 & 1M & 4 \\
\bottomrule
\end{tabular}
\end{table*}
\paragraph{What to do when each axis binds.} The dominant axis dictates
the first lever to pull (Table~\ref{tab:axis-playbook}). Axes $A$ and
$C$ scale nearly linearly with hardware; axis $B$ is the one where
algorithmic techniques (GQA, MLA, INT4 KV, sparse attention) buy the
most because they attack $\mathrm{kv\_bpt}$ directly rather than adding
chips.
\begin{table*}[t]
\centering
\small
\caption{Playbook per binding axis.}
\label{tab:axis-playbook}
\begin{tabular}{@{}l l l@{}}
\toprule
Binding & First lever & Second lever \\
\midrule
A. Capacity & $\uparrow$ TP / PP & bigger-HBM chip; FP8 / INT4 weights \\
B. KV & $\uparrow$ CP; $\downarrow B$ & GQA / MQA / MLA; INT4 KV; sparse attn \\
C. Throughput & $\uparrow$ replicas (DP) & loosen SLO; smaller model; spec.\ decoding \\
\bottomrule
\end{tabular}
\end{table*}
@@ -0,0 +1,91 @@
\subsection{Parallelism Selection: TP $\times$ CP $\times$ PP $\times$ DP $\times$ EP}
\label{sec:parallelism-selection}
Given a capacity-feasible layout (\S\ref{sec:capacity-planning}), the
remaining decision is \emph{how to shard}: which axes to enable and at
what degrees. Memory is the feasibility filter; once you fit, the
design problem is minimising \emph{exposed} communication (communication
that could not be overlapped with compute). The core principle is to
\textbf{rank techniques by how much they communicate per unit of
compute, and assign the chattiest ones to the fastest links.}
\begin{table*}[h]
\centering
\small
\caption{Parallelism axes at a glance. TP AllReduce volume does not
shrink with degree — only compute does — which sets its practical
ceiling near a fast-link (NVLink / intra-SIP) domain.}
\label{tab:parallelism-compare}
\begin{tabular}{@{}l l l l l@{}}
\toprule
Axis & Shards & Comm pattern & Frequency & Hard ceiling \\
\midrule
DP & optimizer state (w/ ZeRO) & AllReduce (grads) & 1$\times$/step & critical batch \\
TP & weights + acts + KV heads & AllReduce & 2$\times$/layer & fast-link domain; $\lesssim h_{kv}$ \\
PP & weights + KV by layer & P2P activation hand-off & per stage boundary & $n_{\text{layers}}$ \\
CP & activations + KV by position & Ring P2P per hop & per layer (overlappable) & $\mathrm{seq\_len} / \mathrm{block}$ \\
EP & expert weights (MoE only) & All-to-all & 2$\times$/MoE layer & $n_{\text{experts}}$ \\
\bottomrule
\end{tabular}
\end{table*}
\paragraph{Symptom-driven axis selection.} The dominant problem dictates
the first knob to try (Table~\ref{tab:parallelism-problem}).
\begin{table}[h]
\centering
\small
\caption{Which axis to reach for first, by dominant problem.}
\label{tab:parallelism-problem}
\begin{tabular}{@{}l l@{}}
\toprule
Dominant problem & First axis \\
\midrule
Weight memory doesn't fit & TP within a fast domain \\
KV of ONE long sequence & CP \\
KV for MANY sequences & DP replicas \\
Single-request TPOT & TP ($\lesssim 8$) \\
Aggregate throughput & Outer DP \\
Model spans multiple nodes & TP intra-node, PP inter-node \\
MoE expert weight memory & EP \\
\bottomrule
\end{tabular}
\end{table}
\paragraph{When to add, when to stop.} Every axis has a natural
saturation point past which further degree hurts more than it helps
(Table~\ref{tab:parallelism-criteria}). Sizing decisions are almost
always constrained by two axes simultaneously (e.g.\ TP by NVLink
domain and $h_{kv}$; CP by ring-hop hiding and per-rank block size).
\begin{table*}[t]
\centering
\small
\caption{Add / stop criteria per axis.}
\label{tab:parallelism-criteria}
\begin{tabular}{@{}l l l@{}}
\toprule
Axis & Add when & Stop when \\
\midrule
DP & more independent requests & global batch $>$ critical \\
TP & weights need sharding or TPOT tight & collectives dominate; local GEMMs shrink \\
PP & depth too large; TP would cross slow links & bubble $>$ $\sim$10\% \\
CP & one sequence needs position sharding & ring hops can't overlap compute \\
EP & MoE expert memory & expert GEMMs too small; routing imbalance \\
\bottomrule
\end{tabular}
\end{table*}
\paragraph{Common misconceptions.} Three widely repeated rules survive
contact with real workloads only partially:
\emph{(i)} memory is not just a feasibility filter but a continuous
performance variable --- more free HBM enables larger $B$, more prefix
cache, higher throughput even after weights fit;
\emph{(ii)} the ``TP $\leq h_{kv}$'' ceiling is an efficiency
preference, not a correctness constraint --- vLLM and others support
KV-head replication and head-dim splitting for TP $>$ $h_{kv}$;
\emph{(iii)} ``always start at TP=8'' is disproven by public
disclosures (DeepSeek-V3 inference uses TP=4, some MLPs at TP=1;
DeepSeek-V3 training uses PP=16 + EP=64 with no TP), which shows the
right starting point is the smallest TP that fits memory and meets
latency, followed by measurement.
@@ -0,0 +1,515 @@
\subsection{Data Placement Policy}
\label{sec:gqa-placement}
Long-context decode is bound by the KV cache, so the first-order design
question is how to place that cache---and the running softmax state it
feeds---across the two hardware axes the machine exposes: the CUBEs and,
within each CUBE, the PEs (here $C{=}8$ CUBEs $\times$ $P{=}8$ PEs, for
$C\!\cdot\!P{=}64$ attention engines over one KV-head group on the
LLaMA-3.1-70B target). Each axis can \emph{replicate} the KV cache or
\emph{shard} it, and a shard can run along the sequence dimension
$S_{kv}$ or the head dimension $d_{\text{head}}$. The cross product is a
small, enumerable taxonomy; six placements span its meaningful corners
(Figure~\ref{fig:gqa-kv-sharding}).
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_long_ctx_6cases_kv_sharding_diagram.png}
\caption{The six KV-placement strategies, drawn on the
$S_{kv}\!\times\!d_{\text{head}}$ KV tensor (rows = sequence, columns =
head dimension). Cube colour bands and dashed PE dividers show which
axis each level shards. Cases~1--3 either replicate the cache or shard
it on a single axis (8-way at most); Cases~4--6 reach a full 64-way
split, three different ways: Case~4 splits $S_{kv}$ across CUBEs and
$d_{\text{head}}$ across PEs, Case~5 the mirror, and Case~6~$\star$
splits $S_{kv}$ on \emph{both} axes.}
\label{fig:gqa-kv-sharding}
\end{figure}
Two quantities decide which placement is viable, and they pull against
each other. The first is \textbf{per-PE KV memory}. With a per-PE HBM
budget of \SI{6.0}{\giga\byte} and \SI{1.76}{\giga\byte} of attention
weights resident, the KV headroom is \SI{4.24}{\giga\byte} per PE. At a
production context of $S_{kv}{=}1\,\text{M}$ tokens the unsharded cache
is \SI{40}{\giga\byte}/PE (Case~1), an 8-way shard is \SI{5}{\giga\byte}
(Cases~2--3)---both \emph{over} the headroom---while only the 64-way
placements bring it to \SI{640}{\mega\byte}/PE (Cases~4--6), comfortably
inside budget. Memory alone therefore eliminates Cases~1--3 at long
context. The second quantity is \textbf{communication per token}, and it
is what separates the three survivors. Sharding $d_{\text{head}}$
(Cases~4--5) makes each PE hold only a slice of every head, so the
$Q\!\cdot\!K^{\top}$ score is \emph{partial} and must be all-reduced
across the slice owners on every token---a reduction whose volume scales
with $S_{kv}$ ($\sim$\SI{166}{\mega\byte}/token analytically, intra-CUBE
on the NoC for Case~4, inter-CUBE on UCIe for Case~5). Case~6~$\star$
instead shards $S_{kv}$ on both axes, so every PE computes a
\emph{complete} score over its own token range and only the small running
softmax state $(m,\ell,O)$ is merged across PEs
($\sim$\SI{6.2}{\mega\byte}/token)---a $\sim$27$\times$ lighter collective
than the $d_{\text{head}}$-split designs.
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_long_ctx_6cases_summary.png}
\caption{Long-context placement analysis at $S_{kv}{=}1\,\text{M}$ tokens.
\emph{Left:} the per-PE HBM budget---\SI{1.76}{\giga\byte} of attention
weights leave \SI{4.24}{\giga\byte} of KV headroom (red line).
\emph{Middle:} per-PE KV memory per case (log scale); only the 64-way
placements (Cases~4--6, \SI{640}{\mega\byte}) clear the headroom, while
the unsharded (\SI{40}{\giga\byte}) and 8-way (\SI{5}{\giga\byte}) cases
overflow. \emph{Right:} analytical communication per token (log scale);
the $d_{\text{head}}$-split Cases~4--5 pay a partial-score all-reduce
($\sim$\SI{166}{\mega\byte}/token) that the both-axes-$S_{kv}$ split of
Case~6~$\star$ avoids ($\sim$\SI{6.2}{\mega\byte}/token, merging only the
softmax state). On these two axes Case~6 (marked $\star$ in the figure)
is the single placement that lands both inside the memory budget and at
low per-token communication; whether that combination is the right one to
pick is a regime-specific question taken up next.}
\label{fig:gqa-budget}
\end{figure}
These two costs---per-PE KV memory and per-token communication---are
intrinsic properties of each placement, fixed by how it shards the cache
and independent of the workload regime. They do not by themselves name a
winner: a short prompt where the whole cache fits on one PE values low
communication and tolerates replication, whereas a million-token decode
is bound by the memory wall and will pay communication to escape it.
Which placement is appropriate is therefore a per-regime question, which
the short- and long-context subsections that follow answer by running the
options on the simulator and reading off latency, traffic, and the
redundant compute each one induces.
\subsection{Inference with Short-Context Length}
\label{sec:gqa-short}
The fused GQA kernel issues its matrix products as scheduler-managed
composite commands and keeps the online-softmax merge and the cross-device
KV reduction inside the kernel, on PE\_IPCQ. Two kernel families cover the
two phases. The \emph{prefill} kernel splits the query tile across the PEs
of a group and broadcasts each KV tile from the group's root PE over
intra-CUBE IPCQ, so the HBM K/V read is paid once per group instead of
once per PE. The \emph{decode} kernel sequence-shards the KV cache across
the same group of PEs, runs per-PE local attention, then chain-reduces the
partial $(m,\ell,O)$ triples back up to the group root. Two further
primitives make long context practical: a \emph{lazy load} that issues the
KV \textsf{DMA\_READ} and returns immediately, auto-waiting only at first
use so KV load overlaps score computation; and per-tile \emph{scratch
recycling} that keeps the running accumulators in a persistent arena while
freeing per-tile temporaries, so the kernel fits the
\SI{1}{\mebi\byte} scratch budget across many tiles. A further refinement
restructures the decode step so the per-tile matrix products and the
online-softmax merge are issued as \emph{composite} commands rather than
hand-tiled primitives; \S\ref{sec:gqa-composite} measures it against the
primitive baseline at long context.
This is a different decomposition from the six-case placement taxonomy of
\S\ref{sec:gqa-placement}: that taxonomy spreads a single KV-head group
across all 64 PEs and is the right lens for long context
(\S\ref{sec:gqa-long}), whereas short context---where the whole cache fits
comfortably---turns instead on how the eight KV heads are distributed
across the eight CUBEs. The design question for short context is therefore
not whether to fuse softmax---that is settled---but how to distribute the
eight KV heads across the eight CUBEs of a SIP. Four mappings cover the spectrum, named
by KV-head count per CUBE: \textsf{1-kv-per-cube} dedicates one whole
CUBE to each head and uses all eight PEs of that CUBE on the head's
sequence shard; \textsf{2-kv-per-cube} packs two heads per CUBE with
group size four; \textsf{4-kv-per-cube} packs four heads with group size
two; \textsf{8-kv-per-cube} loads all heads onto a single CUBE with one
PE per head. The mapping fixes a trade-off: as KV heads per CUBE grows, the per-CUBE KV
footprint grows linearly and the mapping uses proportionally fewer CUBEs
(eight down to one), but the intra-CUBE IPCQ broadcast/reduce cost shrinks
to zero (8-kv-per-cube uses a single PE per head and has no group
communication).
A second axis is how each GEMM tile is issued. We isolate this with three
\emph{composite tiers} applied to the same mapping: \textsf{without
composite} uses primitive \textsf{tl.dot} and unfused softmax;
\textsf{with composite (GEMM-only)} issues the GEMMs as
\textsf{tl.composite(op="gemm")} commands so the scheduler can pipeline
the tile stages but leaves softmax as primitives; \textsf{with composite
+ softmax\_merge} additionally folds the softmax fold into the $P\!\cdot\!V$
composite through a named \textsf{softmax\_merge} prologue recipe.
We sweep all four mappings $\times$ three composite tiers $\times$
$S_{kv}\in\{8\text{K},16\text{K},32\text{K},64\text{K}\}$ for both
phases (prefill uses $T_q{=}8$ sliced tiles; decode uses $T_q{=}1$). The
headline latency comparison is in Figure~\ref{fig:gqa-short-wall-baseline}.
The mappings separate cleanly, and in proportion to how many PEs each
dedicates to a head: \textsf{1-kv-per-cube} spreads one head across all
eight PEs of its CUBE, \textsf{8-kv-per-cube} gives each head a single PE,
and the four mappings form a $\{64,32,16,8\}$-active-PE ladder. Decode
latency tracks that ladder inversely---at $S_{kv}{=}64$K the
\textsf{8-kv-per-cube}/\textsf{1-kv-per-cube} ratio is $7.4\times$
($\SI{87.0}{}$ vs.\ $\SI{11.8}{\micro\second}$), and even at $8$K it is
$4.8\times$ ($\SI{10.8}{}$ vs.\ $\SI{2.2}{\micro\second}$); prefill shows
the same ordering more gently ($2.3\times$ at $64$K). The reason is that
decode here is \emph{bandwidth-bound}: each active PE streams its KV shard
at \SI{46}{}--\SI{76}{\percent} of the \SI{256}{\giga\byte\per\second}
per-PE ceiling (rising with context), so a mapping's speed is set by how
many PEs it puts to work---more PEs, more aggregate HBM bandwidth, lower
latency. The $M{=}G{=}8$ score GEMM is skinny and leaves the MAC array
lightly loaded throughout; the lever here is data movement, not compute.
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_short_context/wall_variant1_mode_compare.png}
\caption{Wall-clock latency of the four short-context mappings, baseline
kernel (no composite; log scale). The mappings separate along the
$\{64,32,16,8\}$-active-PE ladder set by heads-per-CUBE:
\textsf{1-kv-per-cube} (64 PEs) is fastest and \textsf{8-kv-per-cube}
(8 PEs) slowest, by $4.8\times$ at $8$K growing to $7.4\times$ at $64$K in
decode ($2.3\times$ at $64$K in prefill). Decode is bandwidth-bound, so
latency scales inversely with the number of active PEs. Composite tiers
track the baseline at this skinny shape
(Figure~\ref{fig:gqa-short-a1-variant}).}
\label{fig:gqa-short-wall-baseline}
\end{figure}
Figure~\ref{fig:gqa-short-tradeoff} shows what the mappings trade for that
latency. The differentiator is \emph{density}: \textsf{8-kv-per-cube}
concentrates all eight heads onto one CUBE, so its per-CUBE KV footprint is
$8\times$ that of \textsf{1-kv-per-cube} (one CUBE per head), and it
occupies a single one of the SIP's sixteen CUBEs against
\textsf{1-kv-per-cube}'s eight. Per-PE HBM \emph{utilization}, by contrast,
is similar across mappings---every active PE runs near the same
\SI{46}{}--\SI{76}{\percent} of the per-PE ceiling---so the latency ladder
comes from the \emph{count} of active PEs, not from any per-PE
concentration. IPCQ traffic runs opposite to density:
\textsf{1-kv-per-cube} pays the eight-PE chain-reduce while
\textsf{8-kv-per-cube} (one PE per head) pays none, but absolute IPCQ volume
stays under \SI{120}{\kibi\byte} per run, two orders of magnitude below the
HBM traffic. The result is a genuine trade-off rather than a single winner:
\textsf{1-kv-per-cube} minimizes per-request latency by spending the most
hardware (eight CUBEs, 64 PEs) on one request, while denser mappings leave
CUBEs free for other requests---the batched-serving axis taken up below.
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_short_context/per_cube_tradeoff_mode_compare.png}
\caption{Per-CUBE trade-off across the four mappings (baseline kernel;
log scale). Top row: per-CUBE KV footprint---\textsf{8-kv-per-cube} packs
all eight heads onto one CUBE, an $8\times$ larger per-CUBE KV than
\textsf{1-kv-per-cube} and one CUBE used against eight (the $8\times$ ratio
is context-invariant; the absolute footprint grows with $S_{kv}$). Bottom
row: IPCQ traffic per run---\textsf{1-kv-per-cube} pays the eight-PE
chain-reduce while \textsf{8-kv-per-cube} (single PE per head) pays zero.
The density (top) is what sets how many concurrent requests a SIP can hold.}
\label{fig:gqa-short-tradeoff}
\end{figure}
The composite ablation in Figure~\ref{fig:gqa-short-a1-variant} shows the
three tiers within \textsf{1-kv-per-cube}: wall-clock is nearly flat, with
the GEMM-only composite tier a few percent \emph{slower}
(\SI{14.0}{} vs.\ \SI{11.8}{\micro\second} at $64$K decode) and the
\textsf{softmax\_merge} tier matching the baseline. This is the expected
outcome for the decode-skinny shape---$M{=}G{=}8$ is well below the
scheduler's $\textsf{TILE\_M}{=}32$ supertile (\S\ref{sec:gemm}), so the
composite path pads $M$ by $4\times$ with zeros and the fusion has no slack
to win back; the padding is pure overhead here. It shows up in
Figure~\ref{fig:gqa-short-gemm-util} as inflated GEMM-engine busy time on
the composite tiers---the padded-$M$ GEMM is charged in full against a
sub-\SI{15}{\micro\second} decode wall, driving the accounted utilization
well above the baseline's \SI{12}{}--\SI{18}{\percent}. Fusion benefit
requires lifting $M$, either by the long-context Q-tile split
(\S\ref{sec:gqa-composite}) or by batching multiple users---which is also
the lever that converts a mapping's density into throughput.
\paragraph{Latency versus batched throughput.} The latency ladder and the
density trade-off pull in opposite directions once a SIP serves more than
one request. Decode users are independent---each attends its own KV
cache---and these mappings generate no cross-CUBE traffic, so a SIP runs
several users at once on disjoint CUBE groups, up to $16/C$ users: two for
\textsf{1-kv-per-cube}, sixteen for \textsf{8-kv-per-cube}. We measure this
directly, launching $B$ concurrent users and reading aggregate latency off
the shared clock (Figure~\ref{fig:gqa-batch}). The overlap is nearly
perfect: aggregate latency stays within \SI{3}{}--\SI{5}{\percent} of the
single-user latency all the way to a full SIP (\textsf{8-kv-per-cube} at
$16$ users is \SI{11.4}{} vs.\ \SI{10.8}{\micro\second}), so that small
residual is the only cross-user interference the shared fabric adds.
Aggregate throughput at $8$K therefore rises almost linearly with $B$, from
\SI{0.86}{}~requests\,/\,\si{\micro\second} (\textsf{1-kv-per-cube}, capped
at two users) to \SI{1.41}{} (\textsf{8-kv-per-cube}, sixteen users): the
\emph{dense} mapping wins on throughput even though it is the \emph{slowest}
per request.
The reason is worth spelling out, because both mappings fill the same
hardware: at capacity \textsf{1-kv-per-cube} runs two users
$\times$ 64 PEs and \textsf{8-kv-per-cube} sixteen users $\times$ 8 PEs, so
each puts all 128 PEs of the SIP to work. What differs is per-PE
\emph{efficiency}. Splitting one head across eight PEs
(\textsf{1-kv-per-cube}) leaves each PE only $S_{kv}/8$ tokens---a single
tile---so its fixed overheads (pipeline fill on the lazy KV load, and the
eight-PE online-softmax chain-reduce) dominate the little streaming work it
does, and it sustains just \SI{46}{\percent} of the per-PE HBM ceiling.
Giving each head a whole PE (\textsf{8-kv-per-cube}) streams the full
$S_{kv}$-token cache contiguously with no reduce, amortizing those overheads
over $8\times$ more work and reaching \SI{76}{\percent}. The measured
throughput ratio ($1.41/0.86 = 1.6\times$) matches the utilization ratio
($0.76/0.46 = 1.7\times$) almost exactly: because decode is
bandwidth-bound, aggregate throughput is set by total HBM efficiency, not
by PE count. \textsf{1-kv-per-cube} is really spending hardware on
\emph{latency}---eight PEs per head buy only a $4.8\times$ speedup, a $60\%$
strong-scaling efficiency---and that same $40\%$ overhead is what caps its
throughput once the SIP is full. The design conclusion is regime-dependent: \textsf{1-kv-per-cube} is the
latency-optimal choice for single-stream or low-batch decode, while denser
mappings are the throughput-optimal choice for high-batch short-context
serving. At long context the per-request latencies already scale as $1/C$,
so the same accounting predicts the throughputs converge; a measured
long-context batch sweep is 2H work (\S\ref{sec:future}).
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_short_context/batch_scaling.png}
\caption{Batch scaling at $S_{kv}{=}8$K: aggregate throughput (requests per
\si{\micro\second}) versus the number of concurrent decode users on one SIP,
each user on a disjoint CUBE group. Each mapping scales almost linearly with
$B$ up to its SIP capacity $16/C$---\textsf{1-kv-per-cube} saturates at two
users, \textsf{8-kv-per-cube} at sixteen. Solid segments are measured
concurrent runs; the dashed extensions are the saturated-throughput ceiling
beyond capacity, where further users run in waves at that sustained rate
(so the SIP is full, not idle). The dense mapping reaches the highest
ceiling; the spread mapping is latency-optimal but capacity-limited.
Concurrent users overlap to within \SI{3}{}--\SI{5}{\percent} of the
single-user latency.}
\label{fig:gqa-batch}
\end{figure}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_short_context/wall_a1_variant_compare.png}
\caption{Composite-tier ablation on \textsf{1-kv-per-cube} (log scale):
wall-clock is nearly flat across tiers---the GEMM-only composite runs a few
percent slower (padding overhead) and \textsf{softmax\_merge} matches the
baseline. With $M{=}G{=}8 < \textsf{TILE\_M}{=}32$ the composite scheduler
pads $M$ by $4\times$, leaving no fusion slack to recover at this shape.
Fusion pays off only when $M$ fills the supertile---long-context Q-tile
splitting or batched-$M$ inference.}
\label{fig:gqa-short-a1-variant}
\end{figure}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_short_context/gemm_util_a1_variant_ablation.png}
\caption{GEMM-engine busy fraction on \textsf{1-kv-per-cube} across the
three composite tiers. The baseline runs at \SI{12}{}--\SI{18}{\percent};
the composite tiers read much higher---up to and past
\SI{100}{\percent} of the short decode wall---because the padded
$8\!\to\!32$ $M$ dimension is charged as GEMM time, bookkeeping overhead
rather than useful work. The engine is never the bottleneck at this shape;
decode is KV-bandwidth-bound.}
\label{fig:gqa-short-gemm-util}
\end{figure}
\subsection{Inference with Long-Context Length}
\label{sec:gqa-long}
% TODO: prefill long-context kernel implementation description
% (Sequence-Parallel partition of S_kv, per-case mechanics).
% Bench code: src/kernbench/benches/gqa_helpers/long_ctx/
% TODO: prefill long-context performance figure.
Long-context decode is the regime where the KV cache, not attention
compute, sets serving cost, so the placement question of
\S\ref{sec:gqa-placement} becomes decisive here. To pick the right
placement for this regime we run each of the six options as a fused
decode kernel on the simulator (one decode step on the LLaMA-3.1-70B
single-KV-head-group target, $C{=}8$ CUBEs $\times$ $P{=}8$ PEs) at a
tractable $S_{kv}{=}8192$, and read off end-to-end latency, on-device op
traffic, and the redundant compute each one induces. The swept context is
small enough that all six fit in memory at $S_{kv}{=}8192$; the
placements the long-context memory budget rules out (Cases~1--3) are
drawn in red, run here only to expose their issue and communication
structure.
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_decode_long_ctx_6cases_latency.png}
\caption{Measured end-to-end decode latency per placement
($S_{kv}{=}8192$; red = ruled out by the long-context memory budget,
blue = the predicted Pareto choice). The fastest raw latency belongs to
Case~3 (\SI{17.8}{\micro\second})---but Case~3 replicates the full KV
cache into every CUBE, so it overflows the per-PE budget at production
context and wastes 8$\times$ the compute
(Figure~\ref{fig:gqa-6cases-par}). Among the placements that actually fit
1\,M-token memory (Cases~4--6), Case~6~$\star$ is the fastest
(\SI{30.6}{\micro\second}, versus \SI{31.4}{} and \SI{34.5}{\micro\second}
for the $d_{\text{head}}$-split Cases~4 and~5)---making it the placement
of choice for long-context decode.}
\label{fig:gqa-6cases-lat}
\end{figure}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_decode_long_ctx_6cases_parallelism.png}
\caption{Redundant compute per placement, measured as active-PE
$\times$ $S_{\text{local}}$ (PE-tokens; lower means less wasted work).
The minimum is \num{8192} PE-tokens---one pass over the sequence.
Case~3 inflates this 8$\times$ to \num{65536} by replicating the KV
cache across all eight CUBEs so every CUBE redundantly re-attends the
whole sequence; the $d_{\text{head}}$-split Cases~4--5 likewise carry
\num{65536} because each token is processed across eight head slices.
Case~6~$\star$ achieves the full 64-way split at the minimal
\num{8192} PE-tokens---fully parallel, no replication.}
\label{fig:gqa-6cases-par}
\end{figure}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_decode_long_ctx_6cases_traffic.png}
\caption{Measured on-device op traffic per placement. The unsharded
Case~1 issues no IPCQ copies (each PE has the full cache, nothing to
reduce); the single-axis Case~3 charges 168. Among the 64-way splits,
the $d_{\text{head}}$-split Cases~4--5 charge the most---280 IPCQ copies
and 8 DMA writes each, the partial-score all-reduce that head-slicing
forces---while Case~6~$\star$ needs only 189 IPCQ copies and a single DMA
write, because it merges just the running softmax state $(m,\ell,O)$
rather than partial scores. This is the on-device collective traffic that
PE\_IPCQ and the torus links of \S\ref{sec:allreduce} are provisioned to
absorb at link speed.}
\label{fig:gqa-6cases-traffic}
\end{figure}
Taken together the three panels select the placement. The only
memory-feasible family at production context is the 64-way splits
(Cases~4--6), and within it Case~6~$\star$ is both the fastest and the
lightest-communicating, because it merges only the running softmax state
rather than the partial scores that the $d_{\text{head}}$-split
Cases~4--5 must all-reduce. Case~3's lower raw latency is beside the
point---it replicates the full cache into every CUBE, overflowing the
per-PE budget and wasting $8\times$ the compute. For long-context decode
the placement of choice is therefore the both-axes sequence shard, and the
cross-PE softmax reduction it does pay is precisely the traffic the
communication-side codesign of this report is built to move quickly.
\subsection{Use of Composite Commands}
\label{sec:gqa-composite}
The decode kernel of \S\ref{sec:gqa-long} issues its local attention as
primitive operations: it walks each PE's $S_{\text{local}}$ token slice in
\SI{1024}{}-token tiles, and for every tile issues a $Q\!\cdot\!K^{\top}$
\textsf{dot}, the online-softmax primitives, and a $P\!\cdot\!V$
\textsf{dot}, merging the running $(m,\ell,O)$ state by hand. The number
of PE\_CPU commands this costs grows with the context. At a production
context of $S_{kv}{=}1\,\text{M}$ tokens the Case-6 64-way split gives
each PE $S_{\text{local}}{=}16384$ tokens, so its local attention is a
$Q\!\cdot\!K^{\top}$ of $(8,128)\!\cdot\!(128,16384)$ and a
$P\!\cdot\!V$ of $(8,16384)\!\cdot\!(16384,128)$---sixteen hand-issued
tiles, each a fresh batch of CPU commands.
The composite command lets the kernel hand that tiling to PE\_SCHEDULER.
We compare three command forms of the \emph{same} Case-6 kernel---identical
placement and identical $(m,\ell,O)$ reduce, differing only in how the
local attention is issued:
\begin{itemize}
\item \textbf{primitive}---the hand-tiled \textsf{dot}/softmax kernel
above (the baseline of \S\ref{sec:gqa-long}).
\item \textbf{composite}---each matrix product is one coarse
\textsf{composite} GEMM over the \emph{whole} $S_{\text{local}}$, with
$K$ and $V$ passed as HBM references so PE\_SCHEDULER streams and
tiles them on the fixed $32\!\times\!64\!\times\!32$ MAC tile; the
softmax stays primitive.
\item \textbf{composite\,+\,softmax\_merge}---additionally folds the
online-softmax merge and $P\!\cdot\!V$ into a single stateful
\textsf{softmax\_merge} recipe composite.
\end{itemize}
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_decode_long_ctx_composite.png}
\caption{Three command forms of the Case-6 decode kernel, swept over
context length ($S_{\text{local}}{=}S_{kv}/64$ per PE). \emph{Right:}
PE\_CPU commands issued. The hand-tiled primitive kernel rises
$O(n_{\text{tiles}})$---from 96 commands at one tile to 426 at the
1\,M-token, sixteen-tile production point---while both composite forms
issue a context-\emph{independent} $O(1)$ count (94 and 98) that
\emph{saturates}: one coarse descriptor offloads the entire per-tile
fan-out. \emph{Left:} the consequence for wall-clock latency is none---all
three land on the same curve (\SI{30.6}{}, \SI{231}{},
\SI{461}{\micro\second} at 8\,K\,/\,64\,K\,/\,128\,K), because decode is
bound by streaming the KV cache, not by issue. Command-count is measured
at emit time (exact, to 1\,M); latency on the data-mode engine over the
tractable range.}
\label{fig:gqa-composite}
\end{figure}
Figure~\ref{fig:gqa-composite} reads off the two quantities that matter,
and they point in opposite directions. The PE\_CPU command count (right)
collapses from a context-growing $O(n_{\text{tiles}})$ to a flat $O(1)$:
at 1\,M tokens the composite form issues \num{94} commands against the
primitive kernel's \num{426}, a $4.5\times$ reduction
($4.2\times$ in modeled dispatch cost), and---crucially---that number no
longer grows with context. The wall-clock latency (left), by contrast,
is unchanged across all three forms: decode is bound by streaming the KV
cache out of HBM, so the command form does not move the critical path.
That juxtaposition is the point. The composite command is not a latency
optimization for this memory-bound decode; it is a \emph{CPU-issue}
optimization. Its value is removing the per-tile dispatch work that would
otherwise grow without bound as context grows, freeing PE\_CPU to run
ahead and keep the engines fed---which is exactly what lets the
data-movement cost analyzed next show through as the true bottleneck
rather than being masked by issue overhead. The \textsf{softmax\_merge}
recipe folds the online merge into the same descriptor; on this
memory-bound path its marginal cost over the plain GEMM composite is small
(98 vs.\ 94 commands), and like the plain composite it keeps the issued
count flat as context scales.
\paragraph{The compute-bound mirror: prefill.} Decode's verdict---command
form is latency-neutral---is a property of its regime, not of the
composite command. A decode step has $T_q{=}1$, so its score and context
products are skinny ($M{=}G\,T_q{=}8$): the MAC array is barely fed and
the kernel is bound by streaming the KV cache. Prefill is the opposite
corner. It processes a block of query positions at once, so $M{=}G\,T_q$
is large and tile-filling, the GEMMs carry real arithmetic intensity
($\sim$$M$ flops/byte, well above the roofline ridge), and the kernel is
\emph{compute-bound}. This is the regime the composite command was built
for (\S\ref{sec:gemm}): it streams the per-HW-tile
DMA$\rightleftarrows$compute pipeline so the MAC array stays fed, whereas
the primitive kernel's blocking \textsf{tl.dot} serializes each tile's
load and compute and starves the array between tiles. We run the same
three command forms on a single-rank compute-bound prefill (FlashAttention
$Q$-block $\times$ $S_{kv}$-tile, online softmax) and sweep the context
length (Figure~\ref{fig:gqa-prefill-cb}).
\begin{figure}[t]
\centering
\includegraphics[width=\linewidth]{gqa_prefill_compute_bound.png}
\caption{Compute-bound prefill, three command forms, swept over context
length ($M{=}8\,T_q$ tile-filling). \emph{Left:} end-to-end latency.
\emph{Right:} MAC utilization (achieved $\div$ the
\SI{8}{\tera\flop\per\second} per-PE peak). The hand-tiled primitive sits
flat at $\sim$\SI{68}{\percent}---its serial load$\to$dot path leaves the
MAC array idle between tiles regardless of context. The composite forms
climb with context (\SI{67}{}$\to$\SI{80}{\percent} plain,
\SI{67}{}$\to$\SI{83}{\percent} with the recipe) because a deeper $P\!\cdot
\!V$ reduction gives more HW tiles to pipeline, and they convert that into
wall-clock: at \num{1024} the recipe form is \SI{646.9}{} vs.\
\SI{794.1}{\micro\second} (\SI{19}{\percent} faster). The margin
\emph{grows} with context---the compute-bound mirror of the GEMM result of
\S\ref{sec:gemm}.}
\label{fig:gqa-prefill-cb}
\end{figure}
The two studies together state the composite command's value precisely. It
has two distinct benefits, and which one matters is set by the workload's
roofline position. The first is \emph{host-issue offload}: one macro
command in place of $O(n_{\text{tiles}})$ fine ones, which removes
PE\_CPU dispatch work and is regime-independent (it shows in the decode
command count). The second is \emph{MAC-array feeding}: the
scheduler-internal per-tile DMA$\rightleftarrows$compute pipeline, which
only converts to latency when the workload is compute-bound enough to have
a MAC array worth keeping busy (it shows in the prefill utilization). A
memory-bound decode exercises only the first; a compute-bound prefill
exercises both. The composite command is the single mechanism that
delivers each where it applies.
% Summary moved to sections/05z-summary.tex so the three new
% subsections (roofline, capacity planning, parallelism selection)
% can appear between the technical results and the closing summary.
% TODO: cross-regime DP (data parallelism) applicability:
% - Does Case-4 long-context placement compose with batch-level DP
% without further changes?
% - Does the short-context placement compose the same way?
% - Implications for multi-user serving (single vs. mixed regimes).
@@ -0,0 +1,22 @@
\subsection{Summary}
\label{sec:gqa-analysis}
The section's results line up into one picture of the fused kernel.
Placement is decided by memory first and communication second: only the
64-way splits fit production context, and among them the both-axes
sequence shard (Case~6) minimizes the per-token collective by merging
softmax state rather than partial scores. Command form is decided by
roofline position: the composite command removes the per-tile issue work
in both regimes, but converts to wall-clock only in compute-bound prefill,
while memory-bound decode stays bound by KV streaming regardless of command
form. The thread connecting the two is that, once composite issue makes
GEMM cheap and leaves the MAC array idle, the fused kernel's latency is set
almost entirely by data movement---streaming the KV cache and reducing
partials across PEs---which is exactly the cost the communication-side
work (on-device reduction, lazy load/compute overlap, fast TCM staging and
torus links) is built to attack. Roofline framing
(\S\ref{sec:roofline}) turns this observation into a sizing and sharding
programme in the next two subsections: capacity planning
(\S\ref{sec:capacity-planning}) and parallelism selection
(\S\ref{sec:parallelism-selection}). What this implies for hardware
investment across the report as a whole is taken up in the discussion.
@@ -0,0 +1,309 @@
\section{Supporting Agentic Workloads}
\label{sec:agentic}
The fused GQA kernel of \S\ref{sec:gqa} solved the efficient execution of a
\emph{single} logical attention stream: one query stream against one KV
cache, tiled and merged with an online softmax. Agentic inference
introduces a different execution model, in which one request dynamically
\emph{forks} into several cooperating reasoning branches and later
\emph{joins} them. The purpose of this section is to show that this
multi-stream model needs no new attention algorithm---the expensive
hardware machinery built for \S\ref{sec:gqa} is reused unchanged---and to
work out the resulting design across three implementation layers. The
central claim, stated once here and defended throughout, is:
\begin{quote}
\emph{The proposed agentic execution does not change the semantics of
transformer attention; it changes only how independent query rows are
scheduled over a shared KV cache.}
\end{quote}
\noindent Attention stays identical; only the scheduling differs. What
follows is a \emph{design} built on the measured \S\ref{sec:gqa} kernel; it
is not yet a KernBench measurement, and points that go beyond the
implemented path are flagged as such.
\subsection{Motivation: from one attention stream to many}
\label{sec:agentic-why}
Agentic execution has two recurring shapes: a \emph{loop} (an agent that
repeatedly generates, calls a tool, and continues) and a \emph{fan-out /
fan-in} (a parent agent forks several specialised sub-agents and later
synthesises their results). The loop is ordinary autoregressive decoding
and needs nothing new. The fan-out is the interesting case, because the
branches are \emph{structurally related} rather than independent:
\[
\begin{aligned}
\text{context}_i \;=\;& \underbrace{\text{shared prefix}}_{\text{common}} \\
&+\; \underbrace{\text{private role}_i + \text{private suffix}_i}_{\text{branch-specific}} .
\end{aligned}
\]
Ordinary batching groups unrelated requests that merely arrive together;
agentic fan-out groups requests that share an identical prefix KV cache and
differ only in a short private suffix. That structure creates two levers
that unrelated-request batching cannot pull:
\begin{enumerate}
\item \textbf{Shared-prefix KV reuse.} All branches attend to the same
prefix KV, so it is read (and stored) once, not once per branch.
\item \textbf{Query-row batching.} The new query rows of many sub-agents
read the \emph{same} shared KV, so they can be concatenated into one
taller GEMM.
\end{enumerate}
\noindent Both levers land directly on the \S\ref{sec:gqa} design, which
already multicasts a (small) query against a stationary KV cache and merges
the result hierarchically. Fan-out simply makes the query taller, and
fan-in adds a join step; neither touches the attention math.
\subsection{Architecture: three execution layers}
\label{sec:agentic-arch}
Before the mechanics, we fix the layering, because the recurring reader
question is ``whose job is this?''. Agentic execution divides cleanly along
the request-flow layers already used in this report: the \textbf{agentic
framework} decides \emph{what} to run and how to combine it, the
\textbf{runtime} decides \emph{how} to map it onto the hardware without
copying shared state, and the \textbf{kernel} does the actual math and
reduction on the PEs.
\[
\text{Framework} \;\longrightarrow\; \text{Runtime}
\;\longrightarrow\; \text{Kernel}
\]
\noindent Keeping the split this way preserves the topology-agnostic
runtime boundary: the framework never sees the SIP/CUBE/PE hierarchy, and
the kernel never sees the agent tree. The remainder of the section walks
the fan-out $\rightarrow$ runtime $\rightarrow$ kernel $\rightarrow$ fan-in
path through exactly these three layers.
Table~\ref{tab:agentic-levels} states each layer's responsibilities; the
kernel row is unchanged from \S\ref{sec:gqa}.
\begin{table*}[t]
\centering
\caption{Division of responsibilities for agentic attention. Only the
framework and runtime rows are new work; the kernel is the
\S\ref{sec:gqa} fused GQA kernel, unchanged in its math and reduction.}
\label{tab:agentic-levels}
\small
\begin{tabular}{@{}p{0.16\textwidth}p{0.40\textwidth}p{0.36\textwidth}@{}}
\toprule
\textbf{Level} & \textbf{Responsibilities} & \textbf{Explicitly not its job} \\
\midrule
\textbf{Agentic framework}
& Manage the agent tree: fork sub-agents and assign roles; identify the
shared prefix; mark which branches are co-schedulable (same shared prefix).
Fan-in: enforce schema-constrained results, deduplicate/rank findings,
hold evidence behind pointers, insert hierarchical reducers, and drive the
main agent's final synthesis.
& No topology, routing, KV placement, or scheduling. Sees agents and text,
not CUBEs or PEs. \\
\addlinespace
\textbf{Runtime}
& Fork the logical context by page table (shared-prefix pages $+$ private
suffix pages) with \emph{no copy} of shared KV. Concatenate co-scheduled
sub-agent query rows into $Q_{\text{cmb}}$. Select the execution policy
(Stationary-KV vs.\ Distributed-Q, \S\ref{sec:agentic-choice}) from the
cost model. Launch the
fused GQA kernel (composite command $+$ PE\_IPCQ) and hand the batched work
and policy to the simulation engine.
& No attention math and no per-hop routing (delegated to the engine and
policy); no agent-level semantics. \\
\addlinespace
\textbf{Kernel}
& Multicast $Q_{\text{cmb}}$ to all PEs; per-PE local attention over the
stationary KV shard via composite-command $Q\!\cdot\!K^{\top}$ and
$P\!\cdot\!V$; produce per-row online-softmax state; merge that state
hierarchically over PE\_IPCQ by matching row index (row-parallel, not
serialised).
& No knowledge of agents, page tables, or policy selection. Identical to
\S\ref{sec:gqa}; a taller $Q$ is the only difference. \\
\bottomrule
\end{tabular}
\end{table*}
\subsection{Stationary-KV Execution Policy}
\label{sec:agentic-policyA}
The core attention operation is $Q\!\cdot\!K^{\top}$, and under fan-out
multiple sub-agents contribute different query rows while reading a common
$K$. Rather than issuing $Q_A\!\cdot\!K_{\text{shared}}$,
$Q_B\!\cdot\!K_{\text{shared}}$, \dots\ as separate small GEMMs, the runtime
concatenates the rows,
\[
Q_{\text{cmb}} = \begin{bmatrix} Q_A \\ Q_B \\ \vdots \end{bmatrix},
\qquad
Q_{\text{cmb}}\!\cdot\!K_{\text{shared}},
\]
and issues one taller GEMM. The arithmetic is unchanged---each row is still
independent---but the $M$ dimension grows. For a representative fan-out of
$8$ sub-agents at $20$ new tokens each, $M = 8\times20 = 160$; with a per-PE
KV shard of $256$ sequence positions this is a $160\times d$ by $d\times256$
local product---an $M{=}160$, $N{=}256$ shape that sits well above the
per-command issue overhead the composite command (\S\ref{sec:gemm}) is built
to amortise. Fan-out is therefore especially valuable during \emph{prefill}
of the private suffixes, where the combined $M$ is large; during decode the
query stays short and the workload remains, as \S\ref{sec:gqa} found,
KV-bandwidth bound.
The Stationary-KV policy maps onto the \S\ref{sec:gqa} placement
essentially unchanged: logically replicated $Q$ over a stationary,
sequence-sharded KV cache. One
KV head spans four CUBEs and 32 PEs, with each PE owning a different KV
\emph{sequence} shard $K_p[S_p,d]$, $V_p[S_p,d_v]$. The combined $Q$ is
multicast to all of them; each PE forms a complete local score
$Q_{\text{cmb}}\!\cdot\!K_p^{\top}$ over its own shard, and the per-row
online-softmax state is reduced hierarchically---8-PE merge inside each
CUBE, then a 4-CUBE merge---using the same PE\_IPCQ merge primitive from
\S\ref{sec:allreduce}.
\paragraph{The reduction algorithm does not change.} This is the point to
emphasise. Agentic batching does \emph{not} require a new reduction
algorithm: the hierarchical online-softmax reduction of \S\ref{sec:gqa}
remains exactly as-is, and only the query batch becomes larger. The
reduction is always \emph{same row index across sequence shards}, never
\emph{different query rows against each other}, so $M$ batched rows do
\emph{not} become $M$ serialised communication rounds; the $(m,\ell,O)$ row
states are exchanged as vectors/tiles and merged in parallel. Because the
merge is row-indexed, a taller $Q$ widens each payload but adds no rounds.
This is what makes agentic support a reuse of \S\ref{sec:gqa} rather than a
redesign.
\paragraph{Logical vs.\ physical $Q$ replication.} ``Replicated $Q$'' is a
\emph{logical} statement. Because the shard axis is the KV \emph{sequence}
dimension $S$, every PE must form the full score $Q\!\cdot\!K_p^{\top}$
against its local $K_p$ and therefore needs $Q$'s \emph{entire} hidden
dimension $d$; what is partitioned across PEs is $K$/$V$ along $S$, never
$Q$ along its columns. Splitting $Q$ (and $K$) on the hidden dimension
would instead make each PE's product \emph{partial} and force a pre-softmax
hidden-dimension reduction ($QK^{\top}=\sum_i Q_iK_i^{\top}$)---that is
tensor-/head-parallel attention, a different structure from the
sequence-parallel one assumed here, and one that cannot coexist with using
the PE axis for sequence shards. Logical replication also does not mean 32
physical copies: $Q$ can be multicast once into a CUBE-local shared buffer
(shared SRAM) that all PEs in the CUBE read, and a large $Q$ can further be
\emph{row}-tiled in time ($Q[0{:}16,:],\,Q[16{:}32,:],\dots$)---row tiling
splits the $M$ dimension, not the hidden-dimension columns. In short:
the Stationary-KV policy uses logically replicated $Q$ across
sequence-parallel PEs while $K$ and $V$ are partitioned along the sequence
dimension; $Q$ may be
temporally row-tiled or physically shared through multicast buffers, but it
is not partitioned along the hidden-dimension columns.
\paragraph{Replication is not $32\times$ the compute work (attention
FLOPs).} Multicasting $Q$ to 32 PEs does not multiply attention FLOPs,
because each PE computes against a different KV sequence shard rather than
the same one. Let the KV sequence length be $S$; with sequence parallelism
over 32 PEs, each PE owns $S/32$ positions. The score GEMM
$Q[M,d]\!\cdot\!K^{\top}[d,S]$ costs $\propto M\,d\,S$, so each PE performs
$M\,d\,(S/32)$ and the 32 shards sum to
\[
32 \cdot M\,d\,\tfrac{S}{32} \;=\; M\,d\,S,
\]
identical to attention over one undivided sequence. Replication therefore
changes $Q$ distribution, reduction traffic, buffering, and scheduling---not
the total attention FLOPs.
\subsection{Distributed-Q Execution Policy (alternative)}
\label{sec:agentic-policyB}
The natural alternative is to partition (distribute) the query rows across
PE groups (\emph{the Distributed-Q policy}) rather than replicate them. It
is not automatically
better. Because the 32 PEs already shard the KV \emph{sequence}, every query
row must still attend to \emph{all} shards; partitioning $Q$ across PE
groups therefore forces each group to reach every KV shard, which requires
one of: regrouping KV shards per $Q$ group, replicating KV across groups, or
reading remote KV through symmetric memory. Each of these adds
memory-system complexity that the Stationary-KV policy avoids entirely.
Time-multiplexing the same PEs over $Q$ groups is the fourth option, but
that is temporal tiling---already available under the Stationary-KV policy
as row tiling---not true spatial $Q$ partitioning. The Distributed-Q policy
is thus a proposed adaptive extension, not the
baseline, and is only worth its complexity when $Q$ grows large enough that
multicast and reduction traffic dominate remote/regrouped-KV cost.
\subsection{Why the Stationary-KV policy is the baseline}
\label{sec:agentic-choice}
The choice reduces to a cost comparison the runtime can estimate. The
Stationary-KV policy pays for $Q$ multicast and a hierarchical reduction;
the Distributed-Q policy pays for moving or replicating KV plus extra
scheduling:
\[
T_{\text{SK}} = T_{Q\text{-mcast}} + T_{\text{local GEMM}} + T_{\text{hier.\ reduce}},
\]
\[
\begin{aligned}
T_{\text{DQ}} = {}& T_{Q\text{-part}} + T_{\text{remote/repl.\ KV}} + T_{\text{local GEMM}} \\
&+ T_{\text{grp.\ reduce}} + T_{\text{remap}}.
\end{aligned}
\]
For the assumed mapping (one KV head $=$ 4 CUBEs $=$ 32 PEs), the KV cache
is large and stationary while $Q$ is comparatively small, so $T_{\text{SK}}$
is the lower cost and the Stationary-KV policy is the recommended baseline.
A future runtime can compute both estimates per launch---from the current
agent count, $Q$ size, KV-head mapping, and interconnect state---and switch
to the Distributed-Q policy only in the regime where a very large $Q$ batch
makes multicast and reduction traffic outweigh the cost of remote or
regrouped KV. Until that regime is measured, the Distributed-Q policy
remains a designed, not-yet-implemented option.
\subsection{Fan-in: joining sub-agent branches}
\label{sec:agentic-fanin}
Fan-out is only half of the pattern; after it, each branch produces a
private continuation and the parent must synthesise them. We treat fan-in
as a runtime/framework design problem with a clear optimization ladder.
\paragraph{Problem.} The branch KV caches \emph{cannot} be concatenated.
Each token's K/V depends on its full causal history, so stacking several
branches' private KV does not form the cache of any single valid
sequence. Join must therefore happen at the token/text level, and its cost
is dominated by the number of join-input tokens, because the new main-agent
KV grows in proportion to them.
\paragraph{Baseline.} A naive full-text gather concatenates every branch's
raw output: $8$ agents $\times\ 1000$ tokens $=\ 8000$ tokens pushed through
every layer during continuation prefill---inflating prefill work, KV
allocation, and later decode-time KV reads. This is the cost the ladder
below drives down.
\paragraph{Optimization 1 --- schema-constrained results.} Constrain each
sub-agent to emit a compact structured result (claim, confidence, evidence
handles) instead of free text, cutting join input by an order of magnitude
($8\times1000 \to 8\times50$).
\paragraph{Optimization 2 --- deduplication and ranking.} Overlapping
findings across branches are merged and ranked in the framework before they
reach the main context. This is preprocessing, not reasoning, and shrinks
the input further without involving the main model.
\paragraph{Optimization 3 --- pointer-based evidence.} Detailed evidence
stays outside the main context behind handles, materialised only when the
main agent actually requests it, so the effective input is summaries plus
only the evidence truly used.
\paragraph{Optimization 4 --- hierarchical reducers.} For wide fan-out,
intermediate reducer agents summarise groups of branches, shrinking the
final join prompt and organising fan-in traffic hierarchically---mirroring
how the hierarchical online-softmax merge organises attention reduction.
\paragraph{Future --- latent-state join.} A more aggressive step replaces
text outputs with a few learned latent tokens, further cutting join prefill
and KV growth. This requires training the main model to consume latent
tokens and aligning branch representations; it is a model--system co-design
direction, not a drop-in runtime optimization, and is out of scope here.
\medskip
\noindent Throughout, the shared prefix KV is reused by page-table
reference and only the join suffix is new, so shortening join input is the
primary lever on continuation-prefill cost, KV growth, and later
decode-time KV reads. Taken together, \S\ref{sec:agentic-policyA}--\ref{sec:agentic-fanin}
show why this workload is a natural extension of the 1H design rather than a
new one: the decisive hardware levers of this report---cheap composite
issue and a fast on-device reduction path---are exactly what make agentic
fan-out efficient, and no part of the attention math is altered to get
there. Quantifying the fan-out speedup and the fan-in join savings on
KernBench is left as measured 2H work.
@@ -0,0 +1,67 @@
\section{Hardware Performance-Spec Search for GQA}
\label{sec:hwspec}
\emph{Work in progress --- this section states the study's intent and
method; experimental data, figures, and conclusions are not yet
available and will be added in a later revision.}
The studies so far fixed the modeled hardware (\S\ref{sec:hw}) and
varied the \emph{software}: the composite command, the reduction path, and
the KV placement. This section inverts the question. Given the fused GQA
kernel of \S\ref{sec:gqa} as the target workload, \emph{which hardware
specification best serves it}, and where does spending more silicon stop
paying off? The discussion of \S\ref{sec:discussion} already gives a
strong prior---attention is data-movement bound, so raw MAC throughput is
not the binding constraint---but that conclusion was drawn at one operating
point. A systematic sweep is needed to turn it into a defensible
performance-spec recommendation across context lengths, agent counts, and
sharding regimes.
\subsection{Sweep axes}
\label{sec:hwspec-axes}
Four hardware knobs are varied, spanning the compute side and both levels
of the interconnect so that the compute/communication balance can be
located rather than assumed. The KernBench cost model already exposes each
as a parameter, so the sweep reuses the same deterministic engine as the
rest of the report; no production change is required.
\begin{table}[h]
\centering
\caption{Hardware knobs varied in the performance-spec search. Ranges and
step counts are to be finalised with the experiment.}
\label{tab:hwspec-axes}
\small
\begin{tabular}{@{}p{0.30\textwidth}p{0.14\textwidth}@{}}
\toprule
\textbf{Knob} & \textbf{Axis} \\
\midrule
GEMM throughput (TFLOPS) & compute \\
MATH-engine ALU count & compute \\
CUBE-to-CUBE (die-to-die) bandwidth & interconnect \\
SIP-to-SIP (card-to-card) bandwidth & interconnect \\
\bottomrule
\end{tabular}
\end{table}
The first two axes scale the on-PE compute engines; the latter two scale
the two inter-device links that carry the KV reduction and cross-device
softmax merge. Sweeping them jointly---rather than one at a time---is what
exposes the interactions: for example, whether added die-to-die bandwidth
only helps once card-to-card bandwidth is also raised, or whether GEMM
throughput is genuinely inert for attention once issue overhead is removed.
\subsection{Method and target output}
\label{sec:hwspec-method}
The intended procedure is a joint sweep over the four axes, running the
fused GQA kernel at representative decode and prefill configurations
(including the agentic fan-out shapes of \S\ref{sec:agentic}), and reading
latency and per-engine busy time from the engine's completion timestamps
and op log---the same measurement path used in \S\ref{sec:gqa}. The target
deliverables are: (i) the sensitivity of GQA latency to each knob in
isolation, (ii) the Pareto frontier of latency against a simple
area/cost proxy, and (iii) a recommended balanced specification---the knob
combination past which further investment does not move GQA latency. These
results are the natural quantitative counterpart to the qualitative
ranking in \S\ref{sec:discussion}, and completing them is a 2H objective.
@@ -40,7 +40,10 @@ hardware levers for this workload:
GQA panels leave the GEMM and vector-math engines two to three orders of GQA panels leave the GEMM and vector-math engines two to three orders of
magnitude below the DMA engine in busy time. Adding MAC area would not move magnitude below the DMA engine in busy time. Adding MAC area would not move
decode latency; the workload cannot use it. This is the single most decode latency; the workload cannot use it. This is the single most
actionable finding for an attention-dominated roadmap. actionable finding for an attention-dominated roadmap. The implication is
that future hardware investment should prioritize communication and
memory-system efficiency over additional compute throughput for
attention-dominated inference workloads.
\paragraph{Caveats.} These conclusions are achievable-kernel results from a \paragraph{Caveats.} These conclusions are achievable-kernel results from a
deterministic model, not E2E measurements; absolute numbers carry the deterministic model, not E2E measurements; absolute numbers carry the
@@ -1,4 +1,4 @@
\section{Future Work --- 2H} \section{Future Work}
\label{sec:future} \label{sec:future}
The 1H work covered attention end to end. The natural next step is to The 1H work covered attention end to end. The natural next step is to
@@ -30,11 +30,22 @@ vs.\ sequence parallelism---under a single, software-stack-independent
model, so the interconnect and memory implications of each choice are model, so the interconnect and memory implications of each choice are
measured rather than assumed. measured rather than assumed.
\paragraph{Agentic workloads.} Agentic inference interleaves many \paragraph{Agentic workloads: from design to implementation.}
short, bursty decode requests with tool use and long shared contexts, Section~\ref{sec:agentic} established \emph{how} the fused GQA design
which stresses the system differently from a single long generation: extends to agentic fan-out/fan-in and \emph{which} responsibilities fall to
context reuse across requests, dynamic batching, and uneven expert load all the framework, the runtime, and the kernel. The 2H step is no longer to
change how compute and data should be dispersed. Characterizing how total analyse the workload but to \emph{build} that support: the detailed design
compute and data movement distribute across the SIP/CUBE/PE hierarchy under and implementation of all three levels. This is necessary work rather than
such workloads---and which of the 1H hardware levers still dominate when the optional, because today's agentic frameworks are effectively all
workload is this irregular---is the broader 2H agenda. closed-source---there is no open substrate that exposes shared-prefix KV
reuse, cross-agent query batching, and schema/pointer-based fan-in down to
the hardware. Concretely, 2H targets: a \textbf{framework} layer that
manages the agent tree, identifies co-schedulable branches, and enforces
compact fan-in; a \textbf{runtime} layer that forks logical contexts by
page table without copying shared KV, batches sub-agent query rows, and
selects the execution policy; and a \textbf{kernel} layer that runs the
batched replicated-Q attention and hierarchical online-softmax merge over
PE\_IPCQ. Bringing these up on KernBench turns the
Section~\ref{sec:agentic} design into a measured, end-to-end agentic path
and lets us confirm which of the 1H hardware levers still dominate once the
full framework/runtime/kernel stack is in place.
+18 -7
View File
@@ -45,17 +45,28 @@
6. **Fused Grouped Query Attention** (composite + PE_IPCQ) — `sections/05-gqa.tex` 6. **Fused Grouped Query Attention** (composite + PE_IPCQ) — `sections/05-gqa.tex`
necessity · design · results · analysis. (+ GQA seq/head/user configs) necessity · design · results · analysis. (+ GQA seq/head/user configs)
7. **Discussion**`sections/06-discussion.tex` 7. **Supporting Agentic Workloads**`sections/06-agentic.tex`
How the §6 GQA design extends to agentic fan-out/fan-in: shared-prefix KV
reuse, batched replicated-Q attention, schema/pointer-based fan-in.
Implementation split across three levels (agentic framework / runtime /
kernel) with each component's responsibilities. Design, not yet measured.
8. **Hardware Performance-Spec Search for GQA**`sections/07-hw-spec-search.tex`
*Work in progress.* Joint sweep of GEMM TFLOPS, MATH-engine ALU count,
CUBE↔CUBE (die-to-die) BW, SIP↔SIP (card-to-card) BW to find the balanced
spec for GQA. Intent + method only; data/figures/conclusions deferred.
9. **Discussion**`sections/08-discussion.tex`
Which HW changes are meaningful, and under what regimes (cross-cutting). Which HW changes are meaningful, and under what regimes (cross-cutting).
8. **Conclusion**`sections/07-conclusion.tex` 10. **Conclusion**`sections/09-conclusion.tex`
The codesign thesis, stated plainly, supported by the measured results. The codesign thesis, stated plainly, supported by the measured results.
9. **Future Work — 2H**`sections/08-future-work.tex` 11. **Future Work — 2H**`sections/10-future-work.tex`
Add the FFN/MoE layer toward full LLM decoding; how compute & data should Add the FFN/MoE layer toward full LLM decoding; how compute & data should
be distributed for agentic / MoE workloads. be distributed for agentic / MoE workloads.
10. **References** *(optional)* — external literature only (FlashAttention, 12. **References** *(optional)* — external literature only (FlashAttention,
Megatron-LM, GPT-3, Llama 3). No ADR/SPEC entries. Megatron-LM, GPT-3, Llama 3). No ADR/SPEC entries.
## Per-section structure (§4/§5/§6) ## Per-section structure (§4/§5/§6)
+12
View File
@@ -0,0 +1,12 @@
mode,kv_per_cube,C,S_kv,B,capacity,agg_latency_us,mean_user_latency_us,throughput_users_per_us
A1,1,8,8192,1,2,2.2471645000005376,2.2471645000005376,0.44500524994932983
A1,1,8,8192,2,2,2.3131845000012543,2.2471645000012357,0.864608940617973
A2,2,4,8192,1,4,3.3555127500005475,3.3555127500005475,0.2980170467240325
A2,2,4,8192,2,4,3.3935227500010514,3.355512750001042,0.5893580645656141
A2,2,4,8192,4,4,3.4695427500010703,3.3555127500011586,1.1528896711241752
A4,4,2,8192,1,8,5.586135500000557,5.586135500000557,0.17901463364071643
A4,4,2,8192,2,8,5.602295500000823,5.586135500000673,0.35699652044411195
A4,4,2,8192,8,8,5.783285499986028,5.586135499984957,1.3832967436968704
B,8,1,8192,1,16,10.816091000000947,10.816091000000947,0.09245484343649775
B,8,1,8192,2,16,10.848411000001535,10.816091000001236,0.18435879687815265
B,8,1,8192,16,16,11.38492099986691,10.816090999974403,1.4053676788962384
1 mode kv_per_cube C S_kv B capacity agg_latency_us mean_user_latency_us throughput_users_per_us
2 A1 1 8 8192 1 2 2.2471645000005376 2.2471645000005376 0.44500524994932983
3 A1 1 8 8192 2 2 2.3131845000012543 2.2471645000012357 0.864608940617973
4 A2 2 4 8192 1 4 3.3555127500005475 3.3555127500005475 0.2980170467240325
5 A2 2 4 8192 2 4 3.3935227500010514 3.355512750001042 0.5893580645656141
6 A2 2 4 8192 4 4 3.4695427500010703 3.3555127500011586 1.1528896711241752
7 A4 4 2 8192 1 8 5.586135500000557 5.586135500000557 0.17901463364071643
8 A4 4 2 8192 2 8 5.602295500000823 5.586135500000673 0.35699652044411195
9 A4 4 2 8192 8 8 5.783285499986028 5.586135499984957 1.3832967436968704
10 B 8 1 8192 1 16 10.816091000000947 10.816091000000947 0.09245484343649775
11 B 8 1 8192 2 16 10.848411000001535 10.816091000001236 0.18435879687815265
12 B 8 1 8192 16 16 11.38492099986691 10.816090999974403 1.4053676788962384
@@ -0,0 +1,69 @@
"""Batch-scaling figure for concurrent decode users on one SIP.
Reads ``docs/sweeps/decode_batch_sweep.csv`` (from
``tests/attention/test_gqa_decode_batch_sweep.py``) and plots aggregate
throughput (requests / us) versus batch size B for the four KV mappings,
one panel per context length. Each mapping's curve runs up to its SIP
capacity 16/C (A1=2 ... B=16 users).
"""
from pathlib import Path
import csv
import matplotlib.pyplot as plt
ROOT = Path(__file__).resolve().parents[3]
CSV = ROOT / "docs" / "sweeps" / "decode_batch_sweep.csv"
OUT = (ROOT / "docs" / "report" / "1H-codesign-paper"
/ "figures" / "gqa_short_context" / "batch_scaling.png")
MODE_LABEL = {"A1": "1-kv-per-cube", "A2": "2-kv-per-cube",
"A4": "4-kv-per-cube", "B": "8-kv-per-cube"}
MODE_COLOR = {"A1": "#1f77b4", "A2": "#2ca02c",
"A4": "#ffd000", "B": "#d62728"}
MODES = ["A1", "A2", "A4", "B"]
def main():
rows = list(csv.DictReader(CSV.open()))
contexts = sorted({int(r["S_kv"]) for r in rows})
fig, axes = plt.subplots(1, len(contexts), figsize=(6.5 * len(contexts), 4.5),
squeeze=False)
xmax = max(int(r["B"]) for r in rows) # largest SIP capacity (= 16)
for col, S in enumerate(contexts):
ax = axes[0][col]
for m in MODES:
pts = sorted(
((int(r["B"]), float(r["throughput_users_per_us"]))
for r in rows if r["mode"] == m and int(r["S_kv"]) == S),
key=lambda t: t[0],
)
if not pts:
continue
xs, ys = zip(*pts)
# Measured: concurrent users up to the SIP capacity 16/C.
ax.plot(xs, ys, marker="o", color=MODE_COLOR[m],
label=MODE_LABEL[m])
# Beyond capacity the SIP is full: extra users run in waves at
# the saturated rate, so throughput plateaus. Draw that ceiling
# as a dashed extension (projected, not concurrently measured).
if xs[-1] < xmax:
ax.plot([xs[-1], xmax], [ys[-1], ys[-1]],
linestyle="--", color=MODE_COLOR[m], alpha=0.55)
ax.annotate(f"{ys[-1]:.2f}", (xs[-1], ys[-1]),
textcoords="offset points", xytext=(4, 4), fontsize=8)
ax.set_title(f"S_kv = {S // 1024}K")
ax.set_xlabel("batch size B (concurrent users)")
ax.set_ylabel("throughput (requests / µs)")
ax.grid(True, alpha=0.3)
ax.legend(fontsize=9)
fig.suptitle("Batch scaling: aggregate throughput vs concurrent users "
"(one SIP)", fontsize=12, fontweight="bold")
fig.tight_layout()
OUT.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(OUT, dpi=140, bbox_inches="tight")
print(f"{OUT}")
if __name__ == "__main__":
main()
@@ -0,0 +1,231 @@
"""Generate 6 comparison figures for the short-context GQA attention sweep.
Sweep matrix: 3 variants × 2 phases × 4 modes × 4 contexts (8K/16K/32K/64K).
Figures
Wall-clock latency:
1. wall_variant1_mode_compare.png — (1) without composite
2. wall_variant2_mode_compare.png — (2) with composite (GEMM-only)
3. wall_variant3_mode_compare.png — (3) with composite + softmax_merge
4. wall_a1_variant_compare.png — 1-kv-per-cube: variant comparison
Mode trade-off + composite ablation:
5. per_cube_tradeoff_mode_compare.png — HBM BW + IPCQ (variant-invariant)
6. gemm_util_a1_variant_ablation.png — 1-kv-per-cube GEMM util
Output: docs/report/1H-codesign-paper/figures/gqa_short_context/
"""
from __future__ import annotations
import csv
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
ROOT = Path(__file__).resolve().parents[3]
SWEEPS = ROOT / "docs" / "sweeps"
OUT = (ROOT / "docs" / "report" / "1H-codesign-paper"
/ "figures" / "gqa_short_context")
OUT.mkdir(parents=True, exist_ok=True)
CONTEXTS = [8192, 16384, 32768, 65536]
CONTEXT_LABELS = ["8K", "16K", "32K", "64K"]
MODES = ["A1", "A2", "A4", "B"]
MODE_LABEL = {
"A1": "1-kv-per-cube",
"A2": "2-kv-per-cube",
"A4": "4-kv-per-cube",
"B": "8-kv-per-cube",
}
MODE_COLOR = {
MODE_LABEL["A1"]: "#1f77b4",
MODE_LABEL["A2"]: "#2ca02c",
MODE_LABEL["A4"]: "#ffd000",
MODE_LABEL["B"]: "#d62728",
}
VARIANTS = [
("baseline", "(1) without composite"),
("composite", "(2) with composite (GEMM-only)"),
("composite_fused", "(3) with composite + softmax_merge"),
]
VARIANT_COLOR = {
"baseline": "#1f77b4",
"composite": "#2ca02c",
"composite_fused": "#d62728",
}
CSV_MAP = {
("decode", "baseline"): SWEEPS / "short_context_decode_sweep.csv",
("decode", "composite"): SWEEPS / "short_context_decode_composite_sweep.csv",
("decode", "composite_fused"): SWEEPS / "short_context_decode_composite_fused_sweep.csv",
("prefill", "baseline"): SWEEPS / "short_context_prefill_sweep.csv",
("prefill", "composite"): SWEEPS / "short_context_prefill_composite_sweep.csv",
("prefill", "composite_fused"): SWEEPS / "short_context_prefill_composite_fused_sweep.csv",
}
def load(phase, variant):
"""Return {(mode, S_kv): row dict}."""
rows = {}
with CSV_MAP[(phase, variant)].open() as f:
for r in csv.DictReader(f):
rows[(r["mode"], int(r["S_kv"]))] = r
return rows
def grouped_bars(ax, *, x_labels, groups, group_color, ylabel, title,
y_log=False, value_fmt="{:.3g}"):
"""Grouped bars: x = x_labels, groups = list of (label, values)."""
n = len(groups)
nx = len(x_labels)
width = 0.8 / n
x = np.arange(nx)
for i, (label, values) in enumerate(groups):
offset = (i - (n - 1) / 2) * width
bars = ax.bar(x + offset, values, width, label=label,
color=group_color[label], edgecolor="black",
linewidth=0.4)
for bar, v in zip(bars, values):
if v <= 0 and y_log:
continue
ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height(),
value_fmt.format(v), ha="center", va="bottom",
fontsize=7)
ax.set_xticks(x)
ax.set_xticklabels(x_labels)
ax.set_xlabel("S_kv")
ax.set_ylabel(ylabel)
ax.set_title(title)
if y_log:
ax.set_yscale("log")
ax.grid(True, axis="y", alpha=0.3, which="both")
ax.legend(fontsize=8, loc="upper left")
# ── 1-3. Wall mode comparison per variant ──────────────────────────
def fig_wall_mode_compare(variant_key, suptitle, fname):
fig, axes = plt.subplots(1, 2, figsize=(13, 4.8))
for ax, phase in zip(axes, ("prefill", "decode")):
rows = load(phase, variant_key)
groups = [
(MODE_LABEL[m], [float(rows[(m, s)]["wall_us"]) for s in CONTEXTS])
for m in MODES
]
grouped_bars(ax, x_labels=CONTEXT_LABELS, groups=groups,
group_color=MODE_COLOR,
ylabel="Wall-clock latency (μs)",
title=phase.capitalize(),
y_log=True, value_fmt="{:.0f}")
fig.suptitle(suptitle, fontsize=12, fontweight="bold")
fig.tight_layout()
out = OUT / fname
fig.savefig(out, dpi=140, bbox_inches="tight")
plt.close(fig)
print(f"{out}")
# ── 4. Wall 1-kv-per-cube variant comparison ───────────────────────
def fig_wall_a1_variant_compare(fname):
fig, axes = plt.subplots(1, 2, figsize=(13, 4.8))
for ax, phase in zip(axes, ("prefill", "decode")):
groups = []
for variant_key, _ in VARIANTS:
rows = load(phase, variant_key)
values = [float(rows[("A1", s)]["wall_us"]) for s in CONTEXTS]
groups.append((variant_key, values))
grouped_bars(ax, x_labels=CONTEXT_LABELS, groups=groups,
group_color=VARIANT_COLOR,
ylabel="Wall-clock latency (μs)",
title=phase.capitalize(),
y_log=True, value_fmt="{:.0f}")
ax.legend([v[1] for v in VARIANTS], fontsize=8, loc="upper left")
fig.suptitle("1-kv-per-cube: variant comparison",
fontsize=12, fontweight="bold")
fig.tight_layout()
out = OUT / fname
fig.savefig(out, dpi=140, bbox_inches="tight")
plt.close(fig)
print(f"{out}")
# ── 5. Per-cube trade-off (HBM BW + IPCQ, mode compare) ────────────
def fig_per_cube_tradeoff(fname):
fig, axes = plt.subplots(2, 2, figsize=(13, 9))
metrics = [
("kv_cache_per_cube_mb", "Per-cube KV footprint (MB)", True, "{:.0f}"),
("ipcq_kb", "IPCQ traffic (KB)", True, "{:.1f}"),
]
for row, (key, ylabel, log, fmt) in enumerate(metrics):
for col, phase in enumerate(("prefill", "decode")):
ax = axes[row][col]
rows = load(phase, "baseline")
groups = [
(MODE_LABEL[m], [float(rows[(m, s)][key]) for s in CONTEXTS])
for m in MODES
]
grouped_bars(ax, x_labels=CONTEXT_LABELS, groups=groups,
group_color=MODE_COLOR, ylabel=ylabel,
title=phase.capitalize(),
y_log=log, value_fmt=fmt)
fig.suptitle("Per-cube trade-off: KV footprint + IPCQ mode comparison",
fontsize=12, fontweight="bold")
fig.tight_layout()
out = OUT / fname
fig.savefig(out, dpi=140, bbox_inches="tight")
plt.close(fig)
print(f"{out}")
# ── 6. GEMM util ablation (1-kv-per-cube, variant compare) ─────────
def fig_gemm_util_a1_ablation(fname):
fig, axes = plt.subplots(1, 2, figsize=(13, 5.4))
for ax, phase in zip(axes, ("prefill", "decode")):
groups = []
for variant_key, _ in VARIANTS:
rows = load(phase, variant_key)
values = [float(rows[("A1", s)]["gemm_util"]) for s in CONTEXTS]
groups.append((variant_key, values))
grouped_bars(ax, x_labels=CONTEXT_LABELS, groups=groups,
group_color=VARIANT_COLOR,
ylabel="GEMM engine utilization (per PE)",
title=phase.capitalize(),
y_log=False, value_fmt="{:.4f}")
ax.legend([v[1] for v in VARIANTS], fontsize=8, loc="upper left")
fig.suptitle("1-kv-per-cube: GEMM utilization variant comparison",
fontsize=12, fontweight="bold")
fig.text(
0.5, -0.04,
"⚠ Higher gemm_util on composite/fused is largely supertile "
"padding overhead (M=G=8 padded to TILE_M=32), not pure fusion "
"gain. See ADR-0070 limitation #4.",
ha="center", fontsize=9, style="italic", color="#555")
fig.tight_layout()
out = OUT / fname
fig.savefig(out, dpi=140, bbox_inches="tight")
plt.close(fig)
print(f"{out}")
if __name__ == "__main__":
print(f"Output: {OUT}")
fig_wall_mode_compare(
"baseline",
"(1) Without composite: latency mode comparison",
"wall_variant1_mode_compare.png")
fig_wall_mode_compare(
"composite",
"(2) With composite (GEMM-only): latency mode comparison",
"wall_variant2_mode_compare.png")
fig_wall_mode_compare(
"composite_fused",
"(3) With composite + softmax_merge: latency mode comparison",
"wall_variant3_mode_compare.png")
fig_wall_a1_variant_compare("wall_a1_variant_compare.png")
fig_per_cube_tradeoff("per_cube_tradeoff_mode_compare.png")
fig_gemm_util_a1_ablation("gemm_util_a1_variant_ablation.png")
print("\nDone.")
@@ -0,0 +1,17 @@
variant,mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,math_pipeline_us,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
composite_fused,A1,1,8,8192,2.2471645000005376,64,0.11665545624270428,656,0.0,0.457688,16.0625,8.0,57.75,2.0
composite_fused,A1,1,8,16384,4.090972500001895,64,0.4907507933676371,1296,13.903615999992937,0.501714,32.0625,8.0,57.75,4.0
composite_fused,A1,1,8,32768,6.65601250000007,64,0.8261186408347434,2576,41.71084799996763,0.61606,64.0625,8.0,57.75,8.0
composite_fused,A1,1,8,65536,11.786092499984429,64,1.05893212704674,5136,97.32531200143696,0.695438,128.0625,8.0,57.75,16.0
composite_fused,A2,2,4,8192,3.8395887500001407,32,0.5228809986460942,624,6.951807999995537,0.534693,16.03125,8.0,24.75,4.0
composite_fused,A2,2,4,16384,6.404628750001605,32,0.858544064722257,1264,20.855423999989405,0.640318,32.03125,8.0,24.75,8.0
composite_fused,A2,2,4,32768,11.534708749999874,32,1.0820101547616419,2544,48.66265599996224,0.710638,64.03125,8.0,24.75,16.0
composite_fused,A2,2,4,65536,21.794868749984772,32,1.21334541192213,5104,104.27712000153959,0.751966,128.03125,8.0,24.75,32.0
composite_fused,A4,4,2,8192,5.915787499999919,16,0.9294884239792724,608,10.427711999993306,0.693399,16.015625,8.0,8.25,8.0
composite_fused,A4,4,2,16384,11.045867500001041,16,1.1298951395303998,1248,24.33132799998764,0.742178,32.015625,8.0,8.25,16.0
composite_fused,A4,4,2,32768,21.306027499999384,16,1.2411841672219757,2528,52.13855999995954,0.769266,64.015625,8.0,8.25,32.0
composite_fused,A4,4,2,65536,41.82634749998455,16,1.2999645260103314,5088,107.7530240015909,0.783573,128.015625,8.0,8.25,64.0
composite_fused,B,8,1,8192,10.856904999999854,8,1.149560763397397,600,12.16566399999219,0.75528,16.0078125,8.0,0.0,16.0
composite_fused,B,8,1,16384,21.117065000001226,8,1.2522906947682175,1240,26.069279999986758,0.776244,32.0078125,8.0,0.0,32.0
composite_fused,B,8,1,32768,41.637384999999774,8,1.3058641410537761,2520,53.8765119999582,0.787177,64.0078125,8.0,0.0,64.0
composite_fused,B,8,1,65536,82.67802499997313,8,1.33323087973183,5080,109.49097600161657,0.792762,128.0078125,8.0,0.0,128.0
1 variant mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count math_pipeline_us hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 composite_fused A1 1 8 8192 2.2471645000005376 64 0.11665545624270428 656 0.0 0.457688 16.0625 8.0 57.75 2.0
3 composite_fused A1 1 8 16384 4.090972500001895 64 0.4907507933676371 1296 13.903615999992937 0.501714 32.0625 8.0 57.75 4.0
4 composite_fused A1 1 8 32768 6.65601250000007 64 0.8261186408347434 2576 41.71084799996763 0.61606 64.0625 8.0 57.75 8.0
5 composite_fused A1 1 8 65536 11.786092499984429 64 1.05893212704674 5136 97.32531200143696 0.695438 128.0625 8.0 57.75 16.0
6 composite_fused A2 2 4 8192 3.8395887500001407 32 0.5228809986460942 624 6.951807999995537 0.534693 16.03125 8.0 24.75 4.0
7 composite_fused A2 2 4 16384 6.404628750001605 32 0.858544064722257 1264 20.855423999989405 0.640318 32.03125 8.0 24.75 8.0
8 composite_fused A2 2 4 32768 11.534708749999874 32 1.0820101547616419 2544 48.66265599996224 0.710638 64.03125 8.0 24.75 16.0
9 composite_fused A2 2 4 65536 21.794868749984772 32 1.21334541192213 5104 104.27712000153959 0.751966 128.03125 8.0 24.75 32.0
10 composite_fused A4 4 2 8192 5.915787499999919 16 0.9294884239792724 608 10.427711999993306 0.693399 16.015625 8.0 8.25 8.0
11 composite_fused A4 4 2 16384 11.045867500001041 16 1.1298951395303998 1248 24.33132799998764 0.742178 32.015625 8.0 8.25 16.0
12 composite_fused A4 4 2 32768 21.306027499999384 16 1.2411841672219757 2528 52.13855999995954 0.769266 64.015625 8.0 8.25 32.0
13 composite_fused A4 4 2 65536 41.82634749998455 16 1.2999645260103314 5088 107.7530240015909 0.783573 128.015625 8.0 8.25 64.0
14 composite_fused B 8 1 8192 10.856904999999854 8 1.149560763397397 600 12.16566399999219 0.75528 16.0078125 8.0 0.0 16.0
15 composite_fused B 8 1 16384 21.117065000001226 8 1.2522906947682175 1240 26.069279999986758 0.776244 32.0078125 8.0 0.0 32.0
16 composite_fused B 8 1 32768 41.637384999999774 8 1.3058641410537761 2520 53.8765119999582 0.787177 64.0078125 8.0 0.0 64.0
17 composite_fused B 8 1 65536 82.67802499997313 8 1.33323087973183 5080 109.49097600161657 0.792762 128.0078125 8.0 0.0 128.0
@@ -0,0 +1,17 @@
variant,mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,math_pipeline_us,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
composite,A1,1,8,8192,2.532214500000584,64,0.33402541530150626,656,0.0,0.406166,16.0625,8.0,57.75,2.0
composite,A1,1,8,16384,4.176996500001463,64,0.40499148132408613,1360,0.0,0.491382,32.0625,8.0,57.75,4.0
composite,A1,1,8,32768,7.466560500002,64,0.4531264428807111,2768,0.0,0.549182,64.0625,8.0,57.75,8.0
composite,A1,1,8,65536,14.045688499950572,64,0.4817558071541797,5584,0.0,0.58356,128.0625,8.0,57.75,16.0
composite,A2,2,4,8192,3.9256127500006404,32,0.4309258471789165,656,0.0,0.522976,16.03125,8.0,24.75,4.0
composite,A2,2,4,16384,7.215176750001498,32,0.4689138072801237,1360,0.0,0.568385,32.03125,8.0,24.75,8.0
composite,A2,2,4,32768,13.794304750002688,32,0.4905351971318266,2768,0.0,0.594231,64.03125,8.0,24.75,16.0
composite,A2,2,4,65536,26.95256074991636,32,0.5021112511805295,5584,0.0,0.608068,128.03125,8.0,24.75,32.0
composite,A4,4,2,8192,6.726335500000743,16,0.5029924540606681,656,0.0,0.609842,16.015625,8.0,8.25,8.0
composite,A4,4,2,16384,13.305463500001585,16,0.5085574057667106,1360,0.0,0.616138,32.015625,8.0,8.25,16.0
composite,A4,4,2,32768,26.463719500003965,16,0.5113863151276257,2768,0.0,0.619338,64.015625,8.0,8.25,32.0
composite,A4,4,2,65536,52.78023149984703,16,0.5128126048745716,5584,0.0,0.620952,128.015625,8.0,8.25,64.0
composite,B,8,1,8192,13.096491000001318,8,0.5166721375947838,656,0.0,0.626122,16.0078125,8.0,0.0,16.0
composite,B,8,1,16384,26.254747000003057,8,0.5154566524738308,1360,0.0,0.624344,32.0078125,8.0,0.0,32.0
composite,B,8,1,32768,52.57125900000788,8,0.5148510519664782,2768,0.0,0.623459,64.0078125,8.0,0.0,64.0
composite,B,8,1,65536,105.20428299969761,8,0.514548785079354,5584,0.0,0.623016,128.0078125,8.0,0.0,128.0
1 variant mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count math_pipeline_us hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 composite A1 1 8 8192 2.532214500000584 64 0.33402541530150626 656 0.0 0.406166 16.0625 8.0 57.75 2.0
3 composite A1 1 8 16384 4.176996500001463 64 0.40499148132408613 1360 0.0 0.491382 32.0625 8.0 57.75 4.0
4 composite A1 1 8 32768 7.466560500002 64 0.4531264428807111 2768 0.0 0.549182 64.0625 8.0 57.75 8.0
5 composite A1 1 8 65536 14.045688499950572 64 0.4817558071541797 5584 0.0 0.58356 128.0625 8.0 57.75 16.0
6 composite A2 2 4 8192 3.9256127500006404 32 0.4309258471789165 656 0.0 0.522976 16.03125 8.0 24.75 4.0
7 composite A2 2 4 16384 7.215176750001498 32 0.4689138072801237 1360 0.0 0.568385 32.03125 8.0 24.75 8.0
8 composite A2 2 4 32768 13.794304750002688 32 0.4905351971318266 2768 0.0 0.594231 64.03125 8.0 24.75 16.0
9 composite A2 2 4 65536 26.95256074991636 32 0.5021112511805295 5584 0.0 0.608068 128.03125 8.0 24.75 32.0
10 composite A4 4 2 8192 6.726335500000743 16 0.5029924540606681 656 0.0 0.609842 16.015625 8.0 8.25 8.0
11 composite A4 4 2 16384 13.305463500001585 16 0.5085574057667106 1360 0.0 0.616138 32.015625 8.0 8.25 16.0
12 composite A4 4 2 32768 26.463719500003965 16 0.5113863151276257 2768 0.0 0.619338 64.015625 8.0 8.25 32.0
13 composite A4 4 2 65536 52.78023149984703 16 0.5128126048745716 5584 0.0 0.620952 128.015625 8.0 8.25 64.0
14 composite B 8 1 8192 13.096491000001318 8 0.5166721375947838 656 0.0 0.626122 16.0078125 8.0 0.0 16.0
15 composite B 8 1 16384 26.254747000003057 8 0.5154566524738308 1360 0.0 0.624344 32.0078125 8.0 0.0 32.0
16 composite B 8 1 32768 52.57125900000788 8 0.5148510519664782 2768 0.0 0.623459 64.0078125 8.0 0.0 64.0
17 composite B 8 1 65536 105.20428299969761 8 0.514548785079354 5584 0.0 0.623016 128.0078125 8.0 0.0 128.0
@@ -0,0 +1,17 @@
mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
A1,1,8,8192,149.2685345000007,64,0.001756190618994587,656,0.013780533230866485,16.0625,8.0,57.75,2.0
A1,1,8,16384,282.2252665000025,64,0.0018576933472394743,1360,0.014545118695104373,32.0625,8.0,57.75,4.0
A1,1,8,32768,548.138730500006,64,0.0019129755692392889,2768,0.014961540835691614,64.0625,8.0,57.75,8.0
A1,1,8,65536,1080.073058500367,64,0.0019416760593127277,5584,0.01517767698303756,128.0625,8.0,57.75,16.0
A2,2,4,8192,142.16883275000066,32,0.0036877843748065785,656,0.028881154333033524,16.03125,8.0,24.75,4.0
A2,2,4,16384,277.0102967500018,32,0.0037853322143696724,1360,0.029609007665885423,32.03125,8.0,24.75,8.0
A2,2,4,32768,546.8006247504073,32,0.0038353138330044275,2768,0.02998167752182655,64.03125,8.0,24.75,16.0
A2,2,4,65536,1087.0962007518285,32,0.003858263874988177,5584,0.03015188534127058,128.03125,8.0,24.75,32.0
A4,4,2,8192,142.21417550000066,16,0.007373217165681769,656,0.05768763888097753,16.015625,8.0,8.25,8.0
A4,4,2,16384,280.8788035001758,16,0.007466394665122732,1360,0.058373931374247456,32.015625,8.0,8.25,16.0
A4,4,2,32768,558.5655195010398,16,0.007509063580845671,2768,0.058686042828569145,64.015625,8.0,8.25,32.0
A4,4,2,65536,1114.319951501857,16,0.007528006645388841,5584,0.05882332081702009,128.015625,8.0,8.25,64.0
B,8,1,8192,147.93485600006025,8,0.014176185766516818,656,0.11085960701508589,16.0078125,8.0,0.0,16.0
B,8,1,16384,294.3171420004266,8,0.014250967413897551,1360,0.1113900460475132,32.0078125,8.0,0.0,32.0
B,8,1,32768,587.2722140010417,8,0.01428401991446495,2768,0.11162115018757507,64.0078125,8.0,0.0,64.0
B,8,1,65536,1173.1823580017838,8,0.014300603725891686,5584,0.11173710472707321,128.0078125,8.0,0.0,128.0
1 mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 A1 1 8 8192 149.2685345000007 64 0.001756190618994587 656 0.013780533230866485 16.0625 8.0 57.75 2.0
3 A1 1 8 16384 282.2252665000025 64 0.0018576933472394743 1360 0.014545118695104373 32.0625 8.0 57.75 4.0
4 A1 1 8 32768 548.138730500006 64 0.0019129755692392889 2768 0.014961540835691614 64.0625 8.0 57.75 8.0
5 A1 1 8 65536 1080.073058500367 64 0.0019416760593127277 5584 0.01517767698303756 128.0625 8.0 57.75 16.0
6 A2 2 4 8192 142.16883275000066 32 0.0036877843748065785 656 0.028881154333033524 16.03125 8.0 24.75 4.0
7 A2 2 4 16384 277.0102967500018 32 0.0037853322143696724 1360 0.029609007665885423 32.03125 8.0 24.75 8.0
8 A2 2 4 32768 546.8006247504073 32 0.0038353138330044275 2768 0.02998167752182655 64.03125 8.0 24.75 16.0
9 A2 2 4 65536 1087.0962007518285 32 0.003858263874988177 5584 0.03015188534127058 128.03125 8.0 24.75 32.0
10 A4 4 2 8192 142.21417550000066 16 0.007373217165681769 656 0.05768763888097753 16.015625 8.0 8.25 8.0
11 A4 4 2 16384 280.8788035001758 16 0.007466394665122732 1360 0.058373931374247456 32.015625 8.0 8.25 16.0
12 A4 4 2 32768 558.5655195010398 16 0.007509063580845671 2768 0.058686042828569145 64.015625 8.0 8.25 32.0
13 A4 4 2 65536 1114.319951501857 16 0.007528006645388841 5584 0.05882332081702009 128.015625 8.0 8.25 64.0
14 B 8 1 8192 147.93485600006025 8 0.014176185766516818 656 0.11085960701508589 16.0078125 8.0 0.0 16.0
15 B 8 1 16384 294.3171420004266 8 0.014250967413897551 1360 0.1113900460475132 32.0078125 8.0 0.0 32.0
16 B 8 1 32768 587.2722140010417 8 0.01428401991446495 2768 0.11162115018757507 64.0078125 8.0 0.0 64.0
17 B 8 1 65536 1173.1823580017838 8 0.014300603725891686 5584 0.11173710472707321 128.0078125 8.0 0.0 128.0
@@ -0,0 +1,17 @@
mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
A1,1,8,8192,2.2471645000005376,64,0.11665545624270428,656,0.457688,16.0625,8.0,57.75,2.0
A1,1,8,16384,3.6068965000013704,64,0.14535709577464812,1360,0.569049,32.0625,8.0,57.75,4.0
A1,1,8,32768,6.326360500001814,64,0.1657471147905734,2768,0.648161,64.0625,8.0,57.75,8.0
A1,1,8,65536,11.765288499959512,64,0.17824909265982342,5584,0.696668,128.0625,8.0,57.75,16.0
A2,2,4,8192,3.3555127500005475,32,0.15624676139283236,656,0.611829,16.03125,8.0,24.75,4.0
A2,2,4,16384,6.074976750001311,32,0.17260576347056114,1360,0.675064,32.03125,8.0,24.75,8.0
A2,2,4,32768,11.513904750002315,32,0.18214081543451005,2768,0.711922,64.03125,8.0,24.75,16.0
A2,2,4,65536,22.391760749934242,32,0.18731461303283142,5584,0.731921,128.03125,8.0,24.75,32.0
A4,4,2,8192,5.586135500000557,16,0.1877104484844272,656,0.734318,16.015625,8.0,8.25,8.0
A4,4,2,16384,11.025063500001211,16,0.19021677290108474,1360,0.743578,32.015625,8.0,8.25,16.0
A4,4,2,32768,21.902919500003218,16,0.19149520227204342,2768,0.748302,64.015625,8.0,8.25,32.0
A4,4,2,65536,43.65863149988279,16,0.19214088284049563,5584,0.750688,128.015625,8.0,8.25,64.0
B,8,1,8192,10.816091000000947,8,0.1938918598225168,656,0.75813,16.0078125,8.0,0.0,16.0
B,8,1,16384,21.693947000002314,8,0.1933398288471476,1360,0.755602,32.0078125,8.0,0.0,32.0
B,8,1,32768,43.449659000006385,8,0.19306499045255035,2768,0.754344,64.0078125,8.0,0.0,64.0
B,8,1,65536,86.96108299976913,8,0.19292786406575965,5584,0.753716,128.0078125,8.0,0.0,128.0
1 mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 A1 1 8 8192 2.2471645000005376 64 0.11665545624270428 656 0.457688 16.0625 8.0 57.75 2.0
3 A1 1 8 16384 3.6068965000013704 64 0.14535709577464812 1360 0.569049 32.0625 8.0 57.75 4.0
4 A1 1 8 32768 6.326360500001814 64 0.1657471147905734 2768 0.648161 64.0625 8.0 57.75 8.0
5 A1 1 8 65536 11.765288499959512 64 0.17824909265982342 5584 0.696668 128.0625 8.0 57.75 16.0
6 A2 2 4 8192 3.3555127500005475 32 0.15624676139283236 656 0.611829 16.03125 8.0 24.75 4.0
7 A2 2 4 16384 6.074976750001311 32 0.17260576347056114 1360 0.675064 32.03125 8.0 24.75 8.0
8 A2 2 4 32768 11.513904750002315 32 0.18214081543451005 2768 0.711922 64.03125 8.0 24.75 16.0
9 A2 2 4 65536 22.391760749934242 32 0.18731461303283142 5584 0.731921 128.03125 8.0 24.75 32.0
10 A4 4 2 8192 5.586135500000557 16 0.1877104484844272 656 0.734318 16.015625 8.0 8.25 8.0
11 A4 4 2 16384 11.025063500001211 16 0.19021677290108474 1360 0.743578 32.015625 8.0 8.25 16.0
12 A4 4 2 32768 21.902919500003218 16 0.19149520227204342 2768 0.748302 64.015625 8.0 8.25 32.0
13 A4 4 2 65536 43.65863149988279 16 0.19214088284049563 5584 0.750688 128.015625 8.0 8.25 64.0
14 B 8 1 8192 10.816091000000947 8 0.1938918598225168 656 0.75813 16.0078125 8.0 0.0 16.0
15 B 8 1 16384 21.693947000002314 8 0.1933398288471476 1360 0.755602 32.0078125 8.0 0.0 32.0
16 B 8 1 32768 43.449659000006385 8 0.19306499045255035 2768 0.754344 64.0078125 8.0 0.0 64.0
17 B 8 1 65536 86.96108299976913 8 0.19292786406575965 5584 0.753716 128.0078125 8.0 0.0 128.0
@@ -0,0 +1,17 @@
variant,mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,math_pipeline_us,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
composite_fused,A1,1,8,8192,35.53158299944201,64,0.3512557265116541,4800,97.32531200143696,0.0290446,16.0625,64.0,114688.0,2.0
composite_fused,A1,1,8,16384,63.02314799463935,64,0.4196030322618738,9920,208.55424000307917,0.0326229,32.0625,64.0,229376.0,4.0
composite_fused,A1,1,8,32768,118.00299802135117,64,0.4607744625054221,20160,431.01209600543973,0.0347788,64.0625,64.0,458752.0,8.0
composite_fused,A1,1,8,65536,227.95666800683736,64,0.4835519703723695,40640,875.9278079059123,0.0359717,128.0625,64.0,917504.0,16.0
composite_fused,A2,2,4,8192,35.27508350025257,32,0.5747630902258046,2400,96.40870400077105,0.0585116,16.0625,64.0,49152.0,4.0
composite_fused,A2,2,4,16384,65.58160349091561,32,0.653336388872743,4960,206.59008000165224,0.0627005,32.0625,64.0,98304.0,8.0
composite_fused,A2,2,4,32768,126.19361352364719,32,0.69726913692688,10080,426.9528320015669,0.0650429,64.0625,64.0,196608.0,16.0
composite_fused,A2,2,4,65536,247.42072338639758,32,0.7205501525262498,20320,867.678335950613,0.0662839,128.0625,64.0,393216.0,32.0
composite_fused,A4,4,2,8192,45.62825600103196,16,0.7872118539828873,1200,92.28038400042057,0.0904703,16.0625,64.0,16384.0,8.0
composite_fused,A4,4,2,16384,87.81637548340065,16,0.8628360665552867,2480,197.74368000090124,0.09365,32.0625,64.0,32768.0,16.0
composite_fused,A4,4,2,32768,172.19397554247547,16,0.9029073142513345,5040,408.6702720000148,0.0953343,64.0625,64.0,65536.0,32.0
composite_fused,A4,4,2,65536,340.9491752808783,16,0.9235491703853554,10160,830.5234559737444,0.096202,128.0625,64.0,131072.0,64.0
composite_fused,B,8,1,8192,68.33786600000411,8,2.036048828365075,712,121.6138560003899,0.120811,16.0625,64.0,0.0,16.0
composite_fused,B,8,1,16384,133.87386600000133,8,2.209234414765302,1480,260.6011200008355,0.122862,32.0625,64.0,0.0,32.0
composite_fused,B,8,1,32768,264.9458659999771,8,2.2985744263323977,3016,538.575647996068,0.12392,64.0625,64.0,0.0,64.0
composite_fused,B,8,1,65536,527.0898659999762,8,2.3439567929361727,6088,1094.5247039788662,0.124457,128.0625,64.0,0.0,128.0
1 variant mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count math_pipeline_us hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 composite_fused A1 1 8 8192 35.53158299944201 64 0.3512557265116541 4800 97.32531200143696 0.0290446 16.0625 64.0 114688.0 2.0
3 composite_fused A1 1 8 16384 63.02314799463935 64 0.4196030322618738 9920 208.55424000307917 0.0326229 32.0625 64.0 229376.0 4.0
4 composite_fused A1 1 8 32768 118.00299802135117 64 0.4607744625054221 20160 431.01209600543973 0.0347788 64.0625 64.0 458752.0 8.0
5 composite_fused A1 1 8 65536 227.95666800683736 64 0.4835519703723695 40640 875.9278079059123 0.0359717 128.0625 64.0 917504.0 16.0
6 composite_fused A2 2 4 8192 35.27508350025257 32 0.5747630902258046 2400 96.40870400077105 0.0585116 16.0625 64.0 49152.0 4.0
7 composite_fused A2 2 4 16384 65.58160349091561 32 0.653336388872743 4960 206.59008000165224 0.0627005 32.0625 64.0 98304.0 8.0
8 composite_fused A2 2 4 32768 126.19361352364719 32 0.69726913692688 10080 426.9528320015669 0.0650429 64.0625 64.0 196608.0 16.0
9 composite_fused A2 2 4 65536 247.42072338639758 32 0.7205501525262498 20320 867.678335950613 0.0662839 128.0625 64.0 393216.0 32.0
10 composite_fused A4 4 2 8192 45.62825600103196 16 0.7872118539828873 1200 92.28038400042057 0.0904703 16.0625 64.0 16384.0 8.0
11 composite_fused A4 4 2 16384 87.81637548340065 16 0.8628360665552867 2480 197.74368000090124 0.09365 32.0625 64.0 32768.0 16.0
12 composite_fused A4 4 2 32768 172.19397554247547 16 0.9029073142513345 5040 408.6702720000148 0.0953343 64.0625 64.0 65536.0 32.0
13 composite_fused A4 4 2 65536 340.9491752808783 16 0.9235491703853554 10160 830.5234559737444 0.096202 128.0625 64.0 131072.0 64.0
14 composite_fused B 8 1 8192 68.33786600000411 8 2.036048828365075 712 121.6138560003899 0.120811 16.0625 64.0 0.0 16.0
15 composite_fused B 8 1 16384 133.87386600000133 8 2.209234414765302 1480 260.6011200008355 0.122862 32.0625 64.0 0.0 32.0
16 composite_fused B 8 1 32768 264.9458659999771 8 2.2985744263323977 3016 538.575647996068 0.12392 64.0625 64.0 0.0 64.0
17 composite_fused B 8 1 65536 527.0898659999762 8 2.3439567929361727 6088 1094.5247039788662 0.124457 128.0625 64.0 0.0 128.0
@@ -0,0 +1,17 @@
variant,mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,math_pipeline_us,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
composite,A1,1,8,8192,35.312546999435405,64,0.19162004939598534,5248,0.0,0.0292247,16.0625,64.0,114688.0,2.0
composite,A1,1,8,16384,62.80411199463298,64,0.21548245124172694,10880,0.0,0.0327367,32.0625,64.0,229376.0,4.0
composite,A1,1,8,32768,117.78396202135924,64,0.2297967187462748,22144,0.0,0.0348435,64.0625,64.0,458752.0,8.0
composite,A1,1,8,65536,227.7376320068799,64,0.23769780826259174,44672,0.0,0.0360063,128.0625,64.0,917504.0,16.0
composite,A2,2,4,8192,35.873466499721395,32,0.21785371648964683,2624,0.0,0.0575356,16.0625,64.0,49152.0,4.0
composite,A2,2,4,16384,67.67155399355386,32,0.23097350479268527,5440,0.0,0.0607641,32.0625,64.0,98304.0,8.0
composite,A2,2,4,32768,129.35661752446276,32,0.24166271963282046,11072,0.0,0.0634525,64.0625,64.0,196608.0,16.0
composite,A2,2,4,65536,254.34501247150266,32,0.24581313146006306,22336,0.0,0.0644793,128.0625,64.0,393216.0,32.0
composite,A4,4,2,8192,46.88723350110906,16,0.2114076532174909,1312,0.0,0.088041,16.0625,64.0,16384.0,8.0
composite,A4,4,2,16384,89.10037348339473,16,0.22249783278807841,2720,0.0,0.0923004,32.0625,64.0,32768.0,16.0
composite,A4,4,2,32768,173.47797354247328,16,0.22855512537871095,5536,0.0,0.0946287,64.0625,64.0,65536.0,32.0
composite,A4,4,2,65536,342.2399932809714,16,0.23170453934016594,11168,0.0,0.0958392,128.0625,64.0,131072.0,64.0
composite,B,8,1,8192,68.73804599999497,8,0.3341725483628459,656,0.0,0.120108,16.0625,64.0,0.0,16.0
composite,B,8,1,16384,134.27404599999218,8,0.3421415930417734,1360,0.0,0.122496,32.0625,64.0,0.0,32.0
composite,B,8,1,32768,265.34604599999824,8,0.3462703641501576,2768,0.0,0.123733,64.0625,64.0,0.0,64.0
composite,B,8,1,65536,527.4900460000299,8,0.3483723443539211,5584,0.0,0.124363,128.0625,64.0,0.0,128.0
1 variant mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count math_pipeline_us hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 composite A1 1 8 8192 35.312546999435405 64 0.19162004939598534 5248 0.0 0.0292247 16.0625 64.0 114688.0 2.0
3 composite A1 1 8 16384 62.80411199463298 64 0.21548245124172694 10880 0.0 0.0327367 32.0625 64.0 229376.0 4.0
4 composite A1 1 8 32768 117.78396202135924 64 0.2297967187462748 22144 0.0 0.0348435 64.0625 64.0 458752.0 8.0
5 composite A1 1 8 65536 227.7376320068799 64 0.23769780826259174 44672 0.0 0.0360063 128.0625 64.0 917504.0 16.0
6 composite A2 2 4 8192 35.873466499721395 32 0.21785371648964683 2624 0.0 0.0575356 16.0625 64.0 49152.0 4.0
7 composite A2 2 4 16384 67.67155399355386 32 0.23097350479268527 5440 0.0 0.0607641 32.0625 64.0 98304.0 8.0
8 composite A2 2 4 32768 129.35661752446276 32 0.24166271963282046 11072 0.0 0.0634525 64.0625 64.0 196608.0 16.0
9 composite A2 2 4 65536 254.34501247150266 32 0.24581313146006306 22336 0.0 0.0644793 128.0625 64.0 393216.0 32.0
10 composite A4 4 2 8192 46.88723350110906 16 0.2114076532174909 1312 0.0 0.088041 16.0625 64.0 16384.0 8.0
11 composite A4 4 2 16384 89.10037348339473 16 0.22249783278807841 2720 0.0 0.0923004 32.0625 64.0 32768.0 16.0
12 composite A4 4 2 32768 173.47797354247328 16 0.22855512537871095 5536 0.0 0.0946287 64.0625 64.0 65536.0 32.0
13 composite A4 4 2 65536 342.2399932809714 16 0.23170453934016594 11168 0.0 0.0958392 128.0625 64.0 131072.0 64.0
14 composite B 8 1 8192 68.73804599999497 8 0.3341725483628459 656 0.0 0.120108 16.0625 64.0 0.0 16.0
15 composite B 8 1 16384 134.27404599999218 8 0.3421415930417734 1360 0.0 0.122496 32.0625 64.0 0.0 32.0
16 composite B 8 1 32768 265.34604599999824 8 0.3462703641501576 2768 0.0 0.123733 64.0625 64.0 0.0 64.0
17 composite B 8 1 65536 527.4900460000299 8 0.3483723443539211 5584 0.0 0.124363 128.0625 64.0 0.0 128.0
@@ -0,0 +1,17 @@
mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
A1,1,8,8192,1103.359903999566,64,0.0019006962210579498,5248,0.001870649814732448,16.0625,64.0,114688.0,2.0
A1,1,8,16384,2182.457823968016,64,0.0019218259129421755,10880,0.0018841143021603984,32.0625,64.0,229376.0,4.0
A1,1,8,32768,4343.2822238495155,64,0.0019313983221991736,22144,0.0018898150239762977,64.0625,64.0,458752.0,8.0
A1,1,8,65536,8666.440783650422,64,0.001935883071136998,44672,0.0018923570136126979,128.0625,64.0,917504.0,16.0
A2,2,4,8192,1097.2535885006619,32,0.003822547535007557,2624,0.0037621203004135906,16.0625,64.0,49152.0,4.0
A2,2,4,16384,2179.78425743804,32,0.003848366172648072,5440,0.003772850442394648,32.0625,64.0,98304.0,8.0
A2,2,4,32768,4344.834905351148,32,0.003861416225357031,11072,0.003778279349528763,64.0625,64.0,196608.0,16.0
A2,2,4,65536,8674.946611121853,32,0.003867969856656271,22336,0.003781003096658628,128.0625,64.0,393216.0,32.0
A4,4,2,8192,1103.675383502932,16,0.007600611670231015,1312,0.007480460399321815,16.0625,64.0,16384.0,8.0
A4,4,2,16384,2197.0616234262543,16,0.00763620638634706,2720,0.007486362614786297,32.0625,64.0,32768.0,16.0
A4,4,2,32768,4383.834103368879,16,0.007654129058897454,5536,0.007489334501679554,64.0625,64.0,65536.0,32.0
A4,4,2,65536,8757.379062883949,16,0.007663121981838932,11168,0.007490825682997995,128.0625,64.0,131072.0,64.0
B,8,1,8192,1125.9457520018113,8,0.014900554462921552,656,0.01466500492643045,16.0625,64.0,0.0,16.0
B,8,1,16384,2242.543191943168,8,0.014962669223294344,1360,0.014669059716747552,32.0625,64.0,0.0,32.0
B,8,1,32768,4475.650071825993,8,0.014994216018466134,2768,0.014671388277951352,64.0625,64.0,0.0,64.0
B,8,1,65536,8942.79183160376,8,0.015008481750139946,5584,0.014671033662702533,128.0625,64.0,0.0,128.0
1 mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 A1 1 8 8192 1103.359903999566 64 0.0019006962210579498 5248 0.001870649814732448 16.0625 64.0 114688.0 2.0
3 A1 1 8 16384 2182.457823968016 64 0.0019218259129421755 10880 0.0018841143021603984 32.0625 64.0 229376.0 4.0
4 A1 1 8 32768 4343.2822238495155 64 0.0019313983221991736 22144 0.0018898150239762977 64.0625 64.0 458752.0 8.0
5 A1 1 8 65536 8666.440783650422 64 0.001935883071136998 44672 0.0018923570136126979 128.0625 64.0 917504.0 16.0
6 A2 2 4 8192 1097.2535885006619 32 0.003822547535007557 2624 0.0037621203004135906 16.0625 64.0 49152.0 4.0
7 A2 2 4 16384 2179.78425743804 32 0.003848366172648072 5440 0.003772850442394648 32.0625 64.0 98304.0 8.0
8 A2 2 4 32768 4344.834905351148 32 0.003861416225357031 11072 0.003778279349528763 64.0625 64.0 196608.0 16.0
9 A2 2 4 65536 8674.946611121853 32 0.003867969856656271 22336 0.003781003096658628 128.0625 64.0 393216.0 32.0
10 A4 4 2 8192 1103.675383502932 16 0.007600611670231015 1312 0.007480460399321815 16.0625 64.0 16384.0 8.0
11 A4 4 2 16384 2197.0616234262543 16 0.00763620638634706 2720 0.007486362614786297 32.0625 64.0 32768.0 16.0
12 A4 4 2 32768 4383.834103368879 16 0.007654129058897454 5536 0.007489334501679554 64.0625 64.0 65536.0 32.0
13 A4 4 2 65536 8757.379062883949 16 0.007663121981838932 11168 0.007490825682997995 128.0625 64.0 131072.0 64.0
14 B 8 1 8192 1125.9457520018113 8 0.014900554462921552 656 0.01466500492643045 16.0625 64.0 0.0 16.0
15 B 8 1 16384 2242.543191943168 8 0.014962669223294344 1360 0.014669059716747552 32.0625 64.0 0.0 32.0
16 B 8 1 32768 4475.650071825993 8 0.014994216018466134 2768 0.014671388277951352 64.0625 64.0 0.0 64.0
17 B 8 1 65536 8942.79183160376 8 0.015008481750139946 5584 0.014671033662702533 128.0625 64.0 0.0 128.0
@@ -0,0 +1,17 @@
mode,kv_per_cube,C,S_kv,wall_us,n_pe,gemm_util,math_count,hbm_bw_util,hbm_read_mb,hbm_write_kb,ipcq_kb,kv_cache_per_cube_mb
A1,1,8,8192,34.765078999497696,64,0.06032352177393696,5248,0.029685,16.0625,64.0,114688.0,2.0
A1,1,8,16384,62.2566439945763,64,0.06737118692698127,10880,0.0330246,32.0625,64.0,229376.0,4.0
A1,1,8,32768,117.23649402130023,64,0.07155287327558905,22144,0.0350062,64.0625,64.0,458752.0,8.0
A1,1,8,65536,227.190164006345,64,0.07384657726472504,44672,0.0360931,128.0625,64.0,917504.0,16.0
A2,2,4,8192,34.98345650027832,32,0.11989392757596813,2624,0.0589993,16.0625,64.0,49152.0,4.0
A2,2,4,16384,65.32612049105158,32,0.12841123790818756,5440,0.0629457,32.0625,64.0,98304.0,8.0
A2,2,4,32768,126.01144852332584,32,0.13314041062637966,11072,0.0651369,64.0625,64.0,196608.0,16.0
A2,2,4,65536,247.3821043861434,32,0.1356380732680083,22336,0.0662942,128.0625,64.0,393216.0,32.0
A4,4,2,8192,46.571509501108665,16,0.18012317165281877,1312,0.0886379,16.0625,64.0,16384.0,8.0
A4,4,2,16384,88.98265048367577,16,0.18854479956273557,2720,0.0924225,32.0625,64.0,32768.0,16.0
A4,4,2,32768,173.195009542468,16,0.19373786859461295,5536,0.0947833,64.0625,64.0,65536.0,32.0
A4,4,2,65536,341.9424492809754,16,0.19625777419912674,11168,0.0959226,128.0625,64.0,131072.0,64.0
B,8,1,8192,68.50162199999485,8,0.24491706196386726,656,0.120523,16.0625,64.0,0.0,16.0
B,8,1,16384,134.03762199999207,8,0.25033592434218427,1360,0.122712,32.0625,64.0,0.0,32.0
B,8,1,32768,265.1096219999986,8,0.25313628186615866,2768,0.123843,64.0625,64.0,0.0,64.0
B,8,1,65536,527.2536220000293,8,0.2545600872134325,5584,0.124418,128.0625,64.0,0.0,128.0
1 mode kv_per_cube C S_kv wall_us n_pe gemm_util math_count hbm_bw_util hbm_read_mb hbm_write_kb ipcq_kb kv_cache_per_cube_mb
2 A1 1 8 8192 34.765078999497696 64 0.06032352177393696 5248 0.029685 16.0625 64.0 114688.0 2.0
3 A1 1 8 16384 62.2566439945763 64 0.06737118692698127 10880 0.0330246 32.0625 64.0 229376.0 4.0
4 A1 1 8 32768 117.23649402130023 64 0.07155287327558905 22144 0.0350062 64.0625 64.0 458752.0 8.0
5 A1 1 8 65536 227.190164006345 64 0.07384657726472504 44672 0.0360931 128.0625 64.0 917504.0 16.0
6 A2 2 4 8192 34.98345650027832 32 0.11989392757596813 2624 0.0589993 16.0625 64.0 49152.0 4.0
7 A2 2 4 16384 65.32612049105158 32 0.12841123790818756 5440 0.0629457 32.0625 64.0 98304.0 8.0
8 A2 2 4 32768 126.01144852332584 32 0.13314041062637966 11072 0.0651369 64.0625 64.0 196608.0 16.0
9 A2 2 4 65536 247.3821043861434 32 0.1356380732680083 22336 0.0662942 128.0625 64.0 393216.0 32.0
10 A4 4 2 8192 46.571509501108665 16 0.18012317165281877 1312 0.0886379 16.0625 64.0 16384.0 8.0
11 A4 4 2 16384 88.98265048367577 16 0.18854479956273557 2720 0.0924225 32.0625 64.0 32768.0 16.0
12 A4 4 2 32768 173.195009542468 16 0.19373786859461295 5536 0.0947833 64.0625 64.0 65536.0 32.0
13 A4 4 2 65536 341.9424492809754 16 0.19625777419912674 11168 0.0959226 128.0625 64.0 131072.0 64.0
14 B 8 1 8192 68.50162199999485 8 0.24491706196386726 656 0.120523 16.0625 64.0 0.0 16.0
15 B 8 1 16384 134.03762199999207 8 0.25033592434218427 1360 0.122712 32.0625 64.0 0.0 32.0
16 B 8 1 32768 265.1096219999986 8 0.25313628186615866 2768 0.123843 64.0625 64.0 0.0 64.0
17 B 8 1 65536 527.2536220000293 8 0.2545600872134325 5584 0.124418 128.0625 64.0 0.0 128.0
+204
View File
@@ -0,0 +1,204 @@
#!/usr/bin/env python3
"""SCRATCH EXPERIMENT (not production; do not commit).
Question: does charging the *primitive* decode kernel for per-HW-tile
(16x16x16) CPU dispatch flip the "composite gives no decode-latency
benefit" conclusion?
We monkeypatch TLContext.dot so that, in the primitive kernel, every
tl.dot whose (M,K,N) exceeds the HW GEMM tile (mac_m/mac_k/mac_n) is
split by the CPU into ceil(M/mac_m)*ceil(K/mac_k)*ceil(N/mac_n)
HW-tile-sized GemmCmds. Each tile GemmCmd is emitted through the normal
_emit() path, so it (a) charges PE_CPU dispatch overhead via
_charge_dispatch (PeCpuOverheadCmd), and (b) blocks like a normal
single-op GemmCmd on PE_GEMM at the cycle-accurate ceil-product latency.
We also inject mac_m/mac_k/mac_n into every pe_gemm topology node so BOTH
the primitive-tiled and the composite variants run on the *same*
cycle-accurate engine (fair comparison). Composite is left untouched:
the CPU emits ONE CompositeCmd, and PE_SCHEDULER tiles internally (no
per-HW-tile CPU dispatch).
Data correctness:
Inputs are ctx.zeros (q/k/v), so every matmul result is zeros and the
DataExecutor replay is trivial. To keep replay numerically correct
regardless, exactly ONE emitted tile per dot carries the *real* full
operands+output handles (so the DataExecutor computes the true (M,N)
result via the recorded handle shapes), while its timing fields
(m,k,n) are the HW-tile size so the engine charges exactly one tile of
cycle time. The remaining n_tiles-1 emitted tiles are timing-only
GemmCmds (16x16x16) writing to throwaway scratch. Net: n_tiles tiles
of engine time + n_tiles dispatch charges, and a correct final output.
Engine mode: enable_data=True (same as the production sweep's
_engine_latency_ns), op_log end-to-end latency.
"""
from __future__ import annotations
import json
from math import ceil
from pathlib import Path
# --- HW GEMM tile under test -------------------------------------------------
MAC_M = 16
MAC_K = 16
MAC_N = 16
# Legacy alt to also try: (8, 16, 32)
S_KV_LATENCY = (8192, 32_768, 65_536, 131_072)
ROOT = Path(__file__).resolve().parent
SWEEP_JSON = (
ROOT / "src" / "kernbench" / "benches" / "1H_milestone_output"
/ "gqa" / "long_ctx" / "sweep_decode_composite.json"
)
# --- mac-dim topology override -----------------------------------------------
def _topo_with_mac(mac_m: int, mac_k: int, mac_n: int):
"""Compiled topology with mac dims injected into every pe_gemm node."""
from kernbench.topology.builder import resolve_topology
handle = resolve_topology("topology.yaml")
g = handle.topology_obj
n = 0
for node in g.nodes.values():
if node.kind == "pe_gemm":
node.attrs["mac_m"] = mac_m
node.attrs["mac_k"] = mac_k
node.attrs["mac_n"] = mac_n
n += 1
print(f" injected mac=({mac_m},{mac_k},{mac_n}) into {n} pe_gemm nodes")
return handle
# --- tiling monkeypatch for TLContext.dot ------------------------------------
def _make_tiled_dot(orig_dot, mac_m: int, mac_k: int, mac_n: int):
from kernbench.common.pe_commands import GemmCmd
def tiled_dot(self, a, b):
if len(a.shape) < 2 or len(b.shape) < 2:
return orig_dot(self, a, b)
m, k = a.shape[-2], a.shape[-1]
k2, n = b.shape[-2], b.shape[-1]
if k != k2:
raise ValueError(f"dot shape mismatch: a.K={k} != b.K={k2}")
n_tiles = ceil(m / mac_m) * ceil(k / mac_k) * ceil(n / mac_n)
out_shape = (*a.shape[:-2], m, n)
out = self._make_compute_out(shape=out_shape, dtype=a.dtype)
self._await_pending(a, b)
if n_tiles <= 1:
self._emit(GemmCmd(a=a, b=b, out=out, m=m, k=k, n=n))
return out
# One real-data tile: full handles (so DataExecutor computes the
# true result), but timing fields = HW tile (one tile of cycles).
self._emit(GemmCmd(a=a, b=b, out=out, m=mac_m, k=mac_k, n=mac_n))
# Remaining timing-only tiles: throwaway scratch, 16x16x16.
scratch = self._make_compute_out(shape=(mac_m, mac_n), dtype=a.dtype)
for _ in range(n_tiles - 1):
self._emit(GemmCmd(a=a, b=b, out=scratch,
m=mac_m, k=mac_k, n=mac_n))
return out
return tiled_dot
# --- latency runner (replicates sweep's _engine_latency_ns) ------------------
def _engine_latency_ns(variant: str, S_kv: int, topo) -> float:
from kernbench.benches.gqa_helpers.long_ctx.gqa_decode_long_ctx_composite import ( # noqa: E501
_end_to_end_ns, _run_panel_fn,
)
from kernbench.runtime_api.bench_runner import run_bench
from kernbench.runtime_api.types import resolve_device
from kernbench.sim_engine.engine import GraphEngine
result = run_bench(
topology=topo, bench_fn=_run_panel_fn(variant, S_kv),
device=resolve_device(None),
engine_factory=lambda t, d: GraphEngine(
getattr(t, "topology_obj", t), enable_data=True,
),
)
if not result.completion.ok:
raise RuntimeError(
f"{variant}@{S_kv} failed: {result.completion}"
)
return _end_to_end_ns(result.engine.op_log)
def _emit_dispatch(variant: str, S_kv: int) -> int:
"""PE_CPU command count at the center rank (cube 6, pe 0)."""
from kernbench.benches.gqa_helpers.long_ctx.gqa_decode_long_ctx_composite import ( # noqa: E501
_emit_dispatch as prod_emit,
)
return prod_emit(variant, S_kv)[0]
def main() -> None:
import kernbench.triton_emu.tl_context as tlc
# Baseline (A): primitive UNTILED latencies from the production sweep
# (mac=0 / TFLOPS model). Read straight off the committed sweep JSON.
sweep = json.loads(SWEEP_JSON.read_text())
base_A = {}
for r in sweep["rows"]:
if r["variant"] == "primitive" and r["latency_ns"] is not None:
base_A[r["S_kv"]] = r["latency_ns"]
print(f"== mac tile = ({MAC_M},{MAC_K},{MAC_N}) ==")
# --- command-count sanity (emit-time, mac-independent) ---------------
orig_dot = tlc.TLContext.dot
print("\n[dispatch counts @ S_kv=131072]")
n_prim_untiled = _emit_dispatch("primitive", 131072)
tlc.TLContext.dot = _make_tiled_dot(orig_dot, MAC_M, MAC_K, MAC_N)
try:
n_prim_tiled = _emit_dispatch("primitive", 131072)
n_comp = None
finally:
tlc.TLContext.dot = orig_dot
n_comp = _emit_dispatch("composite", 131072)
print(f" primitive UNTILED PE_CPU cmds : {n_prim_untiled}")
print(f" primitive TILED PE_CPU cmds : {n_prim_tiled} "
f"(x{n_prim_tiled / max(n_prim_untiled,1):.0f})")
print(f" composite PE_CPU cmds : {n_comp}")
# --- latency sweep ----------------------------------------------------
rows = []
topo = _topo_with_mac(MAC_M, MAC_K, MAC_N)
for S_kv in S_KV_LATENCY:
A = base_A.get(S_kv)
# (C) composite on the mac engine (untouched dot path)
C = _engine_latency_ns("composite", S_kv, topo)
# (B) primitive TILED on the mac engine
tlc.TLContext.dot = _make_tiled_dot(orig_dot, MAC_M, MAC_K, MAC_N)
try:
B = _engine_latency_ns("primitive", S_kv, topo)
finally:
tlc.TLContext.dot = orig_dot
gap_pct = (B - C) / C * 100.0 if C else float("nan")
rows.append((S_kv, A, B, C, gap_pct))
print(f" S_kv={S_kv:>7}: A(untiled)={A!s:>12} "
f"B(tiled)={B:12.2f} C(comp)={C:12.2f} (B-C)/C={gap_pct:+6.1f}%")
# --- final table ------------------------------------------------------
print("\n==================== RESULT TABLE ====================")
print(f"{'S_kv':>8} | {'A untiled(ns)':>14} | {'B tiled(ns)':>14} | "
f"{'C comp(ns)':>14} | {'(B-C)/C':>9}")
print("-" * 72)
for S_kv, A, B, C, gap in rows:
a_s = f"{A:.2f}" if A is not None else "n/a"
print(f"{S_kv:>8} | {a_s:>14} | {B:>14.2f} | {C:>14.2f} | "
f"{gap:>+8.1f}%")
if __name__ == "__main__":
main()
@@ -0,0 +1,362 @@
"""Measured comm overlay for all 6 GQA decode KV placements.
Runs the simulator for Cases 1-6 (the same 6 placements the chart in
paper_plot_gqa_4cases_summary.py covers analytically) at S_kv = 64 K,
sums actual IPCQ-copy bytes from the engine op_log, projects to
per-token (x80 layers), scales the partial-score-AR component of
Cases 4/5 from S_kv = 64 K -> S_kv = 1 M (linear in S_kv; other
cases are S_kv-independent), adds the constant Wo + FFN AR
(1.25 MB / token), and writes the result to JSON for
paper_plot_gqa_4cases_summary.py to overlay on the analytical bars.
Single layer of decode attention only — the projection × 80 takes
the per-layer measurement to a per-token total.
Usage:
python scripts/paper/measure_gqa_decode_placement_comm.py
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
from kernbench.benches.gqa_helpers.long_ctx._gqa_attention_decode_long_ctx_cube_repl_pe_sp import (
gqa_attention_decode_long_ctx_cube_repl_pe_sp_kernel as _case3_kernel,
)
from kernbench.benches.gqa_helpers.long_ctx._gqa_attention_decode_long_ctx_cube_repl_pe_tp import (
gqa_attention_decode_long_ctx_cube_repl_pe_tp_kernel as _case1_kernel,
)
from kernbench.benches.gqa_helpers.long_ctx._gqa_attention_decode_long_ctx_cube_sp_pe_sp import (
gqa_attention_decode_long_ctx_cube_sp_pe_sp_kernel as _case6_kernel,
)
from kernbench.benches.gqa_helpers.long_ctx._gqa_attention_decode_long_ctx_cube_sp_pe_tp import (
gqa_attention_decode_long_ctx_cube_sp_pe_tp_kernel as _case2_kernel,
)
from kernbench.benches.gqa_helpers.long_ctx._gqa_attention_decode_long_ctx_cube_sp_pe_tp_dhead import (
gqa_attention_decode_long_ctx_cube_sp_pe_tp_dhead_kernel as _case4_kernel,
)
from kernbench.benches.gqa_helpers.long_ctx._gqa_attention_decode_long_ctx_cube_tp_dhead_pe_sp import (
gqa_attention_decode_long_ctx_cube_tp_dhead_pe_sp_kernel as _case5_kernel,
)
from kernbench.benches.gqa_helpers.shared._gqa_panel_helpers import _ccl_cfg
from kernbench.ccl.sfr_config import configure_sfr_intercube_multisip
from kernbench.policy.placement.dp import DPPolicy
from kernbench.runtime_api.bench_runner import run_bench
from kernbench.runtime_api.types import resolve_device
from kernbench.sim_engine.engine import GraphEngine
from kernbench.topology.builder import resolve_topology
_C = 8
_P = 8
_N_LAYERS = 80
_S_KV_MEAS = 64 * 1024 # per-run simulator S_kv (1/16th of headline)
_S_KV_HEADLINE = 1 << 20 # 1 Mi tokens, the chart's headline S_kv
# Per-cube S_kv share for the d_head-TP partial-score AR cost.
# Case 4 (Cube-SP × PE-TP_dhead): per-cube = S_kv / C
# Case 5 (Cube-TP_dhead × PE-SP): per-cube = S_kv (KV replicated across
# cubes for the cube-axis d_head-TP), so partial-score AR scales
# with the full S_kv.
# Headline / measured per-cube ratios give the partial-score-AR scale-up
# from S_kv = 64 K to S_kv = 1 M. (m,,O) AR is S_kv-independent.
_PARTIAL_SCORE_SCALE = _S_KV_HEADLINE / _S_KV_MEAS # = 16
# Per-token Wo + FFN AR (constant across all cases, comes from the
# attn-output and FFN-down all-reduces NOT measured by the attention-
# only kernel run here).
_WO_PER_LAYER_BYTES = 8 * 1024
_FFN_PER_LAYER_BYTES = 8 * 1024
_WO_FFN_PER_TOKEN_BYTES = (
(_WO_PER_LAYER_BYTES + _FFN_PER_LAYER_BYTES) * _N_LAYERS
) # 1.25 MB
# Total PE count in one KV-head group — average per-PE comm = total / N.
_NUM_PES = _C * _P
# Total partial-score-AR slice produced by the attention compute when
# d_head is sharded. Used to split measured IPCQ traffic into the
# S_kv-scaling component (partial scores) vs the S_kv-independent
# component ((m,,O) merge). Same as the analytical formula in
# paper_plot_gqa_4cases_summary.py: h_q · S_q · per_cube_S_kv · 2 bytes.
_H_Q = 8
_S_Q = 1
_BYTES_PER_ELEM = 2
_PARAMS = dict(C=_C, P=_P, T_q=_S_Q, S_kv=_S_KV_MEAS,
d_head=128, h_q=_H_Q, h_kv=1)
def _bench_fn_case1(ctx):
"""Case 1: Cube-Repl x PE-repl (PE-TP doesn't shard KV)."""
p = _PARAMS
configure_sfr_intercube_multisip(ctx.engine, ctx.spec, _ccl_cfg())
dp = DPPolicy(cube="replicate", pe="replicate",
num_cubes=p["C"], num_pes=p["P"])
q = ctx.zeros((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp, name="q_c1")
k = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp, name="k_c1")
v = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp, name="v_c1")
o = ctx.empty((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp, name="o_c1")
ctx.launch("case1_repl_repl", _case1_kernel,
q, k, v, o,
p["T_q"], p["S_kv"], p["h_q"], p["h_kv"],
p["d_head"], p["C"], p["P"],
_auto_dim_remap=False)
def _bench_fn_case2(ctx):
"""Case 2: Cube-SP x PE-repl (PE-TP doesn't shard KV)."""
p = _PARAMS
configure_sfr_intercube_multisip(ctx.engine, ctx.spec, _ccl_cfg())
dp_full = DPPolicy(cube="replicate", pe="replicate",
num_cubes=p["C"], num_pes=p["P"])
dp_kv = DPPolicy(cube="row_wise", pe="replicate",
num_cubes=p["C"], num_pes=p["P"])
q = ctx.zeros((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="q_c2")
k = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="k_c2")
v = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="v_c2")
o = ctx.empty((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="o_c2")
ctx.launch("case2_sp_repl", _case2_kernel,
q, k, v, o,
p["T_q"], p["S_kv"], p["h_q"], p["h_kv"],
p["d_head"], p["C"], p["P"],
_auto_dim_remap=False)
def _bench_fn_case3(ctx):
"""Case 3: Cube-Repl x PE-SP."""
p = _PARAMS
configure_sfr_intercube_multisip(ctx.engine, ctx.spec, _ccl_cfg())
dp_full = DPPolicy(cube="replicate", pe="replicate",
num_cubes=p["C"], num_pes=p["P"])
dp_kv = DPPolicy(cube="replicate", pe="row_wise",
num_cubes=p["C"], num_pes=p["P"])
q = ctx.zeros((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="q_c3")
k = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="k_c3")
v = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="v_c3")
o = ctx.empty((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="o_c3")
ctx.launch("case3_repl_sp", _case3_kernel,
q, k, v, o,
p["T_q"], p["S_kv"], p["h_q"], p["h_kv"],
p["d_head"], p["C"], p["P"],
_auto_dim_remap=False)
def _bench_fn_case4(ctx):
p = _PARAMS
configure_sfr_intercube_multisip(ctx.engine, ctx.spec, _ccl_cfg())
dp_full = DPPolicy(cube="replicate", pe="column_wise",
num_cubes=p["C"], num_pes=p["P"])
dp_kv = DPPolicy(cube="row_wise", pe="column_wise",
num_cubes=p["C"], num_pes=p["P"])
q = ctx.zeros((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="q_c4")
k = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="k_c4")
v = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="v_c4")
o = ctx.empty((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="o_c4")
ctx.launch("case4_dhead_tp", _case4_kernel,
q, k, v, o,
p["T_q"], p["S_kv"], p["h_q"], p["h_kv"],
p["d_head"], p["C"], p["P"],
_auto_dim_remap=False)
def _bench_fn_case5(ctx):
p = _PARAMS
configure_sfr_intercube_multisip(ctx.engine, ctx.spec, _ccl_cfg())
dp_q = DPPolicy(cube="column_wise", pe="replicate",
num_cubes=p["C"], num_pes=p["P"])
dp_kv = DPPolicy(cube="column_wise", pe="row_wise",
num_cubes=p["C"], num_pes=p["P"])
q = ctx.zeros((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_q, name="q_c5")
k = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="k_c5")
v = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="v_c5")
o = ctx.empty((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_q, name="o_c5")
ctx.launch("case5_dhead_tp_inter", _case5_kernel,
q, k, v, o,
p["T_q"], p["S_kv"], p["h_q"], p["h_kv"],
p["d_head"], p["C"], p["P"],
_auto_dim_remap=False)
def _bench_fn_case6(ctx):
p = _PARAMS
configure_sfr_intercube_multisip(ctx.engine, ctx.spec, _ccl_cfg())
dp_full = DPPolicy(cube="replicate", pe="replicate",
num_cubes=p["C"], num_pes=p["P"])
dp_kv = DPPolicy(cube="row_wise", pe="row_wise",
num_cubes=p["C"], num_pes=p["P"])
q = ctx.zeros((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="q_c6")
k = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="k_c6")
v = ctx.zeros((p["S_kv"], p["h_kv"] * p["d_head"]),
dtype="f16", dp=dp_kv, name="v_c6")
o = ctx.empty((p["T_q"], p["h_q"] * p["d_head"]),
dtype="f16", dp=dp_full, name="o_c6")
ctx.launch("case6_sp_sp", _case6_kernel,
q, k, v, o,
p["T_q"], p["S_kv"], p["h_q"], p["h_kv"],
p["d_head"], p["C"], p["P"],
_auto_dim_remap=False)
def _sum_ipcq_bytes(op_log) -> int:
"""Sum nbytes across all ipcq_copy records."""
return sum(
r.params.get("nbytes", 0)
for r in op_log
if r.op_kind == "memory" and r.op_name == "ipcq_copy"
)
def _partial_score_slices(case: int) -> int:
"""Divisor that splits S_kv into partial-score tiles, per the
analytical model in paper_plot_gqa_4cases_summary.py:
partial_score_per_PE = h_q * S_q * (s_kv / slices) * bytes
Case 4 (Cube-SP x PE-TP-dhead): intra-cube AR over d_head shards
on PE axis -> partial tile per PE has per-cube S_kv = s_kv/C.
Case 5 (Cube-TP-dhead x PE-SP): inter-cube AR over d_head shards
on cube axis -> partial tile per PE has per-PE S_kv = s_kv/P.
Cases 1, 2, 3, 6: no partial-score AR (only (m,l,O) merge).
"""
if case == 4:
return _C
if case == 5:
return _P
return 0
def _split_attn_layer_bytes(case: int, total_ipcq_bytes: int,
s_kv: int) -> tuple[int, int]:
"""Split per-layer attention-time IPCQ bytes into:
(partial_score_component, mlo_component).
The partial-score component scales with s_kv (so it must be scaled
when projecting from the measure-time s_kv to the headline s_kv);
the (m,l,O) component is constant in s_kv.
Partial-score size is analytically known per-case (formula in
_partial_score_slices); the remainder is treated as (m,l,O) + any
other S_kv-independent overhead. Per-PE = total / NUM_PES.
"""
per_pe_total = total_ipcq_bytes // _NUM_PES
slices = _partial_score_slices(case)
if slices == 0:
# No partial-score AR for this case.
return 0, per_pe_total
partial_score_per_pe = (
_H_Q * _S_Q * (s_kv // slices) * _BYTES_PER_ELEM
)
partial_score_per_pe = min(partial_score_per_pe, per_pe_total)
mlo_per_pe = per_pe_total - partial_score_per_pe
return partial_score_per_pe, mlo_per_pe
_KERNELS = (
(1, "Case 1 (Cube-Repl x PE-repl)", _bench_fn_case1),
(2, "Case 2 (Cube-SP x PE-repl)", _bench_fn_case2),
(3, "Case 3 (Cube-Repl x PE-SP)", _bench_fn_case3),
(4, "Case 4 (Cube-SP x PE-TP d_head)", _bench_fn_case4),
(5, "Case 5 (Cube-TP d_head x PE-SP)", _bench_fn_case5),
(6, "Case 6 (Cube-SP x PE-SP) [*]", _bench_fn_case6),
)
def main() -> int:
topology = os.environ.get("GQA_1H_TOPOLOGY", "topology.yaml")
topo = resolve_topology(topology)
out: dict = {
"S_kv_measured": _S_KV_MEAS,
"S_kv_headline": _S_KV_HEADLINE,
"n_layers": _N_LAYERS,
"num_pes": _NUM_PES,
"wo_ffn_per_token_bytes": _WO_FFN_PER_TOKEN_BYTES,
"cases": {},
}
print(f"Measuring at S_kv={_S_KV_MEAS:,} ; scaling partial-score AR "
f"to S_kv={_S_KV_HEADLINE:,} (×{int(_PARTIAL_SCORE_SCALE)})")
print()
for case_id, label, bench_fn in _KERNELS:
try:
res = run_bench(
topology=topo, bench_fn=bench_fn,
device=resolve_device(None),
engine_factory=lambda t, d: GraphEngine(
getattr(t, "topology_obj", t), enable_data=True,
),
)
except Exception as e:
print(f" {label:<42} FAIL: {type(e).__name__}: {e}")
return 1
if not res.completion.ok:
print(f" {label:<42} ENGINE FAIL: {res.completion}")
return 1
total_ipcq = _sum_ipcq_bytes(res.engine.op_log)
partial_pe, mlo_pe = _split_attn_layer_bytes(
case_id, total_ipcq, _S_KV_MEAS,
)
# Per-token attention-time comm at S_kv = 1 M:
# (partial_score_per_layer × scale + mlo_per_layer) × 80 layers
scaled_partial_per_token = (
partial_pe * int(_PARTIAL_SCORE_SCALE) * _N_LAYERS
)
mlo_per_token = mlo_pe * _N_LAYERS
attn_per_token = scaled_partial_per_token + mlo_per_token
total_per_token = attn_per_token + _WO_FFN_PER_TOKEN_BYTES
out["cases"][str(case_id)] = {
"label": label,
"total_ipcq_bytes_one_layer": total_ipcq,
"per_pe_partial_score_bytes_one_layer": partial_pe,
"per_pe_mlo_bytes_one_layer": mlo_pe,
"per_pe_attn_bytes_per_token_at_1M": attn_per_token,
"per_pe_total_bytes_per_token_at_1M": total_per_token,
}
print(f" {label:<42} "
f"ipcq_total={total_ipcq:>10,} "
f"per_pe_attn(1L)={(partial_pe + mlo_pe):>9,} "
f"per_pe_total/tok@1M={total_per_token / (1<<20):>7.2f} MB")
out_path = (
Path(__file__).resolve().parents[2]
/ "src" / "kernbench" / "benches"
/ "1H_milestone_output" / "gqa" / "long_ctx"
/ "gqa_long_ctx_6cases_measured_comm.json"
)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(out, indent=2))
print()
print(f"wrote {out_path}")
return 0
if __name__ == "__main__":
sys.exit(main())
+155
View File
@@ -0,0 +1,155 @@
"""Re-emit cube_view.svg in an academic (white-background, large-font)
palette and convert it to PDF for the 1H-codesign-paper Figure 2.
Source of truth: docs/diagrams/cube_view.svg (generated by
src/kernbench/topology/visualizer.py:_render_cube_view_svg, dark theme).
This script does a targeted color/font/size remap on the dark palette so
the resulting figure prints well on a white paper page. If the upstream
palette or geometry in visualizer.py changes, the maps below must be
reviewed.
"""
from __future__ import annotations
import re
import shutil
import subprocess
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
SRC_SVG = REPO / "docs" / "diagrams" / "cube_view.svg"
OUT_DIR = REPO / "docs" / "report" / "1H-codesign-paper" / "figures"
OUT_SVG = OUT_DIR / "cube_architecture.svg"
OUT_PDF = OUT_DIR / "cube_architecture.pdf"
# ── 1. Legend-icon safety: legend rects whose general color rule below
# would turn them white-on-white. Apply BEFORE the wholesale fill rules.
LEGEND_FIXUP: list[tuple[str, str]] = [
# "Relay" legend icon (slate-700 router)
('fill="#334155" stroke="#475569" stroke-width="0.5"',
'fill="#94a3b8" stroke="#475569" stroke-width="0.5"'),
# "Mesh Link" legend icon (slate-600)
('fill="#475569" stroke="#475569" stroke-width="0.5"',
'fill="#94a3b8" stroke="#475569" stroke-width="0.5"'),
]
# ── 2. Color remap: dark theme -> academic (white) theme
COLOR_MAP: list[tuple[str, str]] = [
# page background slate-900 -> white
('fill="#0f172a"', 'fill="#ffffff"'),
# title text slate-400 -> slate-800
('fill="#94a3b8"', 'fill="#1f2937"'),
# subtitle text slate-500 -> slate-600
('fill="#64748b"', 'fill="#475569"'),
# router has-attach stroke slate-500 -> slate-600
('stroke="#64748b"', 'stroke="#475569"'),
# router has-attach fill slate-600 -> white
('fill="#475569"', 'fill="#ffffff"'),
# mesh lines + cube boundary stroke slate-600 -> slate-400
('stroke="#475569"', 'stroke="#94a3b8"'),
# router no-attach fill slate-700 -> white
('fill="#334155"', 'fill="#ffffff"'),
# component block dark fills -> white (PE / M_CPU / SRAM / UCIe)
('fill="#2d1f3d"', 'fill="#ffffff"'),
('fill="#451a03"', 'fill="#ffffff"'),
('fill="#1c1917"', 'fill="#ffffff"'),
('fill="#1e1b4b"', 'fill="#ffffff"'),
# HBM zone background emerald-950 -> emerald-50
('fill="#052e16"', 'fill="#ecfdf5"'),
# router label text white -> slate-800
('fill="white"', 'fill="#1f2937"'),
# boost low-alpha emerald annotations so PE/HBM BW labels read on light bg
('fill="#10b98188"', 'fill="#047857"'),
('fill="#05966988"', 'fill="#059669"'),
]
# ── 3. UCIe palette desaturation: bright violet/indigo/fuchsia is too
# attention-grabbing on a white page; remap to a slate gradient.
# NOTE: side-effect on PE2/PE3 HBM port-bar colors (which share
# #8b5cf6/#a78bfa with UCIe) — PE labels still convey identity.
# Applied AFTER the academic color map so the new slate values are
# not picked up by the rules above.
UCIE_DESATURATE: list[tuple[str, str]] = [
# UCIe block stroke/text (violet-500) -> slate-600
('"#8b5cf6"', '"#475569"'),
# UCIe cell 1 / PE3 port bar (violet-400) -> slate-400
('"#a78bfa"', '"#94a3b8"'),
# UCIe cell 0 (indigo-400) -> slate-300
('"#818cf8"', '"#cbd5e1"'),
# UCIe cell 2 (purple-400) -> slate-500
('"#c084fc"', '"#64748b"'),
# UCIe cell 3 (fuchsia-400) -> gray-700
('"#e879f9"', '"#374151"'),
]
# ── 4. Font-size bumps. The CUBE figure is rendered at half-text-width
# (~250pt) inside the side-by-side subfigure in 02-platform.tex, so
# native fonts get crushed ~3x by \linewidth scaling. We push the
# bumps to the legibility limit of the layout (router-label text
# stays inside a slightly enlarged circle; legend items may touch).
FONT_MAP: dict[str, str] = {
"5": "10",
"6": "12",
"7": "14",
"8": "13", # legend rect text — capped by upstream layout spacing
# (advance = 7*len(label)+24 was sized for ~font 8);
# font 13 keeps each item's text inside its slot.
"9": "14",
"10": "15",
"11": "16",
"14": "18", # title — kept moderate so it does not overflow canvas
}
# ── 5. Router circle radius bump (only circles use r="8"). Enlarged so
# the bumped router labels stay inside the circle.
RADIUS_MAP: list[tuple[str, str]] = [
(' r="8"', ' r="17"'),
]
# ── 6. Tighten whitespace: move legend just below dashed box and crop
# the unused canvas margins so LaTeX's \linewidth scaling does not
# shrink the figure text any more than necessary.
LAYOUT_FIXUP: list[tuple[str, str]] = [
# Move legend rects up (dashed box bottom is at y=760)
(' y="865"', ' y="775"'),
# Move legend text baselines up to match (offset = 9 px)
(' y="874"', ' y="784"'),
# Tight crop: 5 px margin around dashed box + cut top/bottom whitespace
('<svg xmlns="http://www.w3.org/2000/svg" width="970" height="900" '
'viewBox="0 0 970 900">',
'<svg xmlns="http://www.w3.org/2000/svg" width="860" height="798" '
'viewBox="55 2 860 798">'),
]
def _bump_font(m: re.Match) -> str:
return f'font-size="{FONT_MAP.get(m.group(1), m.group(1))}"'
def main() -> None:
if not SRC_SVG.exists():
raise SystemExit(f"source SVG missing: {SRC_SVG}")
rsvg = shutil.which("rsvg-convert")
if rsvg is None:
raise SystemExit("rsvg-convert not found (brew install librsvg)")
svg = SRC_SVG.read_text(encoding="utf-8")
for old, new in (LEGEND_FIXUP + COLOR_MAP + UCIE_DESATURATE
+ RADIUS_MAP + LAYOUT_FIXUP):
if old not in svg:
print(f"warn: pattern not present in source SVG: {old}")
svg = svg.replace(old, new)
svg = re.sub(r'font-size="(\d+)"', _bump_font, svg)
OUT_DIR.mkdir(parents=True, exist_ok=True)
OUT_SVG.write_text(svg, encoding="utf-8")
subprocess.run(
[rsvg, "-f", "pdf", "-o", str(OUT_PDF), str(OUT_SVG)],
check=True,
)
print(f"wrote {OUT_SVG.relative_to(REPO)}")
print(f"wrote {OUT_PDF.relative_to(REPO)}")
if __name__ == "__main__":
main()
-101
View File
@@ -1,101 +0,0 @@
"""Report harness: GQA end-to-end latency + op/engine breakdown.
Isolated under ``scripts/paper/`` for the 1H codesign report only — it is
NOT a registered bench and does not touch other people's benches. It
reuses the existing GQA headline panels (the real GQA kernels wired in
``milestone_gqa_headline``) but, unlike that milestone (which records only
op-counts), it also harvests per-panel end-to-end latency and per-engine
occupancy from ``result.engine.op_log``.
Latency definition (same window convention as ``milestone_1h_gemm``):
end_to_end_ns = max(r.t_end) - min(r.t_start) over all op_log records.
Output: docs/report/1H-codesign-paper/figures/gqa_latency.json
Run:
python scripts/paper/paper_gqa_latency.py
"""
from __future__ import annotations
import json
from pathlib import Path
from kernbench.benches.milestone_gqa_headline import (
_PANEL_DISPATCH,
_PANELS,
_make_bench_fn,
_summarize_op_log,
)
_REPORT_DIR = Path(__file__).resolve().parents[2] / "docs" / "report" / "1H-codesign-paper"
_FIG_DIR = _REPORT_DIR / "figures"
_OUT_JSON = _FIG_DIR / "gqa_latency.json"
# PE engine component suffixes whose occupancy we break out.
_ENGINES = ("pe_gemm", "pe_math", "pe_dma", "pe_fetch_store", "pe_ipcq", "pe_cpu")
def _occupancy_ns(op_log, suffix: str) -> float:
return sum(
r.t_end - r.t_start
for r in op_log
if r.component_id.endswith("." + suffix)
)
def _end_to_end_ns(op_log) -> float:
if not op_log:
return 0.0
return max(r.t_end for r in op_log) - min(r.t_start for r in op_log)
def _run_panel(panel: str, topology: str) -> dict:
from kernbench.runtime_api.bench_runner import run_bench
from kernbench.runtime_api.types import resolve_device
from kernbench.sim_engine.engine import GraphEngine
from kernbench.topology.builder import resolve_topology
topo = resolve_topology(topology)
result = run_bench(
topology=topo,
bench_fn=_make_bench_fn(panel),
device=resolve_device(None),
engine_factory=lambda t, d: GraphEngine(
getattr(t, "topology_obj", t), enable_data=True,
),
)
if not result.completion.ok:
raise RuntimeError(f"panel {panel!r} failed: {result.completion}")
op_log = result.engine.op_log
kind, params = _PANEL_DISPATCH[panel]
return {
"panel": panel,
"kind": kind,
**params,
"latency_ns": _end_to_end_ns(op_log),
"op_log_summary": _summarize_op_log(op_log),
"engine_occupancy_ns": {
eng: _occupancy_ns(op_log, eng) for eng in _ENGINES
},
}
def main() -> None:
topology = "topology.yaml"
rows = [_run_panel(panel, topology) for panel in _PANELS]
_FIG_DIR.mkdir(parents=True, exist_ok=True)
out = {"version": 1, "panels": list(_PANELS), "rows": rows}
_OUT_JSON.write_text(json.dumps(out, indent=2))
print(f"wrote {_OUT_JSON}")
for r in rows:
s = r["op_log_summary"]
print(
f" {r['panel']:24s} latency={r['latency_ns']:10.1f} ns "
f"gemm={s['gemm_count']:3d} ipcq={s['ipcq_copy_count']:3d} "
f"dma_rd={s['dma_read_count']:3d} dma_wr={s['dma_write_count']:2d}"
)
if __name__ == "__main__":
main()
@@ -0,0 +1,535 @@
#!/usr/bin/env python3
"""Conceptual latency-model diagram for the KernBench paper (v5).
Generic naming (Requester Node A/B, Router, Destination Node), with
Router internals visible (two input ports -> switch -> output queue ->
output port) so the queuing delay can be located *at the out port*
rather than at the box edge. The Destination Node shows queue -> drain
slot -> processing logic.
Highlights:
* Same-size Routers, each with explicit switching logic + output queue.
* Edge labels removed -- the wires speak for themselves.
* Annotation lines are strictly vertical; arrow heads use a larger
mutation_scale so no thin line protrudes past the tip; `shrinkA`
pulls the tail away from the text so they no longer overlap.
* "flit-level interleaving on wires" (not "shared FIFO").
* Destination: queue -> drain -> processing logic (no "served").
Output:
docs/report/1H-codesign-paper/figures/latency_model.png
Per /paper isolation: this is a report-only harness under scripts/paper/.
"""
import math
from pathlib import Path
import matplotlib.patches as patches
import matplotlib.pyplot as plt
OUT = Path(
"/Users/ywkang/kernbench/docs/report/1H-codesign-paper/figures/latency_model.png"
)
OUT.parent.mkdir(parents=True, exist_ok=True)
# --- Colours --------------------------------------------------------------
C_A = "#E07B5E"
C_B = "#5E9BD1"
C_NODE = "#FFFFFF"
C_BORD = "#222222"
C_WIRE = "#222222"
C_ANN = "#333333"
C_DIM = "#777777"
C_SWITCH = "#FAFAFA"
C_OQUEUE = "#EFEFEF"
C_PROC = "#F4ECDC"
C_QUEUE = "#3CB371" # medium-sea-green: distinctive vs A/B flit colours
# --- Canvas ---------------------------------------------------------------
fig = plt.figure(figsize=(17.0, 5.0))
ax = fig.add_subplot(111)
ax.set_xlim(0, 33)
ax.set_ylim(2.2, 11.2)
ax.axis("off")
# --- Top header: end-to-end latency formula -----------------------------
ax.text(
16.5, 10.6,
r"End-to-end latency = $\Sigma$ per-node overhead + "
r"$\Sigma$ per-edge transmission + drain + "
r"queuing delay",
ha="center", fontsize=12, color=C_ANN, weight="bold",
)
ax.plot([1.5, 31.5], [10.05, 10.05], color=C_DIM, lw=0.6)
# --- Helpers --------------------------------------------------------------
def box(cx, cy, w, h, label, fs=11, fweight="normal"):
ax.add_patch(patches.FancyBboxPatch(
(cx - w / 2, cy - h / 2), w, h,
boxstyle="round,pad=0.05",
facecolor=C_NODE, edgecolor=C_BORD, linewidth=1.6,
))
if label:
ax.text(cx, cy, label, ha="center", va="center",
fontsize=fs, weight=fweight)
def draw_flit_aligned(cx, cy, angle_rad, w, h, color, label):
"""Draw a flit polygon rotated to align with the wire angle."""
cos_a, sin_a = math.cos(angle_rad), math.sin(angle_rad)
corners = []
for dx, dy in [(-w / 2, -h / 2), (w / 2, -h / 2),
(w / 2, h / 2), (-w / 2, h / 2)]:
rx = dx * cos_a - dy * sin_a
ry = dx * sin_a + dy * cos_a
corners.append((cx + rx, cy + ry))
ax.add_patch(patches.Polygon(
corners, facecolor=color, edgecolor="black", linewidth=0.5,
))
ax.text(cx, cy, label, ha="center", va="center",
fontsize=9, color="white", weight="bold")
def draw_wire_with_flits(x0, y0, x1, y1, n_flits, label_char,
color=None, flit_w=0.66, flit_h=0.95, gap=0.10):
"""Wire (line) + endpoint arrowhead + flits centred on the line.
Wires are drawn in the neutral C_WIRE colour: the wire itself is
the place where the *transmission* delay accumulates (flit_size /
BW), not where flits queue. Queuing happens before the wire, at
the FIFO at the egress side -- coloured separately.
"""
head_back = 0.30
dx, dy = x1 - x0, y1 - y0
wire_len = math.hypot(dx, dy)
ux, uy = dx / wire_len, dy / wire_len
x_line_end = x1 - ux * head_back
y_line_end = y1 - uy * head_back
ax.plot([x0, x_line_end], [y0, y_line_end],
color=C_WIRE, lw=1.6, zorder=1)
head_len, head_half = 0.30, 0.16
bx = x1 - ux * head_len
by = y1 - uy * head_len
px, py = -uy, ux
tri = [
(x1, y1),
(bx + px * head_half, by + py * head_half),
(bx - px * head_half, by - py * head_half),
]
ax.add_patch(patches.Polygon(tri, facecolor=C_WIRE,
edgecolor=C_WIRE, linewidth=0.0))
# Flit train
angle_rad = math.atan2(dy, dx)
train_len = n_flits * flit_w + (n_flits - 1) * gap
centre_p = 0.5
start_p = centre_p - (train_len / 2) / wire_len
step_p = (flit_w + gap) / wire_len
if isinstance(label_char, str):
labels = [label_char] * n_flits
cols = [color] * n_flits
else:
labels = [c[0] for c in label_char]
cols = [c[1] for c in label_char]
for i in range(n_flits):
p = start_p + (i + 0.5) * step_p
cx = x0 + p * dx
cy = y0 + p * dy
draw_flit_aligned(cx, cy, angle_rad,
flit_w, flit_h, cols[i], labels[i])
def draw_router(cx, cy, w, h, two_inputs=True):
"""Same-size Router with explicit internal switching logic and an
output queue. Returns the wire-attachment (x,y) for each input
port and the single output port:
((in1_x, in1_y), (in2_x, in2_y) or None, (out_x, out_y))
"""
# Outer box
box(cx, cy, w, h, "")
ax.text(cx, cy + h / 2 - 0.32, "Router",
ha="center", fontsize=10, weight="bold")
# Input ports (small circles on the left edge)
in_x = cx - w / 2
in_x_internal = in_x + 0.25
if two_inputs:
in_y_top = cy + 0.55
in_y_bot = cy - 0.55
for iy in (in_y_top, in_y_bot):
ax.add_patch(patches.Circle(
(in_x_internal, iy), 0.12,
facecolor="white", edgecolor=C_BORD, linewidth=0.9,
))
else:
in_y_top = None
in_y_bot = cy
ax.add_patch(patches.Circle(
(in_x_internal, in_y_bot), 0.12,
facecolor="white", edgecolor=C_BORD, linewidth=0.9,
))
# Switch (small box, centre-left-ish). sw_cy = cy so that the
# output queue and (single-input) input port are at the same y as
# the router centre -- this keeps all router-to-router edges
# strictly horizontal.
sw_w, sw_h = 0.85, 1.10
sw_cx = cx - 0.55
sw_cy = cy
# The switch is the router's processing logic -- colour it the
# same as the Destination Node's processing-logic block so the
# two read as the same "processing" concept.
ax.add_patch(patches.Rectangle(
(sw_cx - sw_w / 2, sw_cy - sw_h / 2), sw_w, sw_h,
facecolor=C_PROC, edgecolor=C_BORD, linewidth=0.8,
))
ax.text(sw_cx, sw_cy, "switch", ha="center", va="center",
fontsize=7.5, style="italic")
# Short feeder lines (no arrowhead) from input ports to the
# switch. We deliberately drop arrowheads here: the head + port
# circle were too small at this scale and read as an overlap.
sw_left_x = sw_cx - sw_w / 2
if two_inputs:
for iy in (in_y_top, in_y_bot):
ax.plot(
[in_x_internal + 0.12, sw_left_x],
[iy, sw_cy + 0.25 * ((iy - cy) / 0.55)],
color=C_DIM, lw=0.7, zorder=1,
)
else:
ax.plot(
[in_x_internal + 0.12, sw_left_x],
[in_y_bot, sw_cy],
color=C_DIM, lw=0.7, zorder=1,
)
# Output queue (small queue holding a couple of flits). This *is*
# a real queueing location, so its fill takes the C_QUEUE family.
oq_w, oq_h = 0.95, 0.55
oq_cx = cx + 0.55
oq_cy = sw_cy
ax.add_patch(patches.Rectangle(
(oq_cx - oq_w / 2, oq_cy - oq_h / 2), oq_w, oq_h,
facecolor="#D9F0E1", edgecolor=C_QUEUE, linewidth=0.9,
))
ax.text(oq_cx, oq_cy + oq_h / 2 + 0.18, "FIFO",
ha="center", fontsize=7, color=C_DIM, style="italic")
# Two small flits inside (A and B) hinting at the in-flight contents
mini_w, mini_h = 0.22, 0.34
for i, (col, lab) in enumerate([(C_A, "A"), (C_B, "B")]):
mx = oq_cx - 0.30 + i * (mini_w + 0.06)
ax.add_patch(patches.Rectangle(
(mx, oq_cy - mini_h / 2), mini_w, mini_h,
facecolor=col, edgecolor="black", linewidth=0.3,
))
ax.text(mx + mini_w / 2, oq_cy, lab,
ha="center", va="center",
fontsize=5.5, color="white", weight="bold")
# Short feeder line (no arrowhead) from switch into out queue
ax.plot(
[sw_cx + sw_w / 2, oq_cx - oq_w / 2],
[sw_cy, oq_cy],
color=C_DIM, lw=0.7, zorder=1,
)
# Short feeder line from out queue to out port
out_x_internal = cx + w / 2 - 0.25
ax.plot(
[oq_cx + oq_w / 2, out_x_internal - 0.12],
[oq_cy, oq_cy],
color=C_DIM, lw=0.7, zorder=1,
)
# Output port circle on the right edge (a port marker, not a
# queue -- the queue is on the wire that follows, not the port).
ax.add_patch(patches.Circle(
(out_x_internal, oq_cy), 0.12,
facecolor="white", edgecolor=C_BORD, linewidth=0.9,
))
return (
(in_x_internal, in_y_top) if two_inputs else None,
(in_x_internal, in_y_bot),
(out_x_internal, oq_cy),
oq_cx, # also return the out-queue centre so callouts can target it
oq_cy,
)
# --- Box layout ---------------------------------------------------------
# Requester centres set to match Router 1's input-port y so that
# Edge 1A and Edge 1B are strictly horizontal. Box heights are kept
# small enough that a visible gap separates the two Requester boxes.
R1_TMP_CY = 7.0
ReqA = (2.8, R1_TMP_CY + 0.55) # = 7.55, matches in_y_top of R1
ReqB = (2.8, R1_TMP_CY - 0.55) # = 6.45, matches in_y_bot of R1
box(*ReqA, 3.0, 0.85, "Requester\nNode A", fs=10)
box(*ReqB, 3.0, 0.85, "Requester\nNode B", fs=10)
R_W, R_H = 3.0, 2.8
R1 = (10.5, 7.0)
R2 = (20.0, 7.0)
r1_in_top, r1_in_bot, r1_out, r1_oq_cx, r1_oq_cy = draw_router(
*R1, R_W, R_H, two_inputs=True,
)
_, r2_in_bot, r2_out, r2_oq_cx, r2_oq_cy = draw_router(
*R2, R_W, R_H, two_inputs=False,
)
# Destination Node (same height as Router; wide enough so queue +
# drain + processing-logic all fit on a single horizontal row).
Dst = (28.4, 7.0)
Dst_W, Dst_H = 6.0, R_H # match the Router height
box(*Dst, Dst_W, Dst_H, "")
ax.text(Dst[0], Dst[1] + Dst_H / 2 - 0.25, "Destination Node",
ha="center", fontsize=10, weight="bold")
# --- Edges: requester -> router 1 (Edge 1A & 1B, with flits) ------------
draw_wire_with_flits(
ReqA[0] + 1.6, ReqA[1],
r1_in_top[0] - 0.02, r1_in_top[1],
n_flits=4, color=C_A, label_char="A",
)
draw_wire_with_flits(
ReqB[0] + 1.6, ReqB[1],
r1_in_bot[0] - 0.02, r1_in_bot[1],
n_flits=4, color=C_B, label_char="B",
)
# Edge 2: router1 out -> router2 in (horizontal, interleaved)
labels_e2 = [("A", C_A) if i % 2 == 0 else ("B", C_B) for i in range(8)]
E2_y = r1_out[1]
draw_wire_with_flits(
r1_out[0] + 0.02, r1_out[1],
r2_in_bot[0] - 0.02, r2_in_bot[1],
n_flits=8, label_char=labels_e2,
)
# Edge 3: router2 out -> destination (horizontal, interleaved)
labels_e3 = [("A", C_A) if i % 2 == 0 else ("B", C_B) for i in range(4)]
draw_wire_with_flits(
r2_out[0] + 0.02, r2_out[1],
Dst[0] - Dst_W / 2, r2_out[1],
n_flits=4, label_char=labels_e3,
)
# --- Destination internals: queue -> drain -> processing logic ----------
# Light-green halo behind the queue area marks it as a queueing point.
C_QUEUE_BG = "#D9F0E1"
dy_main = Dst[1] - 0.10
Dst_left = Dst[0] - Dst_W / 2
# Queue (4 flits, FIFO; rightmost is the next to drain, so the
# order is set so the serve sequence after the in-flight A alternates
# A (in drain) -> B -> A -> B -> A -- giving a clean BABA queue.
qW, qH, qGap = 0.42, 0.62, 0.07
q_labels = ["A", "B", "A", "B"]
q_cols = [C_A, C_B, C_A, C_B]
q_total = len(q_labels) * qW + (len(q_labels) - 1) * qGap
q_x_start = Dst_left + 0.35
# Green halo behind the queue boxes (the queue itself is a queueing
# location -- mark it with the C_QUEUE colour family)
ax.add_patch(patches.FancyBboxPatch(
(q_x_start - 0.10, dy_main - qH / 2 - 0.08),
q_total + 0.20, qH + 0.16,
boxstyle="round,pad=0.02",
facecolor=C_QUEUE_BG, edgecolor=C_QUEUE,
linewidth=0.9, zorder=1.5,
))
for i, (lab, col) in enumerate(zip(q_labels, q_cols)):
qx = q_x_start + i * (qW + qGap)
ax.add_patch(patches.Rectangle(
(qx, dy_main - qH / 2), qW, qH,
facecolor=col, edgecolor="black", linewidth=0.4,
zorder=2,
))
ax.text(qx + qW / 2, dy_main, lab,
ha="center", va="center",
fontsize=8, color="white", weight="bold", zorder=3)
# Common y for the "queue" / "drain" labels. Matches the FIFO label
# spacing inside the Router (0.18 above the box top) and uses the same
# font size for visual consistency.
DST_LABEL_Y = dy_main + qH / 2 + 0.18
ax.text(q_x_start + q_total / 2, DST_LABEL_Y,
"queue",
ha="center", fontsize=7, style="italic", color=C_DIM)
# Drain slot (height matched to queue boxes; font matched to FIFO/queue
# labels for visual consistency)
dr_W, dr_H = 0.85, qH
dr_x = q_x_start + q_total + 0.35
dr_cx = dr_x + dr_W / 2
dr_cy = dy_main
ax.add_patch(patches.FancyBboxPatch(
(dr_x, dr_cy - dr_H / 2), dr_W, dr_H,
boxstyle="round,pad=0.03",
facecolor=C_A, edgecolor="black", linewidth=1.0,
))
ax.text(dr_cx, dr_cy, "A", ha="center", va="center",
fontsize=9, color="white", weight="bold")
ax.text(dr_cx, DST_LABEL_Y,
"drain",
ha="center", fontsize=7, style="italic", color=C_DIM)
# Drain-time double-arrow underneath the drain slot. Move the "drain
# time" text a touch further down so the descender does not collide
# with the arrowhead.
dt_y = dr_cy - dr_H / 2 - 0.28
ax.annotate(
"", xy=(dr_x + dr_W, dt_y), xytext=(dr_x, dt_y),
arrowprops=dict(arrowstyle="<->", lw=0.9, color=C_ANN),
)
ax.text(dr_cx, dt_y - 0.42, "drain time",
ha="center", fontsize=8.5, color=C_ANN, style="italic")
# Processing-logic block (after drain) — wider so the two-line text
# does not collide with the box edges
pl_W, pl_H = 1.75, 1.00
pl_x = dr_x + dr_W + 0.40
pl_cx = pl_x + pl_W / 2
pl_cy = dr_cy
ax.add_patch(patches.FancyBboxPatch(
(pl_x, pl_cy - pl_H / 2), pl_W, pl_H,
boxstyle="round,pad=0.03",
facecolor=C_PROC, edgecolor=C_BORD, linewidth=1.0,
))
ax.text(pl_cx, pl_cy, "processing\nlogic",
ha="center", va="center", fontsize=9)
# Small gray arrows along the queue -> drain -> processing chain
ax.annotate(
"", xy=(dr_x - 0.04, dr_cy),
xytext=(q_x_start + q_total + 0.13, dr_cy),
arrowprops=dict(arrowstyle="-|>", lw=0.7,
color=C_DIM, mutation_scale=7),
)
ax.annotate(
"", xy=(pl_x - 0.04, dr_cy),
xytext=(dr_x + dr_W + 0.04, dr_cy),
arrowprops=dict(arrowstyle="-|>", lw=0.7,
color=C_DIM, mutation_scale=7),
)
# --- Annotations (strictly vertical, no diagonals, no overlap) ---------
def vcallout(text, xy, x_text_top_y, fs=10, clearance=0.55,
marker_color=None):
"""Vertical dotted callout. Optional `marker_color` paints a small
coloured circle just left of the text -- used to tie the label to
a colour code in the diagram.
"""
text_y = x_text_top_y
if text_y < xy[1]:
line_top_y = text_y + clearance
else:
line_top_y = text_y - clearance
ax.plot([xy[0], xy[0]], [xy[1], line_top_y],
color=C_ANN, lw=0.9, linestyle=":", zorder=2)
if marker_color is not None:
# Text-width estimate at fs=10 (~0.18 data units per char) so
# the marker is placed clearly to the left of the text.
text_w = len(text) * 0.18
marker_x = xy[0] - text_w / 2 - 0.30
ax.add_patch(patches.Circle(
(marker_x, text_y), 0.14,
facecolor=marker_color, edgecolor=C_BORD, linewidth=0.6,
zorder=3,
))
ax.text(xy[0], text_y, text,
ha="center", va="center",
fontsize=fs, color=C_ANN, zorder=3)
# Transmission delay -> flit on Edge 2 (text BELOW the wire).
# Extra clearance ~ 2 x text height so the line stops well clear of
# the label.
e2_total = 8 * 0.66 + 7 * 0.10
e2_centre = (r1_out[0] + r2_in_bot[0]) / 2
e2_start = e2_centre - e2_total / 2
trans_idx = 5
trans_cx = e2_start + (trans_idx + 0.5) * (0.66 + 0.10)
vcallout(
"transmission delay = flit_size / BW",
xy=(trans_cx, E2_y - 0.55),
x_text_top_y=E2_y - 2.6,
clearance=0.45,
)
# Flit-level interleaving -> ABOVE the wire, anchored on a flit near
# the centre/right of the wire. Text y matches the queuing-delay
# callout so the two labels sit on the same horizontal row, separated
# horizontally so they don't collide.
int_idx = 4
int_cx = e2_start + (int_idx + 0.5) * (0.66 + 0.10)
vcallout(
"flit-level interleaving on wires",
xy=(int_cx, E2_y + 0.55),
x_text_top_y=R1[1] + R_H / 2 + 0.85,
)
# Queuing delay -> at Router 1 out-queue. Text positioned just above
# the Router-1 box.
vcallout(
"queuing delay",
xy=(r1_oq_cx, r1_oq_cy + 0.30),
x_text_top_y=R1[1] + R_H / 2 + 0.85,
marker_color=C_QUEUE,
)
# Drain -> below the drain slot (text is below).
# Extra clearance ~ 2.5 x text height so the line stops further from
# the label.
vcallout(
"drain = per-flit service occupancy",
xy=(dr_cx, dt_y - 0.66), # just *inside* the Destination Node
# bottom edge -- the dotted line then
# penetrates the box slightly rather
# than hanging below it
x_text_top_y=E2_y - 2.6,
clearance=0.60,
)
# Per-node overhead -> points at Router 1's switch block (where the
# component's fixed processing cost lives). Text on the same row as
# transmission delay and drain so the three "below the wire" callouts
# sit on one horizontal baseline.
R1_SW_CX = R1[0] - 0.55 # sw_cx for Router 1
R1_SW_BOTTOM_Y = R1[1] - 0.55 # bottom of sw box
vcallout(
"per-node overhead",
xy=(R1_SW_CX, R1_SW_BOTTOM_Y),
x_text_top_y=E2_y - 2.6,
clearance=0.60,
marker_color=C_PROC,
)
# --- Legend (close to the diagram) --------------------------------------
LX, LY = 1.0, 2.6
ax.add_patch(patches.Rectangle((LX, LY), 0.7, 0.45,
facecolor=C_A, edgecolor="black",
linewidth=0.4))
ax.text(LX + 0.95, LY + 0.22, "Transaction A flit",
va="center", fontsize=10)
ax.add_patch(patches.Rectangle((LX + 4.8, LY), 0.7, 0.45,
facecolor=C_B, edgecolor="black",
linewidth=0.4))
ax.text(LX + 5.75, LY + 0.22, "Transaction B flit",
va="center", fontsize=10)
# --- Save ----------------------------------------------------------------
fig.savefig(OUT, dpi=140, bbox_inches="tight", facecolor="white")
print(f"Wrote {OUT}")

Some files were not shown because too many files have changed in this diff Show More