Before we start, one figure fixes the two pictures every problem leans on. We reuse it directly: the pie is the picture behind Exercise 1.1 and 2.1 (energy per op), and the roofline is the picture behind Exercises 2.2, 2.3, 3.3 and 5.1 (which wall).
Read the left picture as: the actual add (teal sliver) is tiny; the fetch / decode / control (orange) is the bulk — this is exactly the waste Exercise 2.1 quantifies. Read the right picture as: your kernel is a dot placed by its intensity I; if it sits under the slanted line it is memory-bound (the I=25 orange dot of Ex 2.2), and the vertical line at Iridge=300 is the ridge point of Ex 2.3 where the two ceilings cross.
Answer: (c) performance-per-watt. Once Dennard scaling died (~2006), voltage could no longer drop with each shrink, so power density rose. We can fit transistors but not power them all (Dark silicon). Therefore the binding constraint is energy per useful operation, not raw clock — look again at the energy pie in the figure: the teal ALU sliver is the only useful part. GHz (a) is limited by heat; transistor count (b) is limited by power; cache (d) is a means, not the goal.
Recall Solution 1.2
False. A systolic array typically runs at a lower clock than a CPU. Its win is massive parallelism (N×N MAC cells firing every cycle), deleted control overhead, lower precision, and data reuse (each loaded value is used many times — quantified in Ex 3.2). Clock speed is not the lever.
What we did: divided energy-per-op of the CPU by energy-per-op of the accelerator.
Why: ops/joule =1/(joules per op), so the ratio of energies is the ratio of efficiencies (units of pJ cancel). This is the correct metric because energy/op is the binding constraint (Exercise 1.1), and the figure's pie shows why the CPU number is so large — most of its joules go to the orange overhead a DSA deletes.
Recall Solution 2.2
First convert units so both ceilings are in ops/second:
B⋅I=400 GB/s400×109sbytes×I25byteops=10×1012sops=10TOPS.
The bytes cancel, leaving ops/s = TOPS. Then
Pattain=min(Ppeak,B⋅I)=min(120,10)=10TOPS.
Since B⋅I=10<120=Ppeak, you are memory-bound — the orange dot in the figure sits under the slanted line. Only 10/120≈8.3% of peak is usable.
Why this step: Roofline tells you the bottleneck before you optimize — here more MACs would sit idle (assumption: compute and memory overlap perfectly; the real machine can only be worse); the fix is to raise I (fuse layers, larger tiles).
Recall Solution 2.3
Set the two ceilings equal: B⋅Iridge=Ppeak, so
Iridge=BPpeak=400×109bytes/s120×1012ops/s=300ops/byte.
(The "per second" cancels top and bottom, leaving ops/byte.) Below 300 ops/byte you are memory-bound; at or above it you are compute-bound. Our kernel's I=25 is far below 300 — deeply memory-bound. This is the vertical line in the figure.
(a) MACs/cycle =256×256=65536 (one multiply-add per cell per cycle).
(b) Each loaded weight streams past all activations along one axis, so reuse factor ≈ the array dimension=256.
(c) The "why" of N3: the output C is an N×N matrix, so it has N×N=N2output entries. Each output entry Cij=∑k=1NAikBkj is a dot product of length N, which costs N multiply-adds. Total =outputsN2×MACs eachN=N3. Here N=512: total MACs =5123=134,217,728≈1.34×108.
Recall Solution 3.2
Naive: total ops =N3 (from Ex 3.1c). If every operand is refetched from memory, bytes moved ≈3N3 (two reads + one write per MAC).
Inaive=3N3N3=31ops/byte — a *flat* line, order 1(unchanged as N grows).Systolic: you load ≈N2 data once into the grid and reuse each value ≈N times, so bytes moved ≈N2 while ops stay N3:
Isystolic=N2N3=Nops/byte — a *rising* line, order N.
Concretely: N=128⇒I≈128; N=256⇒I≈256. Twice the width, twice the intensity.
Why it matters: intensity now grows with array size, so on the Roofline you slide right past the ridge point and become compute-bound — the whole point of the design.
Recall Solution 3.3
New memory ceiling =B⋅I=400×109×340=136×1012=136 TOPS. Since 340>Iridge=300 (Ex 2.3), you have crossed the ridge:
Pattain=min(120,136)=120TOPS (now compute-bound).
Speedup over the un-fused 10 TOPS: 120/10=12×. Note the win came entirely from data reuse, not from buying hardware. Edge case I≫Iridge: raising I still higher (say 500) buys nothing more — you are pinned at the flat 120 TOPS compute ceiling; only more MACs help now.
With parallel fraction p=0.95 and speedup s=50 on that fraction:
Speedup=(1−0.95)+500.951=0.05+0.0191=0.0691≈14.5×.
Even a 50× accelerator yields only ≈14.5× overall because the untouched 5% now dominates (the immovable stub). Lesson: DSAs are narrow; you need a CPU to orchestrate the serial/irregular parts. Real systems are heterogeneous — CPU + DSA — not DSA-only.
Recall Solution 4.2
Starting attainable =B⋅I=500×109×8=4 TOPS.
Option A (double MACs): raises Ppeak but the kernel is memory-bound, so Pattain=min(2Ppeak,B⋅I)=4 TOPS — no change. Wasted money.
Option B (INT8): operands are 4× smaller, so effective I rises from 8 to 32 ops/byte. New ceiling =500×109×32=16 TOPS = 4× improvement.
Choose B.Quantization attacks the actual bottleneck (bytes moved); adding MACs does not.
(a)B⋅I=250×109×12=3 TOPS <60 TOPS ⇒ memory-bound, using 3/60=5% of peak.
(b) Ridge point Iridge=Ppeak/B=60×1012/(250×109)=240 ops/byte. You need to raise I from 12 to 240 — a 20× increase in reuse.
(c) Decision: spend on scratchpad (option ii). The array is already 95% starved; a bigger array raises Ppeak (the flat ceiling) while the kernel is pinned to the slanted ceiling — the extra MACs sit idle. More scratchpad enables bigger tiles / operand reuse, pushing I rightward toward the ridge, which is the only way to convert peak into attainable throughput. Edge case to respect: if a cold/too-small cache lowers effectiveB, you slide down the slanted ceiling and lose even the 3 TOPS — another reason the reuse (scratchpad) fix is the right lever.
Recall Solution 5.2
Scheme: Mixed precision training. Keep the sensitive parts — master weights and gradient accumulation — in higher precision (FP32), while doing the bulk matmuls (forward/backward) in low precision (bfloat16/FP16). Loss scaling prevents small gradients from underflowing.
Why it works (domain tolerance): neural nets are statistical; the millions of forward-pass MACs average away rounding noise, so low precision there is nearly free (as in inference). But accumulation of tiny gradients is a sum where rounding errors compound — that step keeps high precision. You spend precision exactly where the domain is intolerant, and save it everywhere the domain is tolerant. That selective placement is the essence of domain-specific design.
Recall Solution 5.3
Pollack's Rule: single-core performance grows as area. Doubling area gives 2≈1.41×, not 2× — diminishing returns from a monolithic core.
Contrast: the DSA instead spends that area on many small, identical MAC cells whose aggregate throughput scales roughly linearly with area (data-level parallelism), and deletes per-op control overhead. So the same transistors buy far more useful ops/mm² — provided arithmetic intensity keeps them fed (the Roofline caveat from Exercise 5.1).
Recall Self-check: which lever fixes which symptom?
Memory-bound kernel, MACs idle ::: Raise arithmetic intensity — bigger tiles, fused ops, more scratchpad, lower-precision operands.
Serial fraction dominates total time ::: Amdahl limit — keep a CPU to orchestrate; heterogeneous system, not DSA-only.
Gradient diverges in INT8 ::: Mixed precision — high precision only for sensitive accumulation.
"Doubling core area = 2x speed" ::: False by Pollack's Rule; 2≈1.41× for a monolithic core.
Kernel already right of the ridge (I≫Iridge) ::: Compute-bound — more reuse is wasted; only more MACs raise throughput now.