Script: prism_chip_model.py
PRISM-1
prism_chip_model.py
This is the original source text. Reading it does not run or verify the program.
1 from __future__ import annotations
2
3 import argparse
4 import json
5 import math
6 from dataclasses import asdict, dataclass
7 from pathlib import Path
8 from typing import Iterable, Sequence
9
10 import matplotlib
11 matplotlib.use("Agg")
12 import matplotlib.pyplot as plt
13 from matplotlib.patches import FancyArrowPatch, FancyBboxPatch, Rectangle
14 import numpy as np
15
16
17 @dataclass(frozen=True)
18 class PrismConfig:
19 name: str = "Komorebi PRISM-1"
20 clock_ghz: float = 1.5
21 compute_chiplets: int = 4
22 packet_streaming_multiprocessors_per_chiplet: int = 16
23 execution_groups_per_psm: int = 4
24 mac_rows: int = 16
25 mac_cols: int = 16
26 local_sram_kib_per_execution_group: int = 512
27 shared_sram_mib_per_chiplet: int = 16
28 descriptor_cache_kib_per_psm: int = 128
29 transform_engines_per_chiplet: int = 8
30 solver_engines_per_chiplet: int = 4
31 exact_arithmetic_clusters_per_chiplet: int = 1
32 hbm4_stacks: int = 4
33 hbm4_capacity_gib_per_stack: int = 36
34 hbm4_bandwidth_tbps_per_stack: float = 2.8
35 package_network_tbps: float = 16.0
36 host_bidirectional_gbps: float = 512.0
37 tdp_w: int = 500
38 ucie_gtps: int = 64
39
40 @property
41 def psms(self) -> int:
42 return self.compute_chiplets * self.packet_streaming_multiprocessors_per_chiplet
43
44 @property
45 def execution_groups_per_chiplet(self) -> int:
46 return self.packet_streaming_multiprocessors_per_chiplet * self.execution_groups_per_psm
47
48 @property
49 def execution_groups(self) -> int:
50 return self.psms * self.execution_groups_per_psm
51
52 @property
53 def tiles(self) -> int:
54 return self.execution_groups
55
56 @property
57 def total_hbm_gib(self) -> int:
58 return self.hbm4_stacks * self.hbm4_capacity_gib_per_stack
59
60 @property
61 def total_hbm_tbps(self) -> float:
62 return self.hbm4_stacks * self.hbm4_bandwidth_tbps_per_stack
63
64 @property
65 def total_sram_mib(self) -> float:
66 local = self.execution_groups * self.local_sram_kib_per_execution_group / 1024
67 shared = self.compute_chiplets * self.shared_sram_mib_per_chiplet
68 return local + shared
69
70 @property
71 def fp16_tflops(self) -> float:
72 macs = self.execution_groups * self.mac_rows * self.mac_cols
73 return macs * 2 * self.clock_ghz * 1e9 / 1e12
74
75 @property
76 def fp32_tflops(self) -> float:
77 return self.fp16_tflops / 2
78
79 @property
80 def fp64_tflops(self) -> float:
81 return self.fp16_tflops / 8
82
83
84 @dataclass(frozen=True)
85 class PacketDescriptor:
86 packet_id: int
87 parent_id: int
88 atlas_id: int
89 selector_kind: str
90 shape: tuple[int, ...]
91 base_address: int
92 bytes_per_element: int
93 precision: str
94 error_budget: float
95
96
97 @dataclass(frozen=True)
98 class TransferDescriptor:
99 edge_id: int
100 src_packet: int
101 dst_packet: int
102 kernel_kind: str
103 rows: int
104 cols: int
105 rank: int
106 precision: str
107 sensitivity: float
108 dependency_token: int
109
110
111 REPRESENTATION_FORMS = (
112 "explicit_dense",
113 "block_diagonal",
114 "block_sparse",
115 "low_rank",
116 "bdlr",
117 "kronecker",
118 "sparse_factor_chain",
119 "transform_domain",
120 "packet_graph",
121 )
122
123 OUTPUT_CONTRACTS = (
124 "apply",
125 "compressed_operator",
126 "factor_graph",
127 "transform_plan",
128 "explicit_array",
129 )
130
131
132 @dataclass(frozen=True)
133 class PlanChoice:
134 name: str
135 output_form: str
136 local_cost: float
137 local_error: float = 0.0
138 children: tuple[str, ...] = ()
139
140
141 @dataclass(frozen=True)
142 class PlanResult:
143 node: str
144 choice: str
145 output_form: str
146 objective: float
147 local_cost: float
148 local_error: float
149 children: tuple["PlanResult", ...]
150
151
152 @dataclass(frozen=True)
153 class Estimate:
154 workload: str
155 flops: float
156 bytes_moved: float
157 arithmetic_intensity: float
158 compute_time_us: float
159 memory_time_us: float
160 schedule_time_us: float
161 total_time_us: float
162 energy_mj: float
163 notes: str
164
165
166 def packet_slices(sizes: Sequence[int]) -> tuple[slice, ...]:
167 out: list[slice] = []
168 start = 0
169 for size in sizes:
170 if size <= 0:
171 raise ValueError("packet sizes must be positive")
172 out.append(slice(start, start + int(size)))
173 start += int(size)
174 return tuple(out)
175
176
177 def residue_project(x: np.ndarray, order: int, phase: int) -> np.ndarray:
178 arr = np.asarray(x)
179 out = np.zeros_like(arr)
180 mask = np.arange(arr.size) % order == phase % order
181 out[mask] = arr[mask]
182 return out
183
184
185 def polyphase_analyze(x: np.ndarray, order: int) -> list[np.ndarray]:
186 arr = np.asarray(x)
187 return [arr[r::order].copy() for r in range(order)]
188
189
190 def polyphase_synthesize(channels: Sequence[np.ndarray], length: int | None = None) -> np.ndarray:
191 order = len(channels)
192 if order == 0:
193 raise ValueError("channels cannot be empty")
194 arrays = [np.asarray(c) for c in channels]
195 natural = max(order * len(c) + r for r, c in enumerate(arrays))
196 n = natural if length is None else length
197 out = np.zeros(n, dtype=np.result_type(*arrays))
198 for r, channel in enumerate(arrays):
199 positions = np.arange(r, n, order)
200 count = min(len(channel), len(positions))
201 out[positions[:count]] = channel[:count]
202 return out
203
204
205 def haar_analyze(x: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
206 arr = np.asarray(x, dtype=float)
207 if arr.size % 2:
208 raise ValueError("Haar analysis requires even length")
209 s = math.sqrt(2.0)
210 return (arr[0::2] + arr[1::2]) / s, (arr[0::2] - arr[1::2]) / s
211
212
213 def haar_synthesize(low: np.ndarray, high: np.ndarray) -> np.ndarray:
214 low = np.asarray(low, dtype=float)
215 high = np.asarray(high, dtype=float)
216 if low.shape != high.shape:
217 raise ValueError("Haar channels must match")
218 s = math.sqrt(2.0)
219 out = np.empty(low.size * 2)
220 out[0::2] = (low + high) / s
221 out[1::2] = (low - high) / s
222 return out
223
224
225 def block_diagonal_apply(blocks: Sequence[np.ndarray], x: np.ndarray) -> np.ndarray:
226 sizes = [b.shape[0] for b in blocks]
227 slices = packet_slices(sizes)
228 x = np.asarray(x)
229 out = np.empty_like(x, dtype=np.result_type(x, *blocks))
230 for slc, block in zip(slices, blocks, strict=True):
231 out[slc] = block @ x[slc]
232 return out
233
234
235 def bdlr_apply(blocks: Sequence[np.ndarray], u: np.ndarray, v: np.ndarray, x: np.ndarray) -> np.ndarray:
236 return block_diagonal_apply(blocks, x) + u @ (v.T.conj() @ x)
237
238
239 def materialize_bdlr(blocks: Sequence[np.ndarray], u: np.ndarray, v: np.ndarray) -> np.ndarray:
240 sizes = [b.shape[0] for b in blocks]
241 total = sum(sizes)
242 out = np.zeros((total, total), dtype=np.result_type(*blocks, u, v))
243 for slc, block in zip(packet_slices(sizes), blocks, strict=True):
244 out[slc, slc] = block
245 return out + u @ v.T.conj()
246
247
248 def output_contract_allows(output_form: str, contract: str) -> bool:
249 if output_form not in REPRESENTATION_FORMS:
250 raise ValueError(f"unknown output form {output_form}")
251 if contract not in OUTPUT_CONTRACTS:
252 raise ValueError(f"unknown output contract {contract}")
253 if contract == "apply":
254 return True
255 if contract == "explicit_array":
256 return output_form == "explicit_dense"
257 if contract == "transform_plan":
258 return output_form in {"transform_domain", "sparse_factor_chain", "packet_graph"}
259 if contract == "factor_graph":
260 return output_form in {
261 "block_diagonal",
262 "block_sparse",
263 "low_rank",
264 "bdlr",
265 "kronecker",
266 "sparse_factor_chain",
267 "transform_domain",
268 "packet_graph",
269 }
270 return True
271
272
273 def pmr_plan(
274 root: str,
275 options: dict[str, tuple[PlanChoice, ...]],
276 error_weight: float,
277 output_contract: str,
278 ) -> PlanResult:
279 """Bellman planner on a finite mixed-representation tree."""
280 if error_weight < 0:
281 raise ValueError("error weight must be nonnegative")
282 memo: dict[tuple[str, bool], PlanResult] = {}
283 active: set[str] = set()
284
285 def solve(node: str, is_root: bool) -> PlanResult:
286 key = (node, is_root)
287 if key in memo:
288 return memo[key]
289 if node in active:
290 raise ValueError("candidate plan graph must be acyclic")
291 if node not in options or not options[node]:
292 raise ValueError(f"no candidate representations for {node}")
293 active.add(node)
294 candidates: list[PlanResult] = []
295 for choice in options[node]:
296 if choice.output_form not in REPRESENTATION_FORMS:
297 raise ValueError(f"unknown representation {choice.output_form}")
298 if is_root and not output_contract_allows(choice.output_form, output_contract):
299 continue
300 child_results = tuple(solve(child, False) for child in choice.children)
301 objective = (
302 choice.local_cost
303 + error_weight * choice.local_error * choice.local_error
304 + sum(child.objective for child in child_results)
305 )
306 candidates.append(
307 PlanResult(
308 node=node,
309 choice=choice.name,
310 output_form=choice.output_form,
311 objective=float(objective),
312 local_cost=float(choice.local_cost),
313 local_error=float(choice.local_error),
314 children=child_results,
315 )
316 )
317 active.remove(node)
318 if not candidates:
319 raise ValueError(f"no representation satisfies output contract {output_contract}")
320 best = min(candidates, key=lambda result: (result.objective, result.choice))
321 memo[key] = best
322 return best
323
324 return solve(root, True)
325
326
327 def measured_cuda_baselines() -> dict[str, float | str]:
328 """Measured structured CUDA rows imported from the prior packet-GPU study."""
329 return {
330 "hardware": "NVIDIA GeForce RTX 3070 Ti Laptop GPU",
331 "equal_block_packet_ms": 0.046032,
332 "equal_block_speedup_vs_gpu_dense": 14.8919,
333 "equal_block_speedup_vs_cpu_dense": 235.7404,
334 "bdlr_standard_packet_ms": 0.097280,
335 "bdlr_graph_packet_ms": 0.079872,
336 "bdlr_speedup_vs_gpu_dense": 10.9355,
337 "bdlr_speedup_vs_cpu_dense": 153.6527,
338 "bdlr_graph_replay_gain": 0.097280 / 0.079872,
339 "compressed_product_ms": 0.1582,
340 "materialized_product_ms": 0.5073,
341 "compressed_output_gain": 0.5073 / 0.1582,
342 "measured_rows": 154.0,
343 }
344
345
346 def gain_decomposition() -> dict[str, float | str]:
347 cuda = measured_cuda_baselines()
348 return {
349 "representation_reduction_bdlr": 102.4,
350 "measured_packet_gpu_gain_vs_dense_gpu": float(cuda["bdlr_speedup_vs_gpu_dense"]),
351 "measured_cuda_graph_gain_same_representation": float(cuda["bdlr_graph_replay_gain"]),
352 "measured_compressed_output_gain": float(cuda["compressed_output_gain"]),
353 "note": "Factors are reported separately and are not multiplied unless timing scope and output contract match.",
354 }
355
356
357 def topological_order(nodes: Iterable[str], edges: Iterable[tuple[str, str]]) -> tuple[str, ...]:
358 nodes = tuple(sorted(set(nodes)))
359 edge_list = tuple(edges)
360 indegree = {n: 0 for n in nodes}
361 outgoing = {n: [] for n in nodes}
362 for a, b in edge_list:
363 indegree[b] += 1
364 outgoing[a].append(b)
365 ready = sorted(n for n, d in indegree.items() if d == 0)
366 order: list[str] = []
367 while ready:
368 n = ready.pop(0)
369 order.append(n)
370 for b in sorted(outgoing[n]):
371 indegree[b] -= 1
372 if indegree[b] == 0:
373 ready.append(b)
374 ready.sort()
375 if len(order) != len(nodes):
376 raise ValueError("graph is cyclic")
377 return tuple(order)
378
379
380 def substitute_task(
381 nodes: set[str],
382 edges: set[tuple[str, str]],
383 vertex: str,
384 local_nodes: set[str],
385 local_edges: set[tuple[str, str]],
386 entries: set[str],
387 exits: set[str],
388 ) -> tuple[set[str], set[tuple[str, str]], tuple[str, ...]]:
389 old_order = topological_order(nodes, edges)
390 local_order = topological_order(local_nodes, local_edges)
391 preds = {a for a, b in edges if b == vertex}
392 succs = {b for a, b in edges if a == vertex}
393 context_edges = {e for e in edges if vertex not in e}
394 new_edges = set(context_edges) | set(local_edges)
395 new_edges |= {(p, e) for p in preds for e in entries}
396 new_edges |= {(x, s) for x in exits for s in succs}
397 new_nodes = (set(nodes) - {vertex}) | set(local_nodes)
398 idx = old_order.index(vertex)
399 spliced = old_order[:idx] + local_order + old_order[idx + 1 :]
400 if tuple(n for n in spliced if n in nodes - {vertex}) != tuple(n for n in old_order if n != vertex):
401 raise RuntimeError("remote order changed")
402 if set(spliced) != new_nodes:
403 raise RuntimeError("spliced order has wrong nodes")
404 positions = {n: i for i, n in enumerate(spliced)}
405 if not all(positions[a] < positions[b] for a, b in new_edges):
406 raise RuntimeError("spliced order is not topological")
407 return new_nodes, new_edges, spliced
408
409
410 def peak_flops(config: PrismConfig, precision: str) -> float:
411 p = precision.lower()
412 if p in {"fp16", "bf16"}:
413 return config.fp16_tflops * 1e12
414 if p in {"fp32", "tf32"}:
415 return config.fp32_tflops * 1e12
416 if p == "fp64":
417 return config.fp64_tflops * 1e12
418 raise ValueError(f"unsupported precision {precision}")
419
420
421 def roofline_estimate(
422 config: PrismConfig,
423 workload: str,
424 flops: float,
425 bytes_moved: float,
426 precision: str = "fp32",
427 graph_regions: int = 1,
428 power_w: float | None = None,
429 notes: str = "",
430 use_sram: bool = False,
431 ) -> Estimate:
432 peak = peak_flops(config, precision)
433 bandwidth = (config.package_network_tbps if use_sram else config.total_hbm_tbps) * 1e12
434 efficiency_compute = 0.72 if use_sram else 0.62
435 efficiency_bw = 0.72 if use_sram else 0.74
436 compute_s = flops / (peak * efficiency_compute)
437 memory_s = bytes_moved / (bandwidth * efficiency_bw)
438 schedule_s = graph_regions * 0.32e-6
439 total_s = max(compute_s, memory_s) + schedule_s
440 watts = float(config.tdp_w if power_w is None else power_w)
441 return Estimate(
442 workload=workload,
443 flops=flops,
444 bytes_moved=bytes_moved,
445 arithmetic_intensity=flops / max(bytes_moved, 1.0),
446 compute_time_us=compute_s * 1e6,
447 memory_time_us=memory_s * 1e6,
448 schedule_time_us=schedule_s * 1e6,
449 total_time_us=total_s * 1e6,
450 energy_mj=watts * total_s * 1e3,
451 notes=notes,
452 )
453
454
455 def dense_apply_estimate(config: PrismConfig, n: int, q: int, itemsize: int = 4) -> Estimate:
456 flops = 2.0 * n * n * q
457 bytes_moved = itemsize * (n * n + 2 * n * q)
458 return roofline_estimate(
459 config,
460 "Dense apply baseline",
461 flops,
462 bytes_moved,
463 precision="fp32",
464 graph_regions=1,
465 notes="Dense matrix is materialized and read once",
466 )
467
468
469 def bdlr_estimate(config: PrismConfig, n: int, b: int, r: int, q: int, itemsize: int = 4) -> Estimate:
470 flops = 2.0 * q * n * (b + 2 * r)
471 bytes_moved = itemsize * (n * (b + 2 * r) + 3 * n * q + 2 * r * q)
472 return roofline_estimate(
473 config,
474 "Packet BDLR apply",
475 flops,
476 bytes_moved,
477 precision="fp32",
478 graph_regions=4,
479 notes="Factor-first execution with block diagonal and two low-rank contractions",
480 use_sram=True,
481 )
482
483
484 def block_sparse_estimate(config: PrismConfig, blocks: int, b: int, degree: int, q: int, itemsize: int = 4) -> Estimate:
485 n = blocks * b
486 edge_count = blocks * degree
487 flops = 2.0 * edge_count * b * b * q
488 bytes_moved = itemsize * (edge_count * b * b + 2 * n * q)
489 return roofline_estimate(
490 config,
491 "Regular block-sparse transfer",
492 flops,
493 bytes_moved,
494 precision="fp32",
495 graph_regions=max(1, degree),
496 notes="Edges are grouped by equal shape and scheduled as batches",
497 use_sram=True,
498 )
499
500
501 def multi_atlas_estimate(config: PrismConfig, pixels: int, atlases: int = 5, itemsize: int = 4) -> tuple[Estimate, Estimate]:
502 logn = math.log2(pixels)
503 transform_flops = 5.0 * pixels * logn + 8.0 * pixels
504 total_flops = atlases * transform_flops
505 baseline_passes = 3 * atlases + 2
506 prism_passes = 4
507 baseline_bytes = baseline_passes * pixels * itemsize
508 prism_bytes = prism_passes * pixels * itemsize
509 baseline = roofline_estimate(
510 config,
511 "Separate multi-atlas kernels",
512 total_flops,
513 baseline_bytes,
514 precision="fp32",
515 graph_regions=baseline_passes,
516 notes="Each atlas writes an intermediate array to HBM",
517 )
518 prism = roofline_estimate(
519 config,
520 "Fused multi-atlas pipeline",
521 total_flops,
522 prism_bytes,
523 precision="fp32",
524 graph_regions=3,
525 notes="Analysis, packet selection, feature reduction, and synthesis are fused",
526 use_sram=True,
527 )
528 return baseline, prism
529
530
531 def edge_recovery_estimate(config: PrismConfig, samples: int, max_jumps: int, itemsize: int = 8) -> tuple[Estimate, Estimate]:
532 fft_flops = 5.0 * samples * math.log2(samples)
533 hankel = max_jumps + 1
534 svd_flops = 20.0 * hankel**3
535 vandermonde_flops = 4.0 * max_jumps**3
536 root_flops = 250.0 * max_jumps**2
537 flops = fft_flops + svd_flops + vandermonde_flops + root_flops
538 baseline_bytes = itemsize * (10 * samples + 8 * hankel**2)
539 prism_bytes = itemsize * (3 * samples + 4 * hankel**2)
540 baseline = roofline_estimate(
541 config,
542 "General accelerator edge recovery",
543 flops,
544 baseline_bytes,
545 precision="fp64",
546 graph_regions=12,
547 power_w=420,
548 notes="FFT and small solvers run as separate kernels",
549 )
550 prism = roofline_estimate(
551 config,
552 "PRISM polyphase edge recovery",
553 flops,
554 prism_bytes,
555 precision="fp64",
556 graph_regions=4,
557 power_w=260,
558 notes="Streaming Hankel construction and resident small-solver cluster",
559 use_sram=True,
560 )
561 return baseline, prism
562
563
564 def bayes_estimate(config: PrismConfig, models: int, samples: int, features: int, outputs: int, itemsize: int = 4) -> tuple[Estimate, Estimate]:
565 flops_per_model = 2.0 * samples * features * features + (2.0 / 3.0) * features**3 + 2.0 * samples * features * outputs
566 flops = models * flops_per_model
567 baseline_bytes = itemsize * models * (samples * features + features * features + features * outputs)
568 prism_bytes = itemsize * (samples * features + models * (features * features + features * outputs))
569 baseline = roofline_estimate(
570 config,
571 "Independent Bayesian model kernels",
572 flops,
573 baseline_bytes,
574 precision="fp32",
575 graph_regions=models,
576 notes="Each model reloads the shared state matrix",
577 )
578 prism = roofline_estimate(
579 config,
580 "Shape-batched Bayesian posterior",
581 flops,
582 prism_bytes,
583 precision="fp32",
584 graph_regions=8,
585 notes="Shared design matrices remain resident and models are grouped by shape",
586 use_sram=True,
587 )
588 return baseline, prism
589
590
591 def _save(fig: plt.Figure, out_dir: Path, name: str) -> None:
592 out_dir.mkdir(parents=True, exist_ok=True)
593 fig.savefig(out_dir / f"{name}.pdf", bbox_inches="tight", pad_inches=0.08)
594 fig.savefig(out_dir / f"{name}.png", dpi=190, bbox_inches="tight", pad_inches=0.08)
595 plt.close(fig)
596
597
598 def _box(ax: plt.Axes, x: float, y: float, w: float, h: float, text: str, face: str = "white", fontsize: float = 10.5) -> None:
599 patch = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.015,rounding_size=0.02", facecolor=face, edgecolor="black", linewidth=1.2)
600 ax.add_patch(patch)
601 label = ax.text(x + w / 2, y + h / 2, text, ha="center", va="center", fontsize=fontsize, multialignment="center")
602 fig = ax.figure
603 for _ in range(12):
604 fig.canvas.draw()
605 renderer = fig.canvas.get_renderer()
606 bb = label.get_window_extent(renderer=renderer)
607 p0 = ax.transData.transform((x, y))
608 p1 = ax.transData.transform((x + w, y + h))
609 if bb.width <= abs(p1[0] - p0[0]) * 0.86 and bb.height <= abs(p1[1] - p0[1]) * 0.80:
610 break
611 label.set_fontsize(max(6.8, label.get_fontsize() * 0.90))
612
613
614 def _arrow(ax: plt.Axes, a: tuple[float, float], b: tuple[float, float], text: str | None = None, rad: float = 0.0) -> None:
615 arrow = FancyArrowPatch(a, b, arrowstyle="-|>", mutation_scale=12, linewidth=1.3, color="black", connectionstyle=f"arc3,rad={rad}")
616 ax.add_patch(arrow)
617 if text:
618 mx = (a[0] + b[0]) / 2
619 my = (a[1] + b[1]) / 2
620 ax.text(mx, my + 0.025, text, ha="center", va="bottom", fontsize=8.5, bbox={"facecolor": "white", "edgecolor": "none", "pad": 1.0})
621
622
623 def fig_package(config: PrismConfig, out: Path) -> None:
624 fig, ax = plt.subplots(figsize=(12.8, 7.0))
625 ax.set_xlim(0, 1)
626 ax.set_ylim(0, 1)
627 ax.axis("off")
628 ax.text(0.5, 0.96, "Komorebi PRISM-1 chiplet package", ha="center", va="top", fontsize=21, weight="bold")
629 ax.add_patch(Rectangle((0.06, 0.10), 0.88, 0.78, facecolor="#f2f2f2", edgecolor="black", linewidth=1.4))
630 positions = [(0.18, 0.58), (0.58, 0.58), (0.18, 0.22), (0.58, 0.22)]
631 for i, (x, y) in enumerate(positions):
632 _box(ax, x, y, 0.24, 0.20, f"Compute chiplet C{i}\n16 packet SMs\n64 execution groups\n8 transform engines\n4 solver engines", face="#dbeafe", fontsize=11)
633 _box(ax, 0.42, 0.40, 0.16, 0.20, "I/O and control die\nRISC-V control\nglobal scheduler\nPCIe and CXL", face="#fef3c7", fontsize=9.2)
634 hbm_pos = [(0.075, 0.69), (0.815, 0.69), (0.075, 0.20), (0.815, 0.20)]
635 for i, (x, y) in enumerate(hbm_pos):
636 _box(ax, x, y, 0.10, 0.15, f"HBM4 {i}\n36 GiB\n>2.8 TB/s", face="#dcfce7", fontsize=9.2)
637 package_links = [
638 ((0.42, 0.68), (0.435, 0.57)),
639 ((0.58, 0.68), (0.565, 0.57)),
640 ((0.42, 0.32), (0.435, 0.43)),
641 ((0.58, 0.32), (0.565, 0.43)),
642 ]
643 for start, end in package_links:
644 _arrow(ax, start, end)
645 ax.text(0.5, 0.12, f"{config.total_hbm_gib} GiB HBM4 {config.total_hbm_tbps:.1f} TB/s aggregate UCIe {config.ucie_gtps} GT/s package links", ha="center", fontsize=12, weight="bold")
646 _save(fig, out, "01_package_architecture")
647
648
649 def fig_tile(config: PrismConfig, out: Path) -> None:
650 fig, ax = plt.subplots(figsize=(12.8, 7.0))
651 ax.set_xlim(0, 1)
652 ax.set_ylim(0, 1)
653 ax.axis("off")
654 ax.text(0.5, 0.96, "One execution group and shared PSM paths", ha="center", va="top", fontsize=20, weight="bold")
655 boxes = [
656 (0.05, 0.64, 0.20, 0.18, "Descriptor front end\npacket and transfer tables", "#fef3c7"),
657 (0.30, 0.64, 0.20, 0.18, "Projector DMA\nmask, gather, scatter\npolyphase permutation", "#ede9fe"),
658 (0.55, 0.64, 0.20, 0.18, "1024-bit vector lane\nselectors and reductions", "#e0f2fe"),
659 (0.78, 0.64, 0.17, 0.18, "Certificate unit\nFP64 and exact accumulators", "#fee2e2"),
660 (0.18, 0.34, 0.28, 0.22, "one 16 x 16 matrix array\nblock transfer and low rank\nFP16 BF16 FP32 FP64\ncomplex pairs", "#dbeafe"),
661 (0.54, 0.34, 0.28, 0.22, "Nonlinear and memory unit\nquadratic interactions\ndelay and Volterra kernels", "#dcfce7"),
662 (0.18, 0.10, 0.28, 0.16, f"PSM scratchpad\n{config.execution_groups_per_psm * config.local_sram_kib_per_execution_group // 1024} MiB across four banks", "#f3f4f6"),
663 (0.54, 0.10, 0.28, 0.16, "Mesh router\nmulticast and reduction", "#f3f4f6"),
664 ]
665 for x, y, w, h, text, face in boxes:
666 _box(ax, x, y, w, h, text, face=face, fontsize=10.5)
667 _arrow(ax, (0.25, 0.73), (0.30, 0.73))
668 _arrow(ax, (0.50, 0.73), (0.55, 0.73))
669 _arrow(ax, (0.75, 0.73), (0.78, 0.73))
670 _arrow(ax, (0.40, 0.64), (0.32, 0.56))
671 _arrow(ax, (0.65, 0.64), (0.68, 0.56))
672 _arrow(ax, (0.32, 0.34), (0.32, 0.26))
673 _arrow(ax, (0.68, 0.34), (0.68, 0.26))
674 _arrow(ax, (0.46, 0.18), (0.54, 0.18))
675 _save(fig, out, "02_packet_compute_tile")
676
677
678 def fig_calculus_pipeline(out: Path) -> None:
679 fig, ax = plt.subplots(figsize=(13.0, 5.7))
680 ax.set_xlim(0, 1)
681 ax.set_ylim(0, 1)
682 ax.axis("off")
683 ax.text(0.5, 0.92, "The calculus maps directly to a hardware pipeline", ha="center", fontsize=20, weight="bold")
684 labels = [
685 ("seed", "load object and grammar"),
686 ("address", "packet descriptor"),
687 ("project", "analysis or selector"),
688 ("transfer", "block or low-rank edge"),
689 ("refine", "local child graph"),
690 ("synthesize", "exact reconstruction"),
691 ("certify", "residual and bound"),
692 ]
693 xs = np.linspace(0.025, 0.84, len(labels))
694 for i, (x, (title, sub)) in enumerate(zip(xs, labels, strict=True)):
695 _box(ax, x, 0.43, 0.13, 0.22, f"{title}\n{sub}", face="#f8fafc", fontsize=10.2)
696 if i < len(labels) - 1:
697 _arrow(ax, (x + 0.13, 0.54), (xs[i + 1], 0.54))
698 _arrow(ax, (0.91, 0.40), (0.08, 0.40), text="certificate feeds residual-driven refinement", rad=-0.18)
699 ax.text(0.5, 0.15, "Every command keeps the packet identity, atlas, precision tag, parent relation, and error budget attached to the data.", ha="center", fontsize=11.5)
700 _save(fig, out, "03_calculus_pipeline")
701
702
703 def fig_descriptor(out: Path) -> None:
704 fields = [
705 ("packet_id", 12),
706 ("parent_id", 12),
707 ("atlas_id", 8),
708 ("selector", 8),
709 ("precision", 4),
710 ("shape", 24),
711 ("base_address", 64),
712 ("error_budget", 32),
713 ("flags", 12),
714 ]
715 total = sum(bits for _, bits in fields)
716 fig, ax = plt.subplots(figsize=(13.0, 4.3))
717 ax.set_xlim(0, total)
718 ax.set_ylim(0, 1)
719 ax.axis("off")
720 ax.text(total / 2, 0.92, "Packet descriptor entry", ha="center", fontsize=20, weight="bold")
721 x = 0
722 for name, bits in fields:
723 ax.add_patch(Rectangle((x, 0.35), bits, 0.30, facecolor="#e5e7eb", edgecolor="black"))
724 ax.text(x + bits / 2, 0.50, f"{name}\n{bits} bits", ha="center", va="center", fontsize=8.5)
725 x += bits
726 ax.text(total / 2, 0.14, "Selectors may be contiguous ranges, residue classes, bit masks, transform-domain bands, basis projectors, or packet-tree nodes.", ha="center", fontsize=11)
727 _save(fig, out, "04_packet_descriptor")
728
729
730 def fig_transfer_descriptor(out: Path) -> None:
731 fields = [
732 ("edge_id", 12),
733 ("src", 12),
734 ("dst", 12),
735 ("kernel", 8),
736 ("rows", 16),
737 ("cols", 16),
738 ("rank", 8),
739 ("precision", 4),
740 ("sensitivity", 32),
741 ("payload", 64),
742 ("token", 16),
743 ]
744 total = sum(bits for _, bits in fields)
745 fig, ax = plt.subplots(figsize=(13.0, 4.5))
746 ax.set_xlim(0, total)
747 ax.set_ylim(0, 1)
748 ax.axis("off")
749 ax.text(total / 2, 0.92, "Transfer edge descriptor", ha="center", fontsize=20, weight="bold")
750 x = 0
751 for name, bits in fields:
752 ax.add_patch(Rectangle((x, 0.35), bits, 0.30, facecolor="#dbeafe", edgecolor="black"))
753 ax.text(x + bits / 2, 0.50, f"{name}\n{bits}", ha="center", va="center", fontsize=8.2)
754 x += bits
755 ax.text(total / 2, 0.14, "The edge descriptor is the hardware form of P_beta T P_alpha. It carries the operator class and its certificate weight.", ha="center", fontsize=11)
756 _save(fig, out, "05_transfer_descriptor")
757
758
759 def fig_memory(config: PrismConfig, out: Path) -> None:
760 fig, ax = plt.subplots(figsize=(12.8, 6.8))
761 ax.set_xlim(0, 1)
762 ax.set_ylim(0, 1)
763 ax.axis("off")
764 ax.text(0.5, 0.95, "Factor-first packet memory hierarchy", ha="center", fontsize=20, weight="bold")
765 levels = [
766 (0.08, 0.72, 0.84, 0.14, f"HBM4 exact archive and cold factors\n{config.total_hbm_gib} GiB {config.total_hbm_tbps:.1f} TB/s aggregate", "#dcfce7"),
767 (0.16, 0.52, 0.68, 0.14, f"Shared packet cache and graph store\n{config.compute_chiplets * config.shared_sram_mib_per_chiplet} MiB", "#dbeafe"),
768 (0.24, 0.32, 0.52, 0.14, f"Distributed PSM scratchpads\n{config.execution_groups * config.local_sram_kib_per_execution_group / 1024:.0f} MiB", "#ede9fe"),
769 (0.34, 0.12, 0.32, 0.14, "Registers and exact accumulators\nactive blocks, low-rank panels, residuals", "#fef3c7"),
770 ]
771 for x, y, w, h, text, face in levels:
772 _box(ax, x, y, w, h, text, face=face, fontsize=11)
773 for i in range(len(levels) - 1):
774 x0, y0, w0, h0, _, _ = levels[i]
775 x1, y1, w1, h1, _, _ = levels[i + 1]
776 _arrow(ax, (0.50, y0), (0.50, y1 + h1))
777 ax.text(0.5, 0.03, "No dense array is required. Explicit materialization is only one optional output contract.", ha="center", fontsize=11.5, weight="bold")
778 _save(fig, out, "06_memory_hierarchy")
779
780
781 def fig_bdlr(out: Path) -> None:
782 fig, ax = plt.subplots(figsize=(12.8, 5.7))
783 ax.set_xlim(0, 1)
784 ax.set_ylim(0, 1)
785 ax.axis("off")
786 ax.text(0.5, 0.92, "BDLR execution dataflow", ha="center", fontsize=20, weight="bold")
787 _box(ax, 0.05, 0.39, 0.12, 0.20, "input X", face="#f8fafc", fontsize=13)
788 _box(ax, 0.25, 0.60, 0.18, 0.18, "block engines\nD X", face="#dbeafe", fontsize=12)
789 _box(ax, 0.25, 0.20, 0.18, 0.18, "reduction engine\nV* X", face="#ede9fe", fontsize=12)
790 _box(ax, 0.52, 0.20, 0.18, 0.18, "low-rank engine\nU (V* X)", face="#dcfce7", fontsize=12)
791 _box(ax, 0.78, 0.39, 0.16, 0.20, "packet sum\nD X + U(V*X)", face="#fef3c7", fontsize=12)
792 _arrow(ax, (0.17, 0.49), (0.25, 0.69))
793 _arrow(ax, (0.17, 0.49), (0.25, 0.29))
794 _arrow(ax, (0.43, 0.29), (0.52, 0.29))
795 _arrow(ax, (0.43, 0.69), (0.78, 0.52))
796 _arrow(ax, (0.70, 0.29), (0.78, 0.46))
797 ax.text(0.5, 0.07, "The factors remain compressed through the pipeline. Materialization is a separate command.", ha="center", fontsize=11.5)
798 _save(fig, out, "07_bdlr_dataflow")
799
800
801 def fig_transform(out: Path) -> None:
802 fig, ax = plt.subplots(figsize=(12.8, 6.2))
803 ax.set_xlim(0, 1)
804 ax.set_ylim(0, 1)
805 ax.axis("off")
806 ax.text(0.5, 0.94, "Unified transform fabric", ha="center", fontsize=20, weight="bold")
807 _box(ax, 0.05, 0.40, 0.14, 0.20, "input packet\nstream", face="#f8fafc", fontsize=12)
808 units = [
809 (0.27, 0.70, "FFT and DCT\nbutterflies"),
810 (0.27, 0.46, "wavelet lifting\npredict and update"),
811 (0.27, 0.22, "polyphase\npermute and interleave"),
812 ]
813 for x, y, text in units:
814 _box(ax, x, y, 0.20, 0.15, text, face="#dbeafe", fontsize=11)
815 _arrow(ax, (0.19, 0.50), (x, y + 0.075))
816 _box(ax, 0.57, 0.37, 0.18, 0.26, "selector bank\nfrequency bands\npacket-tree nodes\nphase channels", face="#ede9fe", fontsize=11)
817 for x, y, _ in units:
818 _arrow(ax, (x + 0.20, y + 0.075), (0.57, 0.50))
819 _box(ax, 0.82, 0.40, 0.14, 0.20, "analysis output\nor synthesis", face="#dcfce7", fontsize=12)
820 _arrow(ax, (0.75, 0.50), (0.82, 0.50))
821 ax.text(0.5, 0.07, "One configurable butterfly, permutation, and selector fabric replaces separate fixed-function data paths.", ha="center", fontsize=11.3)
822 _save(fig, out, "08_transform_fabric")
823
824
825 def fig_edge_pipeline(out: Path) -> None:
826 fig, ax = plt.subplots(figsize=(13.0, 6.0))
827 ax.set_xlim(0, 1)
828 ax.set_ylim(0, 1)
829 ax.axis("off")
830 ax.text(0.5, 0.93, "Self-calibrating edge recovery pipeline", ha="center", fontsize=20, weight="bold")
831 stages = [
832 ("spectral packet", "high-frequency coefficients"),
833 ("polyphase", "decimated channels"),
834 ("Hankel", "streaming matrix build"),
835 ("small solver", "rank and annihilator"),
836 ("phase recovery", "physical roots"),
837 ("edge synthesis", "native singular packet"),
838 ("certificate", "residual and branch margin"),
839 ]
840 xs = np.linspace(0.025, 0.84, len(stages))
841 for i, (x, (a, b)) in enumerate(zip(xs, stages, strict=True)):
842 _box(ax, x, 0.40, 0.13, 0.22, f"{a}\n{b}", face="#f8fafc", fontsize=9.7)
843 if i < len(stages) - 1:
844 _arrow(ax, (x + 0.13, 0.51), (xs[i + 1], 0.51))
845 ax.text(0.5, 0.17, "The small Hankel, QR, SVD, root, and Vandermonde operations remain on one resident solver cluster.", ha="center", fontsize=11.2)
846 _save(fig, out, "09_edge_recovery_pipeline")
847
848
849 def fig_bayes(out: Path) -> None:
850 fig, ax = plt.subplots(figsize=(12.8, 6.1))
851 ax.set_xlim(0, 1)
852 ax.set_ylim(0, 1)
853 ax.axis("off")
854 ax.text(0.5, 0.94, "Typed Bayesian operator posterior", ha="center", fontsize=20, weight="bold")
855 _box(ax, 0.05, 0.39, 0.16, 0.22, "resident state matrix\nmacro meso micro", face="#dbeafe", fontsize=11.5)
856 _box(ax, 0.29, 0.66, 0.18, 0.16, "linear models", face="#f8fafc", fontsize=11)
857 _box(ax, 0.29, 0.43, 0.18, 0.16, "quadratic and hybrid", face="#f8fafc", fontsize=11)
858 _box(ax, 0.29, 0.20, 0.18, 0.16, "delay and memory", face="#f8fafc", fontsize=11)
859 for y in (0.74, 0.51, 0.28):
860 _arrow(ax, (0.21, 0.50), (0.29, y))
861 _box(ax, 0.56, 0.37, 0.18, 0.26, "shape-batched solver\nGram matrices\nCholesky or QR\npredictive score", face="#ede9fe", fontsize=11)
862 for y in (0.74, 0.51, 0.28):
863 _arrow(ax, (0.47, y), (0.56, 0.50))
864 _box(ax, 0.82, 0.39, 0.14, 0.22, "posterior mixture\nand disagreement", face="#dcfce7", fontsize=11)
865 _arrow(ax, (0.74, 0.50), (0.82, 0.50))
866 ax.text(0.5, 0.08, "Shared design matrices are loaded once. Candidate programs are grouped by feature shape and precision.", ha="center", fontsize=11.2)
867 _save(fig, out, "10_bayesian_pipeline")
868
869
870 def fig_scheduler(out: Path) -> None:
871 fig, ax = plt.subplots(figsize=(12.8, 6.3))
872 ax.set_xlim(0, 1)
873 ax.set_ylim(0, 1)
874 ax.axis("off")
875 ax.text(0.5, 0.95, "Local refinement without global recompilation", ha="center", fontsize=20, weight="bold")
876 old = [(0.08, 0.58, "A"), (0.25, 0.58, "B"), (0.42, 0.58, "packet v"), (0.59, 0.58, "C"), (0.76, 0.58, "D")]
877 for x, y, label in old:
878 _box(ax, x, y, 0.12, 0.14, label, face="#f8fafc", fontsize=12)
879 for i in range(len(old) - 1):
880 _arrow(ax, (old[i][0] + 0.12, 0.65), (old[i + 1][0], 0.65))
881 children = [(0.35, 0.23, "v0"), (0.48, 0.23, "v1"), (0.61, 0.23, "v2")]
882 for x, y, label in children:
883 _box(ax, x, y, 0.10, 0.13, label, face="#dbeafe", fontsize=11)
884 _arrow(ax, (0.20, 0.65), (0.35, 0.30))
885 _arrow(ax, (0.45, 0.295), (0.48, 0.295))
886 _arrow(ax, (0.58, 0.295), (0.61, 0.295))
887 _arrow(ax, (0.71, 0.30), (0.76, 0.65))
888 ax.text(0.14, 0.18, "A and B keep their descriptors", ha="center", fontsize=10.5)
889 ax.text(0.82, 0.18, "C and D keep their descriptors", ha="center", fontsize=10.5)
890 ax.text(0.50, 0.08, "Only the child module and the edges incident to the parent packet are regenerated.", ha="center", fontsize=11.5, weight="bold")
891 _save(fig, out, "11_refinement_local_schedule")
892
893
894 def fig_compiler(out: Path) -> None:
895 fig, ax = plt.subplots(figsize=(13.0, 6.2))
896 ax.set_xlim(0, 1)
897 ax.set_ylim(0, 1)
898 ax.axis("off")
899 ax.text(0.5, 0.94, "PRISM packet compiler", ha="center", fontsize=20, weight="bold")
900 stages = [
901 ("calculus IR", "atlases, packets, edges"),
902 ("canonicalize", "prove shape and parent relations"),
903 ("choose form", "block sparse, BDLR, transform"),
904 ("allocate precision", "sensitivity and error budget"),
905 ("place", "tile and memory locality"),
906 ("schedule", "dependency tokens and replay"),
907 ("emit", "descriptors and certificates"),
908 ]
909 xs = np.linspace(0.02, 0.85, len(stages))
910 for i, (x, (a, b)) in enumerate(zip(xs, stages, strict=True)):
911 _box(ax, x, 0.42, 0.125, 0.21, f"{a}\n{b}", face="#f8fafc", fontsize=9.5)
912 if i < len(stages) - 1:
913 _arrow(ax, (x + 0.125, 0.525), (xs[i + 1], 0.525))
914 ax.text(0.5, 0.18, "The compiler is allowed to change the representation only when the exact output contract and certificate are preserved.", ha="center", fontsize=11.2)
915 _save(fig, out, "12_compiler_pipeline")
916
917
918 def fig_workload_map(out: Path) -> None:
919 labels = ["polyphase", "Fourier", "wavelet", "BDLR", "block sparse", "Bayesian", "Prony", "Volterra", "exact modular"]
920 engines = ["projector DMA", "transform", "transform", "matrix + low rank", "matrix + router", "solver", "solver + transform", "memory unit", "exact arithmetic"]
921 fig, ax = plt.subplots(figsize=(12.8, 6.5))
922 ax.axis("off")
923 ax.text(0.5, 0.95, "Calculus operations and hardware engines", ha="center", fontsize=20, weight="bold", transform=ax.transAxes)
924 y = 0.84
925 for i, (op, engine) in enumerate(zip(labels, engines, strict=True)):
926 face = "#f8fafc" if i % 2 == 0 else "#eef2ff"
927 ax.add_patch(Rectangle((0.08, y - 0.035), 0.84, 0.065, facecolor=face, edgecolor="none", transform=ax.transAxes))
928 ax.text(0.12, y, op, ha="left", va="center", fontsize=11.5, weight="bold", transform=ax.transAxes)
929 ax.text(0.52, y, engine, ha="left", va="center", fontsize=11.5, transform=ax.transAxes)
930 y -= 0.083
931 _save(fig, out, "13_workload_engine_map")
932
933
934 def fig_roofline(config: PrismConfig, estimates: Sequence[Estimate], out: Path) -> None:
935 fig, ax = plt.subplots(figsize=(10.8, 6.5))
936 intensity = np.logspace(-2, 4, 400)
937 peak = config.fp32_tflops
938 bw = config.total_hbm_tbps
939 roof = np.minimum(peak, intensity * bw)
940 ax.loglog(intensity, roof, linewidth=2.2, label="PRISM FP32 roofline")
941 for est in estimates:
942 perf = est.flops / (est.total_time_us * 1e-6) / 1e12
943 ax.scatter(est.arithmetic_intensity, perf, s=55)
944 ax.annotate(est.workload, (est.arithmetic_intensity, perf), xytext=(5, 5), textcoords="offset points", fontsize=8.5)
945 ax.set_xlabel("Arithmetic intensity in operations per byte")
946 ax.set_ylabel("Modeled throughput in TFLOP/s")
947 ax.set_title("PRISM roofline and representative packet workloads", weight="bold")
948 ax.grid(alpha=0.25, which="both")
949 ax.legend()
950 _save(fig, out, "14_roofline")
951
952
953 def fig_performance(pairs: Sequence[tuple[Estimate, Estimate]], out: Path) -> None:
954 names = [p[1].workload.replace("PRISM ", "") for p in pairs]
955 speedups = [p[0].total_time_us / p[1].total_time_us for p in pairs]
956 fig, ax = plt.subplots(figsize=(11.5, 6.2))
957 ax.bar(np.arange(len(names)), speedups)
958 ax.set_xticks(np.arange(len(names)), names, rotation=22, ha="right")
959 ax.set_ylabel("Modeled speedup over matched generic execution")
960 ax.set_title("Architecture-model speedups from structure and kernel fusion", weight="bold")
961 ax.grid(alpha=0.22, axis="y")
962 for i, value in enumerate(speedups):
963 ax.text(i, value, f"{value:.1f}x", ha="center", va="bottom", fontsize=10)
964 fig.tight_layout()
965 _save(fig, out, "15_modeled_speedups")
966
967
968 def fig_memory_reduction(out: Path) -> None:
969 n, b, r = 8192, 64, 8
970 dense = n * n * 4 / 2**20
971 bdlr = n * (b + 2 * r) * 4 / 2**20
972 block = n * b * 4 / 2**20
973 labels = ["dense", "block diagonal", "BDLR"]
974 values = [dense, block, bdlr]
975 fig, ax = plt.subplots(figsize=(9.5, 5.8))
976 ax.bar(labels, values)
977 ax.set_yscale("log")
978 ax.set_ylabel("Stored representation in MiB")
979 ax.set_title("Representation memory for n=8192, b=64, r=8", weight="bold")
980 ax.grid(alpha=0.22, axis="y", which="both")
981 for i, value in enumerate(values):
982 ax.text(i, value, f"{value:.2f}", ha="center", va="bottom", fontsize=10)
983 _save(fig, out, "16_representation_memory")
984
985
986 def fig_power(config: PrismConfig, out: Path) -> None:
987 labels = ["execution groups", "SRAM", "packet network", "HBM PHY", "transform and solver", "I/O and control", "clock and margin"]
988 values = [175, 80, 65, 70, 40, 30, 40]
989 if sum(values) != config.tdp_w:
990 raise RuntimeError("power budget must equal TDP")
991 fig, ax = plt.subplots(figsize=(9.0, 6.2))
992 ax.pie(values, labels=labels, autopct="%1.0f%%", startangle=90)
993 ax.set_title(f"Reference power budget at {config.tdp_w} W", weight="bold")
994 _save(fig, out, "17_power_budget")
995
996
997 def fig_area(out: Path) -> None:
998 labels = ["distributed SRAM", "matrix and vector lanes", "network and DMA", "transform and solver", "control and DFx"]
999 values = [35, 30, 15, 12, 8]
1000 fig, ax = plt.subplots(figsize=(9.0, 6.2))
1001 ax.pie(values, labels=labels, autopct="%1.0f%%", startangle=90)
1002 ax.set_title("Target compute-chiplet area allocation", weight="bold")
1003 _save(fig, out, "18_area_allocation")
1004
1005
1006 def fig_precision(out: Path) -> None:
1007 sensitivity = np.array([0.10, 0.18, 0.30, 0.52, 0.80, 1.20, 2.0, 3.2])
1008 cost = np.ones_like(sensitivity)
1009 tau = 0.02
1010 eps = tau * cost / (sensitivity * cost.sum())
1011 bits = np.clip(np.ceil(-np.log2(eps)), 8, 64)
1012 fig, ax = plt.subplots(figsize=(10.2, 5.8))
1013 ax.plot(sensitivity, bits, marker="o")
1014 ax.set_xlabel("Packet sensitivity weight")
1015 ax.set_ylabel("Allocated mantissa target in bits")
1016 ax.set_title("Sensitivity-driven precision tags", weight="bold")
1017 ax.grid(alpha=0.25)
1018 _save(fig, out, "19_precision_allocation")
1019
1020
1021 def fig_network(out: Path) -> None:
1022 fig, ax = plt.subplots(figsize=(10.8, 7.0))
1023 ax.set_xlim(-0.5, 7.5)
1024 ax.set_ylim(-0.85, 7.5)
1025 ax.set_aspect("equal")
1026 ax.set_xticks([])
1027 ax.set_yticks([])
1028 ax.set_title("One 8 x 8 compute-chiplet packet mesh", fontsize=18, weight="bold")
1029 for i in range(8):
1030 for j in range(8):
1031 ax.add_patch(Rectangle((i - 0.28, j - 0.28), 0.56, 0.56, facecolor="#e5e7eb", edgecolor="black", linewidth=0.7))
1032 if i < 7:
1033 ax.plot([i + 0.28, i + 0.72], [j, j], color="black", linewidth=0.7)
1034 if j < 7:
1035 ax.plot([i, i], [j + 0.28, j + 0.72], color="black", linewidth=0.7)
1036 ax.text(3.5, -0.58, "Routers support unicast, packet multicast, and tree reduction", ha="center", va="top", fontsize=10.5)
1037 _save(fig, out, "20_packet_mesh")
1038
1039
1040 def fig_isa(out: Path) -> None:
1041 groups = {
1042 "packet": ["PLOAD", "PSELECT", "PSPLIT", "PCOARSEN"],
1043 "transform": ["FFT", "WAVELET", "POLYPHASE", "SYNTH"],
1044 "transfer": ["BLK_GEMM", "BDLR", "SPARSE", "WOODBURY"],
1045 "solver": ["CHOLESKY", "QR", "SVD", "ROOT"],
1046 "logic": ["NONLINEAR", "MEMORY", "BAYES", "GRAPH"],
1047 "certificate": ["RESIDUAL", "BOUND", "TRACE", "COMMIT"],
1048 }
1049 fig, ax = plt.subplots(figsize=(12.0, 6.3))
1050 ax.axis("off")
1051 ax.text(0.5, 0.95, "Packet Seed Command groups", ha="center", fontsize=20, weight="bold", transform=ax.transAxes)
1052 y = 0.82
1053 for group, ops in groups.items():
1054 ax.text(0.08, y, group, ha="left", va="center", fontsize=12, weight="bold", transform=ax.transAxes)
1055 for j, op in enumerate(ops):
1056 x = 0.28 + j * 0.17
1057 ax.add_patch(FancyBboxPatch((x, y - 0.035), 0.13, 0.07, boxstyle="round,pad=0.01", facecolor="#eef2ff", edgecolor="black", transform=ax.transAxes))
1058 ax.text(x + 0.065, y, op, ha="center", va="center", fontsize=9.5, transform=ax.transAxes)
1059 y -= 0.125
1060 _save(fig, out, "21_command_groups")
1061
1062
1063 def fig_prototype(out: Path) -> None:
1064 fig, ax = plt.subplots(figsize=(12.8, 5.8))
1065 ax.set_xlim(0, 1)
1066 ax.set_ylim(0, 1)
1067 ax.axis("off")
1068 ax.text(0.5, 0.93, "Implementation path", ha="center", fontsize=20, weight="bold")
1069 stages = [
1070 ("software and CUDA", "shared Packet IR\nmeasured structured kernels"),
1071 ("trace comparison", "matched CUDA and PRISM\nbackend records"),
1072 ("FPGA PSM", "one multiprocessor\nexternal memory"),
1073 ("chiplet and package", "measured PSM scaling\nthen HBM4 and UCIe"),
1074 ]
1075 xs = [0.05, 0.29, 0.53, 0.77]
1076 for i, (x, (a, b)) in enumerate(zip(xs, stages, strict=True)):
1077 _box(ax, x, 0.38, 0.18, 0.24, f"{a}\n{b}", face="#f8fafc", fontsize=11)
1078 if i < len(stages) - 1:
1079 _arrow(ax, (x + 0.18, 0.50), (xs[i + 1], 0.50))
1080 ax.text(0.5, 0.14, "Each stage preserves the same descriptor format, operator contracts, and certificate semantics.", ha="center", fontsize=11.5)
1081 _save(fig, out, "22_implementation_path")
1082
1083
1084 def fig_negative_controls(out: Path) -> None:
1085 labels = ["regular BDLR", "regular block sparse", "multi-atlas fusion", "small one-off FFT", "dense unstructured GEMM", "irregular no-reuse graph"]
1086 suitability = [0.95, 0.88, 0.82, 0.28, 0.22, 0.18]
1087 fig, ax = plt.subplots(figsize=(11.0, 6.0))
1088 ax.barh(np.arange(len(labels)), suitability)
1089 ax.set_yticks(np.arange(len(labels)), labels)
1090 ax.set_xlim(0, 1)
1091 ax.set_xlabel("Expected architectural fit")
1092 ax.set_title("Workloads the chip should accelerate and workloads it should reject", weight="bold")
1093 ax.grid(alpha=0.22, axis="x")
1094 _save(fig, out, "23_workload_fit")
1095
1096
1097
1098 def precision_allocation(sensitivity: np.ndarray, cost: np.ndarray, tau: float) -> np.ndarray:
1099 """Allocate a continuous packet error budget under the logarithmic cost model."""
1100 s = np.asarray(sensitivity, dtype=float)
1101 c = np.asarray(cost, dtype=float)
1102 if s.shape != c.shape or s.ndim != 1:
1103 raise ValueError("sensitivity and cost must be matching one-dimensional arrays")
1104 if np.any(s <= 0) or np.any(c <= 0) or tau <= 0:
1105 raise ValueError("sensitivities, costs, and tau must be positive")
1106 return tau * c / (s * np.sum(c))
1107
1108
1109 def certificate_commit_allowed(
1110 dependency_ready: bool,
1111 kernel_complete: bool,
1112 residual: float,
1113 error_budget: float,
1114 ) -> bool:
1115 """Return true only when execution and its required numerical certificate both pass."""
1116 return bool(
1117 dependency_ready
1118 and kernel_complete
1119 and error_budget >= 0
1120 and residual >= 0
1121 and residual <= error_budget
1122 )
1123
1124
1125 def local_refinement_descriptor_bound(
1126 child_nodes: int,
1127 child_edges: int,
1128 parent_indegree: int,
1129 parent_outdegree: int,
1130 entry_count: int,
1131 exit_count: int,
1132 ) -> int:
1133 """Worst-case descriptor regeneration bound for one local refinement."""
1134 values = [child_nodes, child_edges, parent_indegree, parent_outdegree, entry_count, exit_count]
1135 if any(v < 0 for v in values):
1136 raise ValueError("descriptor counts must be nonnegative")
1137 return (
1138 child_nodes
1139 + child_edges
1140 + parent_indegree * entry_count
1141 + parent_outdegree * exit_count
1142 )
1143
1144
1145 def route_workload(
1146 operator_kind: str,
1147 representation_reduction: float,
1148 reuse_count: int,
1149 graph_nodes: int,
1150 ) -> str:
1151 """Simple reference routing rule used by the compiler examples and tests."""
1152 if reuse_count < 1 or graph_nodes < 1 or representation_reduction <= 0:
1153 raise ValueError("invalid workload description")
1154 kind = operator_kind.lower()
1155 if kind in {"bdlr", "block_sparse", "transform", "small_solver", "packet_graph"}:
1156 if representation_reduction >= 1.25 and (reuse_count >= 2 or graph_nodes >= 8):
1157 return "prism"
1158 if kind == "dense" and representation_reduction < 1.25:
1159 return "dense_path"
1160 if graph_nodes <= 3 and reuse_count == 1:
1161 return "host_or_general_device"
1162 return "profile_before_routing"
1163
1164
1165 def bdlr_sensitivity_tables(config: PrismConfig, n: int = 8192, q: int = 32) -> dict[str, object]:
1166 block_sizes = np.array([16, 32, 64, 128, 256], dtype=int)
1167 ranks = np.array([2, 4, 8, 16, 32], dtype=int)
1168 dense = dense_apply_estimate(config, n, q)
1169 speedups = np.zeros((len(ranks), len(block_sizes)))
1170 storage = np.zeros_like(speedups)
1171 for i, rank in enumerate(ranks):
1172 for j, block in enumerate(block_sizes):
1173 packet = bdlr_estimate(config, n, int(block), int(rank), q)
1174 speedups[i, j] = dense.total_time_us / packet.total_time_us
1175 storage[i, j] = n / (block + 2 * rank)
1176 return {
1177 "block_sizes": block_sizes.tolist(),
1178 "ranks": ranks.tolist(),
1179 "modeled_speedups": speedups.tolist(),
1180 "storage_reduction": storage.tolist(),
1181 }
1182
1183
1184 def fig_packet_lifecycle(out: Path) -> None:
1185 fig, ax = plt.subplots(figsize=(13.0, 6.2))
1186 ax.set_xlim(0, 1)
1187 ax.set_ylim(0, 1)
1188 ax.axis("off")
1189 ax.text(0.5, 0.94, "Life of one packet command", ha="center", fontsize=21, weight="bold")
1190 labels = [
1191 ("archive", "packet data and descriptor", "#e0f2fe"),
1192 ("ready", "dependencies and storage valid", "#ede9fe"),
1193 ("execute", "selected engine runs local map", "#dcfce7"),
1194 ("provisional", "result held before commit", "#fef3c7"),
1195 ("certify", "residual or bound checked", "#fee2e2"),
1196 ("commit", "result becomes visible", "#dbeafe"),
1197 ]
1198 xs = np.linspace(0.03, 0.82, len(labels))
1199 for i, (x, (title, sub, face)) in enumerate(zip(xs, labels, strict=True)):
1200 _box(ax, x, 0.45, 0.145, 0.20, f"{title}\n{sub}", face=face, fontsize=10.2)
1201 if i < len(labels) - 1:
1202 _arrow(ax, (x + 0.145, 0.55), (xs[i + 1], 0.55))
1203 _arrow(ax, (0.89, 0.43), (0.57, 0.35), text="failed certificate returns the region for replay", rad=-0.20)
1204 ax.text(
1205 0.5,
1206 0.16,
1207 "The result remains provisional until the declared certificate passes. A failed check does not silently publish data.",
1208 ha="center",
1209 fontsize=11.3,
1210 )
1211 _save(fig, out, "24_packet_lifecycle")
1212
1213
1214 def fig_workload_router(out: Path) -> None:
1215 fig, ax = plt.subplots(figsize=(12.5, 7.0))
1216 ax.set_xlim(0, 1)
1217 ax.set_ylim(0, 1)
1218 ax.axis("off")
1219 ax.text(0.5, 0.95, "Compiler routing decision", ha="center", fontsize=21, weight="bold")
1220 _box(ax, 0.39, 0.79, 0.22, 0.10, "Is useful packet structure proved?", face="#fef3c7", fontsize=11)
1221 _box(ax, 0.08, 0.58, 0.22, 0.12, "No\nuse the dense path or another device", face="#fee2e2", fontsize=11)
1222 _box(ax, 0.39, 0.58, 0.22, 0.12, "Yes\ndoes it reduce bytes or arithmetic?", face="#e0f2fe", fontsize=10.5)
1223 _box(ax, 0.70, 0.58, 0.22, 0.12, "Unclear\nprofile both representations", face="#f3f4f6", fontsize=10.5)
1224 _box(ax, 0.23, 0.34, 0.22, 0.12, "No\nretain the simpler route", face="#fee2e2", fontsize=11)
1225 _box(ax, 0.55, 0.34, 0.22, 0.12, "Yes\nis reuse large enough to amortize setup?", face="#dcfce7", fontsize=10.2)
1226 _box(ax, 0.41, 0.10, 0.22, 0.12, "Route to PRISM\nattach certificate plan", face="#dbeafe", fontsize=11)
1227 _arrow(ax, (0.44, 0.79), (0.25, 0.70), text="no")
1228 _arrow(ax, (0.50, 0.79), (0.50, 0.70), text="yes")
1229 _arrow(ax, (0.56, 0.79), (0.81, 0.70), text="unknown")
1230 _arrow(ax, (0.45, 0.58), (0.34, 0.46), text="no")
1231 _arrow(ax, (0.55, 0.58), (0.66, 0.46), text="yes")
1232 _arrow(ax, (0.66, 0.34), (0.52, 0.22), text="yes")
1233 ax.text(0.72, 0.23, "A small one-off graph may still be routed away", ha="center", fontsize=10.5)
1234 _save(fig, out, "25_workload_router")
1235
1236
1237 def fig_chiplet_floorplan(config: PrismConfig, out: Path) -> None:
1238 fig, ax = plt.subplots(figsize=(11.2, 7.4))
1239 ax.set_xlim(0, 10)
1240 ax.set_ylim(0, 10)
1241 ax.set_aspect("equal")
1242 ax.axis("off")
1243 ax.set_title("Reference compute-chiplet floorplan", fontsize=20, weight="bold", pad=10)
1244 ax.add_patch(Rectangle((0.4, 0.4), 9.2, 9.2, facecolor="#f8fafc", edgecolor="black", linewidth=1.5))
1245 for i in range(8):
1246 for j in range(8):
1247 ax.add_patch(Rectangle((1.2 + i * 0.9, 1.2 + j * 0.9), 0.68, 0.68, facecolor="#dbeafe", edgecolor="#334155", linewidth=0.45))
1248 for y in [1.25, 3.25, 5.25, 7.25]:
1249 ax.add_patch(Rectangle((8.55, y), 0.55, 1.3, facecolor="#dcfce7", edgecolor="black", linewidth=0.8))
1250 ax.text(8.825, y + 0.65, "solver", ha="center", va="center", rotation=90, fontsize=8)
1251 for x in [1.2, 3.0, 4.8, 6.6]:
1252 ax.add_patch(Rectangle((x, 8.55), 1.25, 0.55, facecolor="#ede9fe", edgecolor="black", linewidth=0.8))
1253 ax.text(x + 0.625, 8.825, "transform", ha="center", va="center", fontsize=8)
1254 ax.add_patch(Rectangle((0.55, 1.2), 0.42, 7.2, facecolor="#fef3c7", edgecolor="black", linewidth=0.8))
1255 ax.text(0.76, 4.8, "shared SRAM banks", ha="center", va="center", rotation=90, fontsize=9)
1256 ax.add_patch(Rectangle((1.2, 0.55), 6.8, 0.42, facecolor="#fee2e2", edgecolor="black", linewidth=0.8))
1257 ax.text(4.6, 0.76, "UCIe and package router edge", ha="center", va="center", fontsize=9)
1258 ax.add_patch(Rectangle((8.55, 8.55), 0.55, 0.55, facecolor="#fecaca", edgecolor="black", linewidth=0.8))
1259 ax.text(8.825, 8.825, "exact", ha="center", va="center", fontsize=7.5)
1260 _save(fig, out, "26_chiplet_floorplan")
1261
1262
1263 def fig_bandwidth_budget(config: PrismConfig, out: Path) -> None:
1264 labels = ["PCIe x16 host", "HBM4 aggregate", "package fabric"]
1265 values = [config.host_bidirectional_gbps / 1000.0, config.total_hbm_tbps, config.package_network_tbps]
1266 fig, ax = plt.subplots(figsize=(10.5, 5.8))
1267 bars = ax.bar(labels, values, edgecolor="black")
1268 ax.set_ylabel("Reference bidirectional or aggregate bandwidth in TB/s")
1269 ax.set_title("Bandwidth levels in the reference package", weight="bold")
1270 ax.grid(alpha=0.22, axis="y")
1271 for bar, value in zip(bars, values, strict=True):
1272 ax.text(bar.get_x() + bar.get_width() / 2, value, f"{value:.3g}", ha="center", va="bottom", fontsize=10.5)
1273 ax.text(
1274 0.5,
1275 -0.18,
1276 "The host link is not the steady-state data path. Reusable packet graphs and factors are expected to remain on the package.",
1277 ha="center",
1278 transform=ax.transAxes,
1279 fontsize=10.5,
1280 )
1281 fig.tight_layout(rect=[0, 0.08, 1, 1])
1282 _save(fig, out, "27_bandwidth_budget")
1283
1284
1285 def fig_command_state_machine(out: Path) -> None:
1286 fig, ax = plt.subplots(figsize=(12.5, 6.3))
1287 ax.set_xlim(0, 1)
1288 ax.set_ylim(0, 1)
1289 ax.axis("off")
1290 ax.text(0.5, 0.94, "Command and certificate state machine", ha="center", fontsize=21, weight="bold")
1291 states = {
1292 "queued": (0.08, 0.54, "#e0f2fe"),
1293 "ready": (0.26, 0.54, "#ede9fe"),
1294 "running": (0.44, 0.54, "#dcfce7"),
1295 "provisional": (0.62, 0.54, "#fef3c7"),
1296 "committed": (0.82, 0.72, "#dbeafe"),
1297 "replay": (0.82, 0.36, "#fee2e2"),
1298 }
1299 for name, (x, y, face) in states.items():
1300 _box(ax, x, y, 0.12, 0.11, name, face=face, fontsize=11)
1301 _arrow(ax, (0.20, 0.595), (0.26, 0.595), text="tokens ready")
1302 _arrow(ax, (0.38, 0.595), (0.44, 0.595), text="resources granted")
1303 _arrow(ax, (0.56, 0.595), (0.62, 0.595), text="kernel done")
1304 _arrow(ax, (0.74, 0.61), (0.82, 0.74), text="certificate passes")
1305 _arrow(ax, (0.74, 0.56), (0.82, 0.42), text="certificate fails")
1306 _arrow(ax, (0.88, 0.36), (0.50, 0.49), text="reissue local region", rad=-0.18)
1307 ax.text(0.5, 0.16, "Only committed results can satisfy downstream completion tokens.", ha="center", fontsize=11.5)
1308 _save(fig, out, "28_command_state_machine")
1309
1310
1311 def fig_bdlr_sensitivity(config: PrismConfig, out: Path) -> None:
1312 table = bdlr_sensitivity_tables(config)
1313 blocks = table["block_sizes"]
1314 ranks = table["ranks"]
1315 speedups = np.array(table["modeled_speedups"])
1316 fig, ax = plt.subplots(figsize=(9.8, 6.4))
1317 im = ax.imshow(speedups, origin="lower", aspect="auto", cmap="viridis")
1318 ax.set_xticks(range(len(blocks)), blocks)
1319 ax.set_yticks(range(len(ranks)), ranks)
1320 ax.set_xlabel("Packet block width b")
1321 ax.set_ylabel("Low-rank correction r")
1322 ax.set_title("Modeled BDLR speedup sensitivity for n=8192 and q=32", weight="bold")
1323 for i in range(len(ranks)):
1324 for j in range(len(blocks)):
1325 ax.text(j, i, f"{speedups[i, j]:.1f}x", ha="center", va="center", fontsize=9, color="white" if speedups[i, j] < speedups.max() * 0.55 else "black")
1326 fig.colorbar(im, ax=ax, label="Modeled speedup")
1327 fig.tight_layout()
1328 _save(fig, out, "29_bdlr_speedup_sensitivity")
1329
1330
1331 def fig_bdlr_storage_sensitivity(config: PrismConfig, out: Path) -> None:
1332 table = bdlr_sensitivity_tables(config)
1333 blocks = table["block_sizes"]
1334 ranks = table["ranks"]
1335 reduction = np.array(table["storage_reduction"])
1336 fig, ax = plt.subplots(figsize=(9.8, 6.4))
1337 im = ax.imshow(reduction, origin="lower", aspect="auto", cmap="magma")
1338 ax.set_xticks(range(len(blocks)), blocks)
1339 ax.set_yticks(range(len(ranks)), ranks)
1340 ax.set_xlabel("Packet block width b")
1341 ax.set_ylabel("Low-rank correction r")
1342 ax.set_title("Dense-to-factor storage reduction", weight="bold")
1343 for i in range(len(ranks)):
1344 for j in range(len(blocks)):
1345 ax.text(j, i, f"{reduction[i, j]:.1f}x", ha="center", va="center", fontsize=9, color="white" if reduction[i, j] < reduction.max() * 0.50 else "black")
1346 fig.colorbar(im, ax=ax, label="Storage reduction")
1347 fig.tight_layout()
1348 _save(fig, out, "30_bdlr_storage_sensitivity")
1349
1350
1351 def fig_solver_pipeline(out: Path) -> None:
1352 stages = [
1353 ("FFT packet", 0.0, 3.0, "#dbeafe"),
1354 ("polyphase", 2.3, 1.3, "#ede9fe"),
1355 ("Hankel build", 3.1, 1.0, "#dcfce7"),
1356 ("rank and SVD", 4.0, 1.5, "#fef3c7"),
1357 ("roots and phase", 5.3, 1.5, "#fee2e2"),
1358 ("Vandermonde", 6.5, 1.2, "#cffafe"),
1359 ("native synthesis", 7.4, 1.5, "#bfdbfe"),
1360 ("certificate", 8.5, 1.0, "#fecaca"),
1361 ]
1362 fig, ax = plt.subplots(figsize=(12.0, 6.0))
1363 y = np.arange(len(stages))
1364 for i, (name, start, width, color) in enumerate(stages):
1365 ax.barh(i, width, left=start, color=color, edgecolor="black")
1366 ax.text(start + width / 2, i, name, ha="center", va="center", fontsize=9.5)
1367 ax.set_yticks([])
1368 ax.set_xlabel("Illustrative normalized time")
1369 ax.set_title("Resident small-solver pipeline for unknown-edge recovery", weight="bold")
1370 ax.set_xlim(0, 10)
1371 ax.grid(alpha=0.18, axis="x")
1372 ax.invert_yaxis()
1373 fig.tight_layout()
1374 _save(fig, out, "31_solver_pipeline")
1375
1376
1377 def fig_pr_lasm_state_map(out: Path) -> None:
1378 fig, ax = plt.subplots(figsize=(12.8, 7.0))
1379 ax.set_xlim(0, 1)
1380 ax.set_ylim(0, 1)
1381 ax.axis("off")
1382 ax.text(0.5, 0.95, "PR-LASM data that remains resident on PRISM", ha="center", fontsize=20, weight="bold")
1383 _box(ax, 0.05, 0.58, 0.25, 0.22, "exact archive\nspatial, Fourier, wavelet, polyphase\nreconstructible signal object", face="#e0f2fe", fontsize=10.8)
1384 _box(ax, 0.38, 0.67, 0.18, 0.12, "macro state\n48 values", face="#dcfce7", fontsize=11)
1385 _box(ax, 0.38, 0.49, 0.18, 0.12, "meso state\n96 values", face="#dcfce7", fontsize=11)
1386 _box(ax, 0.38, 0.31, 0.18, 0.12, "micro state\n192 values", face="#dcfce7", fontsize=11)
1387 _box(ax, 0.66, 0.58, 0.27, 0.22, "typed program portfolio\nlinear, nonlinear, memory, Bayesian\nshared resident design matrices", face="#fef3c7", fontsize=10.8)
1388 _box(ax, 0.66, 0.23, 0.27, 0.17, "transition graph and planner\nexact replay edges kept separate\nfrom probabilistic predictions", face="#ede9fe", fontsize=10.5)
1389 _arrow(ax, (0.30, 0.69), (0.38, 0.73))
1390 _arrow(ax, (0.30, 0.69), (0.38, 0.55))
1391 _arrow(ax, (0.30, 0.69), (0.38, 0.37))
1392 _arrow(ax, (0.56, 0.73), (0.66, 0.69))
1393 _arrow(ax, (0.56, 0.55), (0.66, 0.66))
1394 _arrow(ax, (0.56, 0.37), (0.66, 0.63))
1395 _arrow(ax, (0.80, 0.58), (0.80, 0.40))
1396 _arrow(ax, (0.70, 0.23), (0.28, 0.56), text="residual-driven refinement", rad=-0.16)
1397 ax.text(0.5, 0.10, "Compact prediction state never replaces the exact archive.", ha="center", fontsize=11.5)
1398 _save(fig, out, "32_pr_lasm_state_map")
1399
1400
1401 def fig_verification_pyramid(out: Path) -> None:
1402 fig, ax = plt.subplots(figsize=(10.0, 7.0))
1403 ax.set_xlim(0, 1)
1404 ax.set_ylim(0, 1)
1405 ax.axis("off")
1406 ax.text(0.5, 0.95, "Verification ladder", ha="center", fontsize=21, weight="bold")
1407 levels = [
1408 (0.08, 0.14, 0.84, "mathematical operator contracts", "#dbeafe"),
1409 (0.14, 0.27, 0.72, "behavioral and performance model", "#e0f2fe"),
1410 (0.20, 0.40, 0.60, "bit-accurate kernels and compiler conformance", "#dcfce7"),
1411 (0.26, 0.53, 0.48, "RTL simulation and formal properties", "#fef3c7"),
1412 (0.32, 0.66, 0.36, "FPGA overlay and measured data movement", "#fee2e2"),
1413 (0.38, 0.79, 0.24, "single-chiplet silicon", "#fecaca"),
1414 ]
1415 for x, y, w, label, color in levels:
1416 ax.add_patch(Rectangle((x, y), w, 0.09, facecolor=color, edgecolor="black", linewidth=1.0))
1417 ax.text(x + w / 2, y + 0.045, label, ha="center", va="center", fontsize=10.5)
1418 ax.text(0.5, 0.06, "The present work covers the first two levels and defines the interfaces needed by the remaining levels.", ha="center", fontsize=10.7)
1419 _save(fig, out, "33_verification_pyramid")
1420
1421
1422 def fig_fpga_overlay(out: Path) -> None:
1423 fig, ax = plt.subplots(figsize=(12.6, 6.6))
1424 ax.set_xlim(0, 1)
1425 ax.set_ylim(0, 1)
1426 ax.axis("off")
1427 ax.text(0.5, 0.94, "Recommended FPGA overlay", ha="center", fontsize=21, weight="bold")
1428 _box(ax, 0.04, 0.40, 0.15, 0.20, "host and runtime\nPCIe DMA\ncommand ring", face="#fef3c7", fontsize=10.5)
1429 _box(ax, 0.25, 0.62, 0.17, 0.16, "packet directory\nand token scoreboard", face="#e0f2fe", fontsize=10.5)
1430 _box(ax, 0.25, 0.28, 0.17, 0.16, "projector DMA\nand packet SRAM", face="#ede9fe", fontsize=10.5)
1431 _box(ax, 0.49, 0.62, 0.17, 0.16, "transform engine\nFFT and lifting", face="#dcfce7", fontsize=10.5)
1432 _box(ax, 0.49, 0.28, 0.17, 0.16, "matrix and low-rank\nengine", face="#dbeafe", fontsize=10.5)
1433 _box(ax, 0.73, 0.62, 0.17, 0.16, "small solver\nQR, SVD, roots", face="#fef3c7", fontsize=10.5)
1434 _box(ax, 0.73, 0.28, 0.17, 0.16, "certificate unit\nand commit controller", face="#fee2e2", fontsize=10.5)
1435 _arrow(ax, (0.19, 0.50), (0.25, 0.70))
1436 _arrow(ax, (0.19, 0.50), (0.25, 0.36))
1437 _arrow(ax, (0.42, 0.70), (0.49, 0.70))
1438 _arrow(ax, (0.42, 0.36), (0.49, 0.36))
1439 _arrow(ax, (0.66, 0.70), (0.73, 0.70))
1440 _arrow(ax, (0.66, 0.36), (0.73, 0.36))
1441 _arrow(ax, (0.82, 0.62), (0.82, 0.44))
1442 _arrow(ax, (0.73, 0.34), (0.66, 0.34), text="replay on failure", rad=-0.18)
1443 ax.text(0.5, 0.12, "The overlay should preserve the final descriptor format so software can move to a later ASIC without changing its graph model.", ha="center", fontsize=10.8)
1444 _save(fig, out, "34_fpga_overlay")
1445
1446
1447 def fig_risk_matrix(out: Path) -> None:
1448 risks = [
1449 (4, 4, "HBM and package power"),
1450 (3, 4, "small-solver latency"),
1451 (3, 3, "NoC congestion"),
1452 (2, 5, "compiler misclassification"),
1453 (3, 2, "descriptor overhead"),
1454 (2, 4, "certificate false pass"),
1455 (2, 3, "exact-arithmetic timing"),
1456 ]
1457 fig, ax = plt.subplots(figsize=(9.2, 6.8))
1458 ax.set_xlim(0.5, 5.5)
1459 ax.set_ylim(0.5, 5.5)
1460 ax.set_xticks(range(1, 6))
1461 ax.set_yticks(range(1, 6))
1462 ax.set_xlabel("Likelihood before mitigation")
1463 ax.set_ylabel("Impact")
1464 ax.set_title("Development risk map", weight="bold")
1465 for x in range(1, 6):
1466 for y in range(1, 6):
1467 score = x * y
1468 color = "#dcfce7" if score <= 6 else "#fef3c7" if score <= 12 else "#fee2e2"
1469 ax.add_patch(Rectangle((x - 0.5, y - 0.5), 1, 1, facecolor=color, edgecolor="white"))
1470 for x, y, label in risks:
1471 ax.scatter([x], [y], s=70, edgecolor="black", zorder=3)
1472 ax.text(x + 0.08, y + 0.08, label, fontsize=8.5, va="bottom")
1473 ax.grid(color="white", linewidth=1.2)
1474 fig.tight_layout()
1475 _save(fig, out, "35_risk_matrix")
1476
1477
1478 def fig_compiler_trace(out: Path) -> None:
1479 fig, ax = plt.subplots(figsize=(12.8, 6.2))
1480 ax.set_xlim(0, 1)
1481 ax.set_ylim(0, 1)
1482 ax.axis("off")
1483 ax.text(0.5, 0.94, "Worked compiler trace for one BDLR operator", ha="center", fontsize=20, weight="bold")
1484 stages = [
1485 (0.03, "typed operator\n$A=D+UV^*$"),
1486 (0.20, "legality\nshape and output contract"),
1487 (0.37, "representation\nkeep factors"),
1488 (0.54, "placement\nco-locate blocks and factor rows"),
1489 (0.71, "precision\nallocate edge budgets"),
1490 (0.86, "schedule\nreduce and broadcast"),
1491 ]
1492 for i, (x, text) in enumerate(stages):
1493 _box(ax, x, 0.48, 0.12, 0.19, text, face="#f8fafc", fontsize=9.8)
1494 if i < len(stages) - 1:
1495 _arrow(ax, (x + 0.12, 0.575), (stages[i + 1][0], 0.575))
1496 ax.text(0.5, 0.29, "Explicit array never created", ha="center", fontsize=13, weight="bold")
1497 ax.text(0.5, 0.20, "Stored scalars fall from $n^2$ to $n(b+2r)$ while the external operator contract remains unchanged.", ha="center", fontsize=11)
1498 _save(fig, out, "36_compiler_trace")
1499
1500
1501 def fig_packet_streaming_multiprocessor(config: PrismConfig, out: Path) -> None:
1502 fig, ax = plt.subplots(figsize=(13.0, 7.2))
1503 ax.set_xlim(0, 1)
1504 ax.set_ylim(0, 1)
1505 ax.axis("off")
1506 ax.text(0.5, 0.96, "Packet Streaming Multiprocessor", ha="center", va="top", fontsize=21, weight="bold")
1507 _box(ax, 0.05, 0.69, 0.19, 0.16, "front end\npacket warps\ndependency tokens", face="#fef3c7", fontsize=10.5)
1508 _box(ax, 0.29, 0.69, 0.19, 0.16, "representation router\nblock, factors, transform\nsmall solver, dense leaf", face="#ede9fe", fontsize=10.0)
1509 _box(ax, 0.53, 0.69, 0.19, 0.16, "descriptor and atlas cache\nparent, precision, output contract", face="#e0f2fe", fontsize=9.8)
1510 _box(ax, 0.77, 0.69, 0.18, 0.16, "certificate and commit\nresidual, bound, replay", face="#fee2e2", fontsize=10.2)
1511 for i in range(3):
1512 starts = [0.24, 0.48, 0.72]
1513 ends = [0.29, 0.53, 0.77]
1514 _arrow(ax, (starts[i], 0.77), (ends[i], 0.77))
1515 xs = [0.06, 0.29, 0.52, 0.75]
1516 colors = ["#dbeafe", "#dcfce7", "#fef3c7", "#ede9fe"]
1517 labels = [
1518 "execution group 0\n16 x 16 matrix\nvector lanes",
1519 "execution group 1\n16 x 16 matrix\nvector lanes",
1520 "execution group 2\n16 x 16 matrix\nvector lanes",
1521 "execution group 3\n16 x 16 matrix\nvector lanes",
1522 ]
1523 for x, face, label in zip(xs, colors, labels, strict=True):
1524 _box(ax, x, 0.36, 0.19, 0.22, label, face=face, fontsize=10.4)
1525 _arrow(ax, (0.385, 0.69), (x + 0.095, 0.58))
1526 _box(ax, 0.12, 0.10, 0.34, 0.16, f"PSM packet scratchpad\n{config.execution_groups_per_psm * config.local_sram_kib_per_execution_group // 1024} MiB, four independently banked regions", face="#f3f4f6", fontsize=11)
1527 _box(ax, 0.54, 0.10, 0.34, 0.16, "asynchronous packet DMA and mesh port\ngather, scatter, multicast, reduction", face="#f3f4f6", fontsize=11)
1528 for x in xs:
1529 _arrow(ax, (x + 0.095, 0.36), (0.29, 0.26))
1530 _arrow(ax, (x + 0.095, 0.36), (0.71, 0.26))
1531 ax.text(0.5, 0.03, "A packet warp may invoke several execution forms without converting the operator into one dense array.", ha="center", fontsize=11.4, weight="bold")
1532 _save(fig, out, "37_packet_streaming_multiprocessor")
1533
1534
1535 def fig_mixed_representation_space(out: Path) -> None:
1536 fig, ax = plt.subplots(figsize=(13.0, 6.8))
1537 ax.set_xlim(0, 1)
1538 ax.set_ylim(0, 1)
1539 ax.axis("off")
1540 ax.text(0.5, 0.95, "One operator, several executable representations", ha="center", fontsize=21, weight="bold")
1541 _box(ax, 0.39, 0.72, 0.22, 0.14, "typed operator\noutput contract and tolerance", face="#fef3c7", fontsize=11.5)
1542 forms = [
1543 (0.04, 0.43, "block and\nblock sparse", "#dbeafe"),
1544 (0.23, 0.43, "low rank and\nBDLR factors", "#dcfce7"),
1545 (0.42, 0.43, "Kronecker and\ntensor factors", "#ede9fe"),
1546 (0.61, 0.43, "sparse chain and\nbutterfly", "#e0f2fe"),
1547 (0.80, 0.43, "transform and\npacket graph", "#fef3c7"),
1548 ]
1549 for x, y, label, face in forms:
1550 _box(ax, x, y, 0.16, 0.15, label, face=face, fontsize=10.5)
1551 _arrow(ax, (0.50, 0.72), (x + 0.08, y + 0.15))
1552 _box(ax, 0.38, 0.14, 0.24, 0.14, "explicit full array\noptional fallback or requested output", face="#fee2e2", fontsize=11.0)
1553 for x, y, _, _ in forms:
1554 _arrow(ax, (x + 0.08, y), (0.50, 0.28), rad=0.04 if x < 0.5 else -0.04)
1555 ax.text(0.5, 0.05, "The dense representation is one candidate leaf. It is not the machine state that every path must preserve.", ha="center", fontsize=11.5, weight="bold")
1556 _save(fig, out, "38_mixed_representation_space")
1557
1558
1559 def fig_pmr_gemm_planner(out: Path) -> None:
1560 fig, ax = plt.subplots(figsize=(13.0, 7.0))
1561 ax.set_xlim(0, 1)
1562 ax.set_ylim(0, 1)
1563 ax.axis("off")
1564 ax.text(0.5, 0.96, "PMR-GEMM mixed-representation planning", ha="center", fontsize=21, weight="bold")
1565 _box(ax, 0.39, 0.78, 0.22, 0.12, "product node\nA B", face="#fef3c7", fontsize=13)
1566 choices = [
1567 (0.04, 0.54, "generic dense leaf"),
1568 (0.21, 0.54, "low-rank\ncontraction"),
1569 (0.38, 0.54, "Kronecker\nidentity"),
1570 (0.55, 0.54, "transform-domain\nrule"),
1571 (0.72, 0.54, "sparse factor\nchain"),
1572 (0.87, 0.54, "split and\nrecurse"),
1573 ]
1574 for x, y, label in choices:
1575 w = 0.12 if x < 0.85 else 0.10
1576 _box(ax, x, y, w, 0.14, label, face="#f8fafc", fontsize=9.7)
1577 _arrow(ax, (0.50, 0.78), (x + w / 2, y + 0.14))
1578 _box(ax, 0.24, 0.25, 0.22, 0.13, "Bellman objective\nlocal cost + error + child plans", face="#dbeafe", fontsize=10.8)
1579 _box(ax, 0.54, 0.25, 0.22, 0.13, "output contract\napply, factors, transform plan, array", face="#dcfce7", fontsize=10.5)
1580 _arrow(ax, (0.92, 0.54), (0.35, 0.38))
1581 _arrow(ax, (0.50, 0.54), (0.65, 0.38))
1582 _box(ax, 0.39, 0.06, 0.22, 0.12, "least-cost certified plan", face="#ede9fe", fontsize=12)
1583 _arrow(ax, (0.35, 0.25), (0.48, 0.18))
1584 _arrow(ax, (0.65, 0.25), (0.52, 0.18))
1585 ax.text(0.5, 0.01, "The generic leaf remains available, but structured leaves are not converted back to dense form unless the output contract asks for it.", ha="center", fontsize=10.8)
1586 _save(fig, out, "39_pmr_gemm_planner")
1587
1588
1589 def fig_shared_packet_ir(out: Path) -> None:
1590 fig, ax = plt.subplots(figsize=(13.0, 6.6))
1591 ax.set_xlim(0, 1)
1592 ax.set_ylim(0, 1)
1593 ax.axis("off")
1594 ax.text(0.5, 0.95, "One Packet IR, two execution backends", ha="center", fontsize=21, weight="bold")
1595 _box(ax, 0.37, 0.73, 0.26, 0.13, "calculus front end\npackets, atlases, transfer graph", face="#fef3c7", fontsize=11.2)
1596 _box(ax, 0.37, 0.52, 0.26, 0.13, "Packet IR\nrepresentation, schedule, precision, certificate", face="#ede9fe", fontsize=10.8)
1597 _arrow(ax, (0.50, 0.73), (0.50, 0.65))
1598 _box(ax, 0.08, 0.20, 0.34, 0.22, "CUDA compatibility backend\nCUDA Graphs\nasynchronous copies\ngrouped GEMM, cuFFT, cuSPARSE, CUB", face="#dbeafe", fontsize=10.8)
1599 _box(ax, 0.58, 0.20, 0.34, 0.22, "PRISM backend\nPacket Streaming Multiprocessors\nresident factors and small solvers\nlocal refinement and certified commit", face="#dcfce7", fontsize=10.8)
1600 _arrow(ax, (0.43, 0.52), (0.25, 0.42))
1601 _arrow(ax, (0.57, 0.52), (0.75, 0.42))
1602 ax.text(0.5, 0.08, "The CUDA path is the software foundation and the matched baseline. PRISM accelerates the same Packet IR rather than replacing it with a different mathematics.", ha="center", fontsize=11.2, weight="bold")
1603 _save(fig, out, "40_shared_packet_ir")
1604
1605
1606 def fig_gain_decomposition(out: Path) -> None:
1607 data = gain_decomposition()
1608 labels = ["representation\nreduction", "measured packet CUDA\nvs dense CUDA", "CUDA Graph gain\nsame representation", "compressed output\nvs materialization"]
1609 values = [float(data["representation_reduction_bdlr"]), float(data["measured_packet_gpu_gain_vs_dense_gpu"]), float(data["measured_cuda_graph_gain_same_representation"]), float(data["measured_compressed_output_gain"])]
1610 fig, ax = plt.subplots(figsize=(12.2, 6.3))
1611 bars = ax.bar(np.arange(len(values)), values)
1612 ax.set_yscale("log")
1613 ax.set_xticks(np.arange(len(values)), labels)
1614 ax.set_ylabel("Gain or reduction factor, logarithmic scale")
1615 ax.set_title("Separate the source of each gain", weight="bold")
1616 ax.grid(alpha=0.22, axis="y", which="both")
1617 for bar, value in zip(bars, values, strict=True):
1618 ax.text(bar.get_x() + bar.get_width() / 2, value * 1.08, f"{value:.2f}x", ha="center", va="bottom", fontsize=10.5)
1619 fig.text(0.5, 0.01, "These factors answer different questions. They are not multiplied unless the timing scope, representation, and output contract match.", ha="center", fontsize=10.5)
1620 fig.tight_layout(rect=[0, 0.05, 1, 1])
1621 _save(fig, out, "41_gain_decomposition")
1622
1623
1624 def fig_output_contracts(out: Path) -> None:
1625 fig, ax = plt.subplots(figsize=(13.0, 6.5))
1626 ax.set_xlim(0, 1)
1627 ax.set_ylim(0, 1)
1628 ax.axis("off")
1629 ax.text(0.5, 0.95, "Output contracts determine whether materialization is needed", ha="center", fontsize=21, weight="bold")
1630 _box(ax, 0.06, 0.42, 0.18, 0.20, "operator plan\npacket graph or factors", face="#fef3c7", fontsize=11.5)
1631 contracts = [
1632 (0.31, 0.70, "apply to vectors\nreturn Y"),
1633 (0.31, 0.49, "compressed operator\nreturn factors"),
1634 (0.31, 0.28, "transform plan\nreturn chain"),
1635 (0.31, 0.07, "explicit array\nreturn every entry"),
1636 ]
1637 outputs = [
1638 (0.68, 0.70, "streamed application\nno matrix object"),
1639 (0.68, 0.49, "BDLR, Kronecker, sparse chain\nno full array"),
1640 (0.68, 0.28, "FFT, wavelet, butterfly\nno full array"),
1641 (0.68, 0.07, "materialization command\nonly for this contract"),
1642 ]
1643 for (x, y, label), (_, _, output) in zip(contracts, outputs, strict=True):
1644 _box(ax, x, y, 0.23, 0.14, label, face="#f8fafc", fontsize=10.5)
1645 _box(ax, 0.68, y, 0.25, 0.14, output, face="#dbeafe" if y > 0.1 else "#fee2e2", fontsize=10.2)
1646 _arrow(ax, (0.24, 0.52), (x, y + 0.07), rad=0.04)
1647 _arrow(ax, (x + 0.23, y + 0.07), (0.68, y + 0.07))
1648 ax.text(0.5, 0.02, "The architecture preserves the requested mathematical result, not an unnecessary intermediate representation.", ha="center", fontsize=11.3, weight="bold")
1649 _save(fig, out, "42_output_contracts")
1650
1651
1652 def fig_cuda_measured_baselines(out: Path) -> None:
1653 data = measured_cuda_baselines()
1654 labels = ["equal block\npacket", "BDLR standard\npacket", "BDLR graph\npacket", "compressed\nproduct", "materialized\nproduct"]
1655 values = [
1656 float(data["equal_block_packet_ms"]),
1657 float(data["bdlr_standard_packet_ms"]),
1658 float(data["bdlr_graph_packet_ms"]),
1659 float(data["compressed_product_ms"]),
1660 float(data["materialized_product_ms"]),
1661 ]
1662 fig, ax = plt.subplots(figsize=(12.4, 6.2))
1663 bars = ax.bar(np.arange(len(values)), values)
1664 ax.set_xticks(np.arange(len(values)), labels)
1665 ax.set_ylabel("Measured time in milliseconds")
1666 ax.set_title("Existing CUDA packet algorithms provide the software foundation", weight="bold")
1667 ax.grid(alpha=0.22, axis="y")
1668 for bar, value in zip(bars, values, strict=True):
1669 ax.text(bar.get_x() + bar.get_width() / 2, value + max(values) * 0.025, f"{value:.4f}", ha="center", va="bottom", fontsize=10)
1670 fig.text(0.5, 0.01, "Prior measurements on an RTX 3070 Ti Laptop GPU. Compressed and materialized product rows have different output contracts.", ha="center", fontsize=10.4)
1671 fig.tight_layout(rect=[0, 0.05, 1, 1])
1672 _save(fig, out, "43_cuda_measured_baselines")
1673
1674
1675 def generate_all(root: Path) -> dict[str, object]:
1676 config = PrismConfig()
1677 figures = root / "figures"
1678 results_dir = root / "results"
1679 results_dir.mkdir(parents=True, exist_ok=True)
1680
1681 dense = dense_apply_estimate(config, 8192, 32)
1682 bdlr = bdlr_estimate(config, 8192, 64, 8, 32)
1683 sparse = block_sparse_estimate(config, 128, 64, 5, 32)
1684 atlas_base, atlas_prism = multi_atlas_estimate(config, 1024 * 1024, 5)
1685 edge_base, edge_prism = edge_recovery_estimate(config, 16384, 12)
1686 bayes_base, bayes_prism = bayes_estimate(config, 256, 4096, 96, 8)
1687
1688 estimates = [dense, bdlr, sparse, atlas_base, atlas_prism, edge_base, edge_prism, bayes_base, bayes_prism]
1689 pairs = [(dense, bdlr), (atlas_base, atlas_prism), (edge_base, edge_prism), (bayes_base, bayes_prism)]
1690
1691 fig_package(config, figures)
1692 fig_tile(config, figures)
1693 fig_calculus_pipeline(figures)
1694 fig_descriptor(figures)
1695 fig_transfer_descriptor(figures)
1696 fig_memory(config, figures)
1697 fig_bdlr(figures)
1698 fig_transform(figures)
1699 fig_edge_pipeline(figures)
1700 fig_bayes(figures)
1701 fig_scheduler(figures)
1702 fig_compiler(figures)
1703 fig_workload_map(figures)
1704 fig_roofline(config, [bdlr, sparse, atlas_prism, edge_prism, bayes_prism], figures)
1705 fig_performance(pairs, figures)
1706 fig_memory_reduction(figures)
1707 fig_power(config, figures)
1708 fig_area(figures)
1709 fig_precision(figures)
1710 fig_network(figures)
1711 fig_isa(figures)
1712 fig_prototype(figures)
1713 fig_negative_controls(figures)
1714 fig_packet_lifecycle(figures)
1715 fig_workload_router(figures)
1716 fig_chiplet_floorplan(config, figures)
1717 fig_bandwidth_budget(config, figures)
1718 fig_command_state_machine(figures)
1719 fig_bdlr_sensitivity(config, figures)
1720 fig_bdlr_storage_sensitivity(config, figures)
1721 fig_solver_pipeline(figures)
1722 fig_pr_lasm_state_map(figures)
1723 fig_verification_pyramid(figures)
1724 fig_fpga_overlay(figures)
1725 fig_risk_matrix(figures)
1726 fig_compiler_trace(figures)
1727 fig_packet_streaming_multiprocessor(config, figures)
1728 fig_mixed_representation_space(figures)
1729 fig_pmr_gemm_planner(figures)
1730 fig_shared_packet_ir(figures)
1731 fig_gain_decomposition(figures)
1732 fig_output_contracts(figures)
1733 fig_cuda_measured_baselines(figures)
1734
1735 tests = run_self_tests()
1736 result = {
1737 "config": asdict(config),
1738 "derived": {
1739 "packet_streaming_multiprocessors": config.psms,
1740 "execution_groups": config.execution_groups,
1741 "tiles": config.tiles,
1742 "packet_streaming_multiprocessors_per_chiplet": config.packet_streaming_multiprocessors_per_chiplet,
1743 "execution_groups_per_psm": config.execution_groups_per_psm,
1744 "fp16_tflops": config.fp16_tflops,
1745 "fp32_tflops": config.fp32_tflops,
1746 "fp64_tflops": config.fp64_tflops,
1747 "total_hbm_gib": config.total_hbm_gib,
1748 "total_hbm_tbps": config.total_hbm_tbps,
1749 "total_sram_mib": config.total_sram_mib,
1750 },
1751 "estimates": [asdict(e) for e in estimates],
1752 "modeled_speedups": {
1753 p[1].workload: p[0].total_time_us / p[1].total_time_us for p in pairs
1754 },
1755 "self_tests": tests,
1756 "bdlr_sensitivity": bdlr_sensitivity_tables(config),
1757 "measured_cuda_baselines": measured_cuda_baselines(),
1758 "gain_decomposition": gain_decomposition(),
1759 "representation_forms": list(REPRESENTATION_FORMS),
1760 "output_contracts": list(OUTPUT_CONTRACTS),
1761 "bandwidth_budget_tbps": {
1762 "host": config.host_bidirectional_gbps / 1000.0,
1763 "hbm": config.total_hbm_tbps,
1764 "package": config.package_network_tbps,
1765 },
1766 "figure_pairs": 43,
1767 }
1768 (results_dir / "prism_chip_results.json").write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
1769 return result
1770
1771
1772 def run_self_tests() -> dict[str, float | int | bool]:
1773 rng = np.random.default_rng(20260730)
1774
1775 x = rng.normal(size=64)
1776 packets = [residue_project(x, 4, r) for r in range(4)]
1777 packet_residual = float(np.max(np.abs(sum(packets, np.zeros_like(x)) - x)))
1778
1779 channels = polyphase_analyze(x, 4)
1780 polyphase_residual = float(np.max(np.abs(polyphase_synthesize(channels, length=x.size) - x)))
1781
1782 low, high = haar_analyze(x)
1783 haar_residual = float(np.max(np.abs(haar_synthesize(low, high) - x)))
1784
1785 blocks = [rng.normal(size=(8, 8)) for _ in range(8)]
1786 u = rng.normal(size=(64, 4))
1787 v = rng.normal(size=(64, 4))
1788 y_structured = bdlr_apply(blocks, u, v, x)
1789 y_dense = materialize_bdlr(blocks, u, v) @ x
1790 bdlr_residual = float(np.max(np.abs(y_structured - y_dense)))
1791
1792 nodes = {"A", "B", "v", "C", "D"}
1793 edges = {("A", "B"), ("B", "v"), ("v", "C"), ("C", "D")}
1794 local_nodes = {"v0", "v1", "v2"}
1795 local_edges = {("v0", "v1"), ("v1", "v2")}
1796 new_nodes, new_edges, order = substitute_task(nodes, edges, "v", local_nodes, local_edges, {"v0"}, {"v2"})
1797 schedule_ok = set(order) == new_nodes and all(order.index(a) < order.index(b) for a, b in new_edges)
1798
1799 config = PrismConfig()
1800 derived_ok = (
1801 config.psms == 64
1802 and config.execution_groups == 256
1803 and config.tiles == 256
1804 and abs(config.total_hbm_tbps - 11.2) < 1e-12
1805 and abs(config.total_sram_mib - 192.0) < 1e-12
1806 )
1807
1808 descriptor = PacketDescriptor(1, 0, 2, "residue", (64,), 0x1000, 4, "fp32", 1e-5)
1809 edge = TransferDescriptor(1, 1, 2, "bdlr", 64, 64, 4, "fp32", 0.3, 9)
1810 descriptor_ok = descriptor.packet_id == edge.src_packet and descriptor.bytes_per_element == 4
1811
1812 sensitivities = np.array([0.2, 0.5, 1.0, 2.0])
1813 costs = np.array([1.0, 2.0, 1.0, 3.0])
1814 tau = 0.025
1815 eps = precision_allocation(sensitivities, costs, tau)
1816 precision_budget_ok = abs(float(np.dot(sensitivities, eps)) - tau) < 1e-14
1817
1818 commit_ok = certificate_commit_allowed(True, True, 8e-6, 1e-5)
1819 commit_rejects = not certificate_commit_allowed(True, True, 2e-5, 1e-5)
1820
1821 refinement_bound_ok = local_refinement_descriptor_bound(3, 2, 1, 1, 1, 1) == 7
1822
1823 routing_ok = (
1824 route_workload("bdlr", 12.0, 8, 32) == "prism"
1825 and route_workload("dense", 1.0, 1, 1) == "dense_path"
1826 and route_workload("transform", 1.1, 1, 2) == "host_or_general_device"
1827 )
1828
1829 table = bdlr_sensitivity_tables(config)
1830 sensitivity_ok = (
1831 len(table["block_sizes"]) == 5
1832 and len(table["ranks"]) == 5
1833 and np.all(np.asarray(table["modeled_speedups"]) > 1.0)
1834 and np.all(np.asarray(table["storage_reduction"]) > 1.0)
1835 )
1836
1837 bandwidth_ok = (
1838 config.package_network_tbps > config.total_hbm_tbps
1839 and config.total_hbm_tbps > config.host_bidirectional_gbps / 1000.0
1840 )
1841
1842 options = {
1843 "root": (
1844 PlanChoice("dense", "explicit_dense", 100.0),
1845 PlanChoice("transform", "transform_domain", 8.0),
1846 PlanChoice("split", "packet_graph", 1.0, children=("left", "right")),
1847 ),
1848 "left": (
1849 PlanChoice("left_block", "block_diagonal", 2.0),
1850 PlanChoice("left_dense", "explicit_dense", 20.0),
1851 ),
1852 "right": (
1853 PlanChoice("right_low_rank", "low_rank", 2.5, local_error=0.01),
1854 PlanChoice("right_dense", "explicit_dense", 18.0),
1855 ),
1856 }
1857 compressed_plan = pmr_plan("root", options, error_weight=10.0, output_contract="factor_graph")
1858 explicit_plan = pmr_plan("root", options, error_weight=10.0, output_contract="explicit_array")
1859 pmr_optimal_ok = compressed_plan.choice == "split" and explicit_plan.choice == "dense"
1860
1861 contract_ok = (
1862 output_contract_allows("bdlr", "apply")
1863 and output_contract_allows("bdlr", "factor_graph")
1864 and not output_contract_allows("bdlr", "explicit_array")
1865 and output_contract_allows("explicit_dense", "explicit_array")
1866 )
1867
1868 cuda = measured_cuda_baselines()
1869 cuda_rows_ok = (
1870 abs(float(cuda["bdlr_graph_replay_gain"]) - (0.097280 / 0.079872)) < 1e-12
1871 and float(cuda["bdlr_speedup_vs_gpu_dense"]) > 1.0
1872 and float(cuda["compressed_output_gain"]) > 1.0
1873 )
1874
1875 decomposition = gain_decomposition()
1876 gain_decomposition_ok = (
1877 float(decomposition["representation_reduction_bdlr"]) == 102.4
1878 and float(decomposition["measured_packet_gpu_gain_vs_dense_gpu"]) > 1.0
1879 )
1880
1881 representation_registry_ok = (
1882 len(REPRESENTATION_FORMS) == 9
1883 and len(OUTPUT_CONTRACTS) == 5
1884 and "explicit_dense" in REPRESENTATION_FORMS
1885 and "packet_graph" in REPRESENTATION_FORMS
1886 )
1887
1888 result = {
1889 "packet_reconstruction_residual": packet_residual,
1890 "polyphase_residual": polyphase_residual,
1891 "haar_residual": haar_residual,
1892 "bdlr_residual": bdlr_residual,
1893 "local_schedule_valid": bool(schedule_ok),
1894 "derived_configuration_valid": bool(derived_ok),
1895 "descriptor_contract_valid": bool(descriptor_ok),
1896 "precision_budget_valid": bool(precision_budget_ok),
1897 "commit_protocol_valid": bool(commit_ok and commit_rejects),
1898 "local_refinement_bound_valid": bool(refinement_bound_ok),
1899 "routing_policy_valid": bool(routing_ok),
1900 "sensitivity_tables_valid": bool(sensitivity_ok),
1901 "bandwidth_hierarchy_valid": bool(bandwidth_ok),
1902 "pmr_planner_valid": bool(pmr_optimal_ok),
1903 "output_contracts_valid": bool(contract_ok),
1904 "cuda_baseline_registry_valid": bool(cuda_rows_ok),
1905 "gain_decomposition_valid": bool(gain_decomposition_ok),
1906 "representation_registry_valid": bool(representation_registry_ok),
1907 "tests_passed": int(packet_residual < 1e-12)
1908 + int(polyphase_residual < 1e-12)
1909 + int(haar_residual < 1e-12)
1910 + int(bdlr_residual < 1e-11)
1911 + int(schedule_ok)
1912 + int(derived_ok)
1913 + int(descriptor_ok)
1914 + int(precision_budget_ok)
1915 + int(commit_ok and commit_rejects)
1916 + int(refinement_bound_ok)
1917 + int(routing_ok)
1918 + int(sensitivity_ok)
1919 + int(bandwidth_ok)
1920 + int(pmr_optimal_ok)
1921 + int(contract_ok)
1922 + int(cuda_rows_ok)
1923 + int(gain_decomposition_ok)
1924 + int(representation_registry_ok),
1925 "tests_total": 18,
1926 }
1927 if result["tests_passed"] != result["tests_total"]:
1928 raise RuntimeError(f"self tests failed: {result}")
1929 return result
1930
1931
1932 def main() -> None:
1933 parser = argparse.ArgumentParser(description="Generate the Komorebi PRISM-1 architecture model and figures")
1934 parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parent)
1935 args = parser.parse_args()
1936 result = generate_all(args.root)
1937 print(json.dumps({"status": "ok", "tests": result["self_tests"], "figure_pairs": result["figure_pairs"]}, indent=2))
1938
1939
1940 if __name__ == "__main__":
1941 main()
Other PDFs for this work
Original file and source record
This copy matches the recorded source file. Its publication here does not change the work’s review status.
SHA-256: ca9b474adeb980e95d97947371883c3745167aea7a97d52a45e1109959afee57