Appendix C

The Measurement Kit

Benchmarking methodology, the statistics you actually need, the thermal soak protocol, and the report templates every lab in this course is graded against

Appendix C — The Measurement Kit

This appendix exists to stop you making an honest measurement of the wrong thing.

Fraud is rare in this field and easy to condemn. What is common is to run a real program on real hardware, record a real number with real precision, and publish a claim the number does not support. Nobody lied. The instrument worked. The arithmetic was right. And the sentence at the top of the report is still wrong, because it is a sentence about a situation the measurement was never in.

Everything below prevents that, and it is deliberately narrow. There are no confidence intervals here and no hypothesis tests — not because those are bad, but because a measurement taken on a machine that rescales its own clocks does not satisfy their assumptions, and running them anyway is theatre that makes a weak result look strong. What you get is the small set of practices a hardware measurement actually needs, stated precisely enough that two students on two machines produce numbers a third person can compare.

Where this sits. Appendix A is the toolchain and the three workbenches. Appendix B is block-diagram literacy and dataset provenance. Appendix D is the grading contract, submission mechanics, and the AI-use policy. Appendix E is the glossary. This appendix owns methodology, statistics, the sustained-load protocol, and the six templates you are graded against. Every lab in the lab arc points here; every rubric line reading “per Appendix C” means the rules below.


C.0 — The Card

#The ruleWhy it exists
1Predict before you measure, in writing.A measurement that cannot surprise you carries no information.
2Warm up, and discard the warm-up.The first run measures cold state, not your code.
3Know which steady state you are in.Microarchitectural steady state arrives in milliseconds; thermal steady state in minutes, and it is lower.
4Repeat an odd number of times. 31 is the default.One run is an anecdote with a decimal point.
5Report the median with a dispersion figure.The median resists the environment; the dispersion tells a reader whether to believe you.
6Never report a bare number.A number without its conditions is a different claim than the one it appears to be.
7Defeat the optimizer.At -O2 the compiler will delete your loop and let you time nothing, precisely.
8Interleave when you compare.A B A B survives a drifting clock; AAAA BBBB does not.
9Control placement, or say you could not.Same instruction set, different microarchitecture, different answer.
10Tag every number: measured, derived, cited, or modeled.There is no fifth category and no untagged number.
11If the difference is smaller than the dispersion, you did not measure it.Saying so costs nothing and is most of your credibility.
12Name the choice that most flatters your conclusion, out loud, first.Somebody will find it. Better you.

C.1 — What a Microbenchmark Can and Cannot Support

Both kinds of run are legitimate. Each licenses a different kind of sentence, and writing the wrong one is the commonest analysis error in this course after mistaking utilization for performance.

MicrobenchmarkApplication benchmark
What it doesIsolates one mechanismRuns the real thing
Examplebench_harness --kernel chase; cache_walk.cppA frame of the actual renderer; a token from the actual model
VirtueUnconfounded — when the number moves you know what moved itRepresentative — it is what a user experiences
ViceTrivially easy to make precisely wrongConfounded — a dozen things could have moved it
Licenses”This mechanism, in isolation, costs about X under these conditions.""Under these conditions, the user waits about X.”
Does not licenseAny statement about how long an application takesAny statement about why, until you attach counters

Precisely wrong looks like: a working set that accidentally fits in L2; a stride that lines up with the cache’s associativity; a compiler that vectorized the loop you thought was scalar; a pointer chase timed for a fixed hop count rather than a whole lap, so it touched a twelfth of the structure you believed. Each produces a tight, repeatable, low-dispersion number, which is why it fools people. Low dispersion is evidence of a stable experiment, not of a correct one.

The professional loop — and you must say which step produced each number:

  1. Application benchmark — establish that it is slow, and how slow it feels.
  2. Counters — hypothesize why: IPC plus a miss rate, per Chapter 8.
  3. Microbenchmark — confirm the mechanism where nothing else can be responsible.
  4. Application benchmark again — confirm the fix moved a number a person can feel.

Skipping step 1 is how you make a kernel four times faster and the application zero percent faster, because the kernel was two percent of the frame. Get its share of the whole first; Chapter 2’s amdahl.py exists so you have no excuse.

Coach’s Note — Read your sentence aloud and ask what would falsify this? “The accelerator path is 3× faster” is falsified by a stopwatch. “The accelerator path is 3× faster because every operator mapped” is falsified only by a per-operator assignment dump — so if you did not get one, you may not write the second half. Most bad engineering writing is a microbenchmark result wearing an application benchmark’s grammar.


C.2 — Latency Against Throughput

Latency is how long one operation takes end to end when the next thing depends on its result. Throughput is how much work completes per unit time with many operations in flight. They are not reciprocals, and you may not derive one from the other by division. The bridge is Little’s Law:

items in flight = throughput × latency

68e9 bytes/s × 100e-9 s  =  6800 bytes in flight
6800 ÷ 64 bytes/line     ≈  106 outstanding line requests

That is Chapter 3’s memory case: a phone-class 64-bit interface at 8533 MT/s gives roughly 68 GB/s of theoretical peak, and at a DRAM latency on the order of 100 ns you must keep about a hundred cache lines in flight, permanently, to reach it. A dependent pointer chase gives you one. That is why a program can idle a 68 GB/s memory system, and why “we have bandwidth headroom” is not an answer to a latency problem.

The harness ships the pair: seq walks sequentially — high memory-level parallelism, prefetchable, so it runs at bandwidth — while chase follows a random cycle where each load’s address is the previous load’s result, so it runs at latency. Same array, same loads, same instruction count; the gap between the curves is your prefetcher and your memory-level parallelism.

SituationReportNot
Fixed work, you care how long it takesMedian latency + IQRThroughput obtained by inverting the median
A stream, you care how much gets doneThroughput over a stated windowPer-item latency obtained by inverting throughput
A deadline exists (a frame, an input response)A tail percentile as well as the median, plus the fraction of runs that met the deadlineThe mean, which hides exactly the frames that ruin the experience
Comparing two implementationsMedian + IQR for both, identical conditions”2× the throughput,” with no statement of how many were in flight

Row three matters more on mobile than anywhere: a renderer hitting its budget on the median frame and missing one in twenty produces a visible stutter, and the median is silent about it.


C.3 — Warm-Up, and the Two Steady States

There are two. They arrive on different timescales and push the number in opposite directions, which is why students get contradictory results from one machine in one afternoon.

Microarchitectural steady stateThermal steady state
Arrives inMilliseconds to a few secondsMinutes
What is warmingCaches, branch predictor, TLB, first-touch page faults, dynamic linking, any JIT, the file cache, the DVFS operating pointDie, package, spreader, chassis, surrounding air
EffectCold is slower — compulsory misses and mispredictsCold is faster — the chassis has heat capacity and the device is spending a thermal loan
Reached byDiscarded warm-up repetitionsA sustained run of minutes (C.9)
Supports”This kernel, warm, costs X""This device sustains X”

So “throw away the first run” is necessary and not sufficient: it fixes the first column and does nothing about the second. A benchmark warm microarchitecturally and cold thermally is both correct and misleading — every mechanism trained, the device at an operating point it cannot hold. Which is why every number in this course carries its thermal condition as well as its repetition count.

Choosing a warm-up count. The harness defaults to three. Test that rather than trusting it: run with --warmup 0 and a high repetition count, compare the first timed repetition against the median of the rest, and raise the warm-up until repetition one is indistinguishable from repetition ten. Anything with an interpreter, runtime or driver in the path needs more than you expect. Record the number — a warm-up count is part of the method.

Coach’s Note — Warm-up is where honest and flattering diverge quietly. There is a count that makes your favourite build look best, reachable by raising the number until the answer stops improving, and nobody will catch it. The defence is procedural: fix the warm-up count before you look at the comparison, write it in the log, use the same one on both sides.


C.4 — Why the First Run Is Always Wrong

Not “usually” — always, in one direction or the other, and often both at once.

What is coldEffect on the numberRemoved by
Data cachesEvery access is a compulsory missWarm-up
Branch predictorMispredicts on branches that will become predictableWarm-up
TLBA page-table walk on every new pageWarm-up
Unfaulted pagesA fault and a zero-fill on first touchWarm-up (allocate and touch before timing)
Lazy dynamic linkingThe first call to each symbol resolves itWarm-up
Any JIT or runtimeInterpreting before compilingWarm-up, more than you think
The file cacheThe first read comes from storage; later reads do notWarm-up, or state that cold I/O was deliberate
The DVFS operating pointThe governor has not noticed you; the core is low and climbingWarm-up, plus a stated power source
The chassisCold is fast — the thermal budget is unspentNothing. State the thermal condition instead

That last row is why best-of-N is wrong on a thermally limited device. On a fixed-clock machine with nothing else running, reporting the minimum is defensible. On a phone or a fanless laptop the fastest run is the coldest run, and coldness is not a property of the silicon — it is a property of the last four minutes. Best-of-N there measures your patience between runs. Report the median; and when min sits well below it while max sits far above, that spread is the environment talking, not noise to discard (C.8.3).


C.5 — Defeating the Compiler That Optimized Your Benchmark Away

A C++ compiler may transform your program any way that preserves observable behaviour. A benchmark’s observable behaviour is characteristically nothing at all, so deleting the loop is the obviously correct thing to do. You then measure, with impressive precision, the cost of an empty loop.

The portable barrier, from Chapter 8’s bench_harness.cpp:

static volatile std::uint64_t g_sink = 0;
static inline void do_not_optimize(std::uint64_t v) { g_sink ^= v; }

A read-modify-write of a volatile object is an observable side effect the compiler may not remove, so the value handed in must genuinely be computed and everything it depended on must genuinely run. It is portable — no inline assembly, no compiler-specific attribute — which is why the harness uses it. And it costs a load and a store per call, which is why the harness calls it once per repetition, not once per iteration: the sink write sits outside each kernel’s loop. Put it inside your own hot loop and you are measuring your barrier.

The stronger barrier, from Chapter 3’s memory programs:

inline void consume(std::uint64_t v) {
#if defined(__GNUC__) || defined(__clang__)
    asm volatile("" : : "r"(v) : "memory");
#else
    g_sink = v;
#endif
}

The empty asm volatile emits no instructions, so it costs nothing. The "r"(v) constraint says the value must exist in a register there, so the loads that produced it cannot be deleted. The "memory" clobber says memory may have changed, so loads cannot be hoisted out of the loop or across repetitions — the failure a plain volatile sink does not prevent. When memory behaviour is the subject, use this one.

What the compiler doesSymptomDefence
Deletes the loopTime constant regardless of problem size; ns/op absurdly smallBarrier, then the doubling test
Hoists the loop out of your repetition loopRepetition one is slow, the other thirty are freeThe "memory" clobber, or recompute from a per-repetition value
Constant-folds the inputTime does not respond to input values at allRead the size or seed from argv, or from a volatile
Vectorizes a “scalar” loopSub-cycle time per elementRead the assembly, or compare against a no-vectorize build — and say which you reported
Interchanges your loopsYour ijk-versus-ikj demonstration shows no differenceBuild at -O2, not -O3, and record what your compiler did

Three sanity checks, in order. (1) Doubling — double the input; a linear kernel should take about twice as long, and if it does not, the loop is gone or hoisted. (2) The clock check — convert to operations per cycle; for a dependent chain more than one per cycle is impossible, and Chapter 3’s cautionary “0.02 ns per access on a 3 GHz machine” is about sixteen dependent accesses per cycle, which is a deleted loop rather than a fast machine. Careful with the converse: an independent summation loop can legitimately retire several elements per cycle once vectorized, so know which kind you have before calling a number impossible. (3) -O0, as a diagnostic only — confirm the shape survives, then discard those numbers. Never report -O0: nobody ships it, and it flatters memory-bound code by drowning it in instruction overhead.

Whatever you did, record the compiler and the exact flags. A benchmark without its build flags is not a result.


C.6 — Isolating a Measurement From a Machine That Rescales Its Own Clocks

Your machine is not a stable instrument. It is a control system with its own goals — finish the work quickly, do not get hot, do not drain the battery — and it will change its clock underneath you several times during your experiment.

MechanismWhat it changesTimescale
DVFS governorThe core’s voltage/frequency operating pointMilliseconds
Boost behaviourA higher-than-nominal point, for a limited windowSeconds to tens of seconds
Thermal managementOperating points down as a temperature limit approachesTens of seconds to minutes
Power sourceA different policy entirely on battery, on most platformsInstant, when you unplug
Other tenantsAnother process — or on a shared host, another customerUnpredictable
Core migrationThe scheduler moving your thread to another core classMilliseconds
WorkbenchWhat you can controlWhat you must state
L, LinuxRead the governor and operating points under /sys/devices/system/cpu/cpu0/cpufreq; changing them needs administrator rights most students do not have and none needThe governor in effect, the power source, whether you changed anything
L, macOSEssentially nothing — the tunables are not exposed; powermetrics observes package power with elevated rightsThat the platform does not expose the controls, so you did not control them
B (browser cloud)Nothing. Shared host, unknown topology, invisible neighboursShared host, no placement control, no counter access, higher dispersion expected
D (optional device)Nothing an ordinary app can rely onDevice, charging state, case on or off, ambient, soak duration

On the default path you control almost nothing, and that is fine — this course grades a defensible number on the machine you have.

  1. Interleave, do not batch. A B A B A B …, never AAAA BBBB. Drift, a governor decision, or a neighbour waking hits both arms equally when interleaved and only the second when batched. Batched comparisons are where a “regression” comes from when nothing regressed.
  2. Repeat in the opposite order. If A B A B and B A B A disagree by more than your dispersion, order is a confound and you say so rather than picking the run you like.
  3. Report cycles as well as wall time where counters exist. A cycle count is clock-invariant; wall time is not. A run whose wall time moved while its cycle count did not is a run whose clock changed, not whose work changed.
  4. Fix the power source and name it. Never compare a mains run against a battery run. The commonest missing field in student submissions.
  5. Settle the machine, and say for how long. Ten minutes idle, lid open, in its final position — then do not move it, close it, or set it on a blanket halfway through.

Never run half an experiment on Monday and half on Wednesday and present the difference as a result. If one must span sessions, repeat a common control condition in each and report the control’s drift; if the control moved more than your effect, you measured the calendar.


C.7 — Controlling Placement When You Compare

Chapter 2 is why this exists: on a heterogeneous cluster all cores run the same instruction set and differ in microarchitecture. A thread that migrates mid-run has changed machines, and your median is now an average of two machines weighted by an accident of scheduling. The signature is bimodality — run times clustering into two groups rather than scattering around one centre — and a median hides it perfectly.

PlatformPlacement controlNotes
Linuxtaskset, sched_setaffinity, pthread_setaffinity_npAvailable to an ordinary user for their own processes on most systems. The one platform where you can genuinely pin
Apple platformsNone. No affinity API is exposedYou express intent with Quality-of-Service classes — QOS_CLASS_USER_INTERACTIVE, …_USER_INITIATED, …_UTILITY, …_BACKGROUND — and the system decides. A deliberate design decision, not an omission
AndroidPlatform-owned: cpusets groups such as top-app, foreground, background, plus uclamp hintsNot something an ordinary app controls
Workbench BNone, and the topology may be invisibleState it. Higher dispersion is a condition, not a failure

When you cannot pin: say so in the conditions line (“no placement control available on this platform” is complete and creditable); raise the repetition count so one migration cannot dominate the median; report min and max and comment on bimodality if you see two clumps; and do not attribute a difference to microarchitecture you could not control. If build A ran mostly on performance cores and build B mostly on efficiency cores, you measured the scheduler. Chapter 2’s migrate.cpp reports whether affinity control was available on your platform, which is what a good instrument does: it tells you what it could not do rather than silently pretending.

Pin the inputs too. The harness shuffles its chase order with std::mt19937(12345u) — a fixed seed, deliberately, so the order is identical between runs and machines. Same discipline in your own kernels: fixed seed, fixed input size, fixed file, all three recorded. A comparison whose arms saw different random data is not a comparison.


C.8 — The Statistics You Actually Need

Nothing here needs more than a sorted list and a calculator. That is deliberate.

C.8.1 — Repetitions: how many, and why odd

What you are timingRepetitionsReasoning
A fast kernel (well under a second per repetition)31 — the course defaultCheap enough to be free; one bad run cannot move the median
A slow kernel (seconds per repetition)11, or as many as your budget allows — say whichFewer is acceptable; an unstated count is not
Anything the capstone grades21 minimum, 31 preferredProject 8 sets the floor; the default is better
A sustained-load soakOne run, whose repetitions are the samples inside itC.9. Two or three with full cool-downs is stronger, and then report run-to-run spread

Use an odd count. The median of an odd sample is an observed run rather than an interpolation, so you can point at the row of raw output that produced it — with 31 repetitions, median_ns is exactly the sixteenth-fastest run. The iqr_ns is interpolated, because the 25th and 75th percentiles do not land on sample boundaries at n=31; that is fine, since dispersion is a band rather than a claim about one run.

Was 31 enough? Go to 51 and see whether the median moves by more than the difference you intend to claim. If the IQR is more than about a tenth of the median, extra repetitions will not help — the problem is the environment, not sampling. Quiet the machine, or state the noise as a limitation and shrink your claims.

C.8.2 — Median against mean

Five real timings of one kernel, in milliseconds: 12.4 12.5 12.4 12.6 24.9. Median 12.5; mean 15.0. The fifth run is not a property of your code but of whatever else the operating system did for eleven milliseconds.

Noise in a timing measurement is one-sided. A run can be delayed by a context switch, a page fault, a migration, an interrupt, a neighbour on a shared host. Nothing makes a run mysteriously faster than the machine can go. So the sample has a long right tail and the mean is dragged into exactly the events you are excluding. The median is not.

You are reportingUse
How long this operation takesMedian + IQR
How much work a fixed period deliversMedian of the per-sample rates + IQR
The total cost of N operationsMean × N — and say you used the mean, and why
The best caseNothing. Never the minimum on a thermally limited device

Row three is the honest exception — total energy over a shift, total time for N tasks — because the slow runs really happened and really cost you.

One reconciliation you will notice: throttle_analysis.py summarizes the soak’s delivered-work score with a mean over its burst and tail windows while summarizing clock and power with a median. That is defensible for the shipped dataset, whose plateaus are flat enough that the two agree. Your own soak has dropouts that drag a mean down, so report the median for your own soak (C.9.5), and if you quote the tool’s output, say its score summary is a mean.

C.8.3 — Dispersion, and the decimal point you cannot defend

The interquartile range is the 75th percentile minus the 25th. Half your runs fell inside that band — the whole definition and the whole interpretation. Not the standard deviation: it assumes a shape the data does not have, it is dragged by the same one-sided outliers the mean is, and it does not answer the reader’s actual question, if I ran this again, roughly where would I land? Report min and max too when you have room; the gap between the 75th percentile and the maximum is where the environment lives.

The rounding rule: quote the dispersion to one or two significant digits — never more — and round the median to that same decimal place.

RawDispersionReport as
12.4372 msIQR 0.31 ms12.4 ms, IQR 0.3
843.19 nsIQR 47 ns843 ns, IQR 47
8 400 459 nsIQR 1 372 458 ns8.4 ms, IQR 1.4 ms
0.075 47 ns/opIQR 0.003 2 ns/op0.0755 ns/op, IQR 0.0032

The instrument may print ten digits. What you publish is what you can defend.

C.8.4 — When a difference is too small to claim

A convention, chosen so two students reach the same verdict on the same data. Say in your report that you used it. Let A and B have medians mA, mB, interquartile ranges iA, iB, and d = |mA − mB|.

ConditionVerdictHow to write it
d > iA + iBClaim it. The middle halves are cleanly separated”B is faster by d (medians 12.4 vs 15.1 ms; IQR 0.3 and 0.4; n=31 each).”
max(iA, iB) < d ≤ iA + iBSuggestive, not established”B’s median is lower by d, but the interquartile ranges overlap; I do not claim a difference. What would settle it: ⟨more repetitions / a quieter machine / a larger problem size⟩.”
d ≤ max(iA, iB)Do not claim it”No difference I can resolve with this instrument.”

This is not a statistical test. Your repetitions are not independent draws from a stationary distribution — the machine drifts thermally, the governor’s state carries between repetitions, a background process affects consecutive runs together — and those are precisely the assumptions a t-test or confidence interval needs. Computing one anyway produces the appearance of rigour and none of the substance; if a reviewer asks for a p-value, explaining why the data cannot support one is the stronger answer.

“No difference I can resolve” is a result — often the most useful sentence in a report, because it stops an organization shipping a change on the strength of noise. And percentages need a base: write B is 1.22× A, or 22% below A, name the direction, and name the metric before the winner.

C.8.5 — The honest way to write a result

Long form — measurements.xlsx:

matmul ikj, N=512
  12.4 ms, IQR 0.3, min 12.1, max 19.8, n=31 (warm-up 3)
  laptop, 8 logical CPUs (heterogeneity unknown), macOS
  clang++ -O2 -std=c++17
  mains power, idle 10 min before, ambient ~22 °C, hard desk, nothing else running
  no placement control available on this platform
  instrument: bench_harness.cpp, steady_clock
  provenance: MEASURED

Short form — a sentence of report.docx: Reordering from ijk to ikj cut the median from 41.7 ms to 12.4 ms — medians of 31 repetitions after 3 discarded, IQR 0.9 and 0.3, on mains power after ten minutes idle, clang++ -O2. Checksums identical.

Spoken form — the briefing: Three point four times faster — median of thirty-one runs, plugged in, after a ten-minute idle.

The conditions travel with the number, and in speech in the same breath, because there is no footnote in a spoken sentence.

Coach’s Note — Students ask how much of this a lab really needs. Wrong question. The right one: could a stranger with your machine reproduce this row from the log alone? Every field above exists because somebody once could not, and the missing field was always the boring one — the power source, the flags, the browser playing video. Write the boring fields. They are the only reason anyone believes the interesting ones.


C.9 — The Sustained-Load (Thermal Soak) Protocol

Every thermal claim in this book is graded against this protocol. Project 5 requires you to run it on hardware you have; Project 8 requires a stated thermal condition on every number. It is written to be unambiguous, so two students on two laptops produce comparable numbers. Where a threshold looks arbitrary, it is: the values were chosen so everyone agrees, not because physics selected them. Changing one is allowed; changing one silently is not. No phone is needed — laptops throttle, cloud instances throttle, and the shape is the same.

C.9.1 — Before you start: write it down

In measurements.xlsx, before the first run: the duration, the sampling interval, the opening and tail windows, the onset threshold, and your prediction — will the rate fall, at roughly what second, and to what fraction of its opening value. Two committed numbers. Choosing the tail window after seeing which value flatters your conclusion is the exact failure Chapter 8 exists to prevent, and it is invisible in a finished report. The defence is that you wrote it first.

C.9.2 — The load

RequirementSpecification
WorkersOne per logical CPU. State the count and the command that produced it
Work unitA fixed chunk calibrated so one chunk takes roughly 0.5–1.0 s on your machine. Rep 8 of Chapter 5’s reps is the reference implementation; calibrate before running
CharacterCompute-bound. Not I/O bound, not network dependent
StabilityDo not change the load, worker count, or chunk mid-run. Changing any makes a new experiment with a new log entry

Any load you can describe precisely enough for a stranger to reproduce is acceptable — a video encode, a compile in a loop, a SIMD kernel. “I ran some benchmarks” is not.

C.9.3 — Duration, sampling, cool-down

ParameterValueWhy this value
Pre-run settle≥ 10 minutes at idle, machine in its final physical positionThe run must start from a known thermal state
Run duration≥ 600 s (10 min); 900 s (15 min) preferred600 s is the graded floor; 900 s matches the window of the book’s thermal-soak.csv, so the shapes are directly comparable
Sampling interval10 s, fixedMatches the shipped dataset and the reference probe; ≥ 60 samples in a minimum-length run
Opening windowFirst 30 s (samples at ≈10, 20, 30 s), excluding any t = 0 sampleLong enough to average out one bad chunk, short enough to still be inside the burst on a laptop that throttles early
Tail windowLast 300 sMatches throttle_analysis.py’s default --tail 300
Cool-down between runs≥ the run length, at idle, same positionA run started warm is not a repetition. It is a different experiment
Runs1 acceptable at Normal tier; 2–3 with full cool-downs is stronger, and then report run-to-run spread

One reconciliation you must know. throttle_analysis.py defaults to --window 60, because the shipped dataset’s burst lasts about seventy seconds. Your laptop may begin throttling at forty seconds, in which case a sixty-second opening window already contains throttled samples and understates your peak. That is why this protocol uses thirty. If you feed your own log to the tool, pass --window 30 so the two agree, and record that you did.

C.9.4 — What to record

Emit a CSV with exactly these six columns, in this order — the schema throttle_analysis.py reads, so your own log can be analysed with the shipped tool rather than by hand:

t_seconds,clock_mhz,power_w,soc_temp_c,skin_temp_c,score
ColumnRequired?What goes in it
t_secondsYesSeconds since the run started
scoreYesYour delivered-work index: chunks/s, iterations/s, frames/s, tokens/s. Raw units are fine — the sustained/peak ratio is scale-invariant
clock_mhzIf availableCore frequency, if your platform exposes it
power_wIf availablePackage or battery draw, if exposed
soc_temp_cIf availableDie or package temperature
skin_temp_cIf availableChassis or surface temperature. On a laptop, usually unavailable — say so

Two warnings about placeholders. The loader converts all six columns to floating point, so every column must be present and numeric even when you have nothing for it. And throttle onset is derived from the clock column — the first row whose clock is below the run’s peak clock — so a constant in clock_mhz makes the tool correctly report “throttle onset: none observed — the run never left its peak point” even when your delivered rate collapsed. That is the tool saying it had no clock data. Find the onset from score by hand using C.9.5, and say in the log which columns were placeholders. A placeholder must never appear in your report as an observation.

Everything that is not a column goes in # comment lines at the top of the CSV — the loader skips them — and in measurements.xlsx: the machine, OS and logical CPU count; the power source; the ambient temperature and how you got it; the physical setup (surface, lid, case, external cooling); what else was running and whether the host is shared; the pre-run idle duration; and the load’s worker count, chunk size and calibrated chunk time.

C.9.5 — The three numbers, defined exactly

Report peak, knee and sustained separately. They are three different claims, and collapsing them into one is the whole failure this protocol prevents.

QuantityDefinition
Peak (opening) rate, PThe median of the score samples with t ≤ 30 s, excluding any t = 0 sample
Throttle onset, t_onsetThe first sample time at which score < 0.95 × P and the following two samples are also below 0.95 × P. A single dip is not an onset
Stabilisation, t_stableThe first sample after t_onset at which three consecutive samples differ from one another by less than 0.03 × P. This ends the knee
Sustained rate, SThe median of the score samples in the last 300 s
Sustained/peak ratioS / P, to two decimal places
Sustained dispersionThe IQR of the tail samples, as a percentage of S

Report all six. A soak reported as one number has thrown away the only thing it was run to find out. The knee’s duration is informative too: short means the platform stepped hard and settled, long means it hunted its way down.

The thresholds — 30 s, 300 s, 0.95, 0.03, three consecutive samples — are conventions, so your ratio and your classmate’s mean the same thing. If your machine’s behaviour makes one wrong, change it, state the new value and why, and report under both if you can. Silently choosing the threshold that flatters your curve is what this course fails people for.

C.9.6 — Reading the shape

Every soak curve has three phases, named in Chapter 5. The book’s thermal-soak.csvsynthetic teaching data, modeled on published behaviour and not a measurement of any product — is the reference shape:

PhaseIn the shipped datasetWhat is happening
The burst0–70 s: full clock, full power, nothing throttlingThe chassis has heat capacity. The device is spending a thermal loan it has not had to repay
The knee80–130 s: clock falls from peak to floor in about fifty secondsSkin temperature approaches the limit; the governor steps operating points down
The sustained state130 s onward: power settles, temperature hovers, the clock hunts between adjacent pointsThis is the machine. Everything before was a transient

Running throttle_analysis.py on that dataset prints the burst, an onset at t = 80 s, a sustained clock and power far below peak, and an “honesty line” contrasting what a sixty-second benchmark would have claimed with what fifteen minutes delivered. Never cite that dataset as a measurement of anything — cite your own soak, and the dataset only as a modeled shape, per Appendix B.

C.9.7 — The negative result

“My machine did not throttle in ten minutes” is a valid result, graded the same. Report P, S, the ratio (near 1.00) and the tail IQR; state “no onset observed under the stated threshold within the stated window”; give the two most plausible reasons — genuine cooling headroom at this load, or a load too light to reach the envelope — and say what would distinguish them: raise the worker count or the chunk weight, or extend the run, and which you would do first.

On a shared cloud host, add one more sentence and it is not optional: you cannot separate your own throttling from a neighbour’s load, because you cannot see the neighbour. Concealing that is the only thing that would weaken the report. What earns nothing is quietly re-running until the curve looks good — if you ran the soak four times and are reporting the fourth, the log says so and reports all four.

C.9.8 — The optional device path (Workbench D)

Never required; every graded requirement in this book is completable without a device. With an Android device and the debug bridge, adb shell dumpsys thermalservice shows the platform’s own view. The application-facing surface is PowerManager.getCurrentThermalStatus() and addThermalStatusListener(), reporting the ladder THERMAL_STATUS_NONE, LIGHT, MODERATE, SEVERE, CRITICAL, EMERGENCY, SHUTDOWN; plus getThermalHeadroom(int forecastSeconds), a normalized forecast where 1.0 is the throttling threshold. Log the status alongside your score samples: a chart showing the score falling and the status climbing has explained itself, where one showing only the falling score has reported a mystery. Documentation is under https://developer.android.com/.

Record the device, ambient, charging state, whether it was in a case, and the soak duration — a phone in a case on a warm desk is a different thermal system from the same phone in a hand. And never report a device’s numbers as a general fact about that product: you have one measurement, of one unit, under one set of conditions.

C.9.9 — Comparing two soaks

Everything in C.9.3 identical for both, plus the same ambient (or state the difference and treat it as a confound), the same physical position and surface, the same power source, a full cool-down between them, and interleave if you can — A, cool-down, B, cool-down, A again. If the two A runs disagree by more than your tail IQR, the day drifted and you must say so. Then compare the three numbers separately: two devices can share a peak and have completely different sustained states, and reporting only the ratio, or only the peak, discards the finding.


C.10 — Reporting: What Must Appear Beside Every Number

#FieldExample
1What was measured, precisely enough to repeat”median wall time of one ikj matrix multiply, N=512”
2The machine”laptop, 8 logical CPUs, heterogeneity unknown, macOS”
3The build — compiler and exact flagsclang++ -O2 -std=c++17
4The conditions — power, thermal state, ambient, other load”mains, idle 10 min before, ~22 °C, hard desk, nothing else running”
5Placement control, or its absence”no affinity API on this platform”
6The instrumentbench_harness.cpp, steady_clock
7n, split into warm-up and timed”n=31, warm-up 3”
8The summary statistic, named”median”
9The dispersion”IQR 0.3 ms (min 12.1, max 19.8)“
10The provenance tagMEASURED
TagMeansRequires
MEASUREDYou produced it on hardware you had access toA row in measurements.xlsx with all ten fields, and the raw output committed
DERIVEDArithmetic you performed on other numbersThe inputs, each with their own tag, and the arithmetic shown — e.g. bytes/s = MT/s × bus bytes
CITEDIt came from a primary source you personally openedA vendor specification page, an architecture reference manual, a standards document, or a named published paper — plus the URL and the retrieval date
MODELEDIt came from a synthetic dataset shipped with this bookThe dataset named, its synthetic status stated in the sentence where you use it, and what that limits

A number that is none of the four does not belong in the document. Delete it — always correct, never expensive. “The vendor does not publish the last-level cache size” is a true, useful sentence that earns credit; an invented figure is an integrity failure rather than a deduction, per Appendix D.

Charts. The measurement window and the thermal condition go on the chart, not in a caption and not in a spoken aside — charts get screenshotted out of reports and the conditions must travel with the pixels. Label axes and units. Anything from a synthetic dataset carries the word synthetic on the chart. If the y-axis does not start at zero, say so on the chart.

The flattering-choice disclosure. Every investigation contains one decision that helped its conclusion — a measurement window, a summary statistic, a cool-down policy, which build, which cores, which workload got weighted most. It is rarely wrong, which is what makes it dangerous. Name it before your reader does: “The choice that most helps my conclusion is ⟨X⟩; it was defensible because ⟨Y⟩; reversed, my headline becomes ⟨Z⟩, and my conclusion ⟨does / does not⟩ survive.” It costs nothing on any rubric, it is a named line on several, and it is the difference between a report and an advertisement.


C.11 — The Templates

Six document stems, fixed course-wide. Do not invent a seventh: if your work needs another document, it is a section of report.docx. The site renders these six into the formats your section submits, so renaming them breaks the submission.

FileWhat it isWhere
report.docxThe main write-upEvery lab, the practical, the capstone
measurements.xlsxThe measurement log, tabularEvery lab that measures
soc-architecture-review.docxThe running SoC reviewChapter 1Chapter 8
placement-decision.docxThe placement table and its defenseChapter 4, Chapter 8
threat-model-report.docxAssets, adversaries, mechanisms, residual riskChapter 7
ai-usage.txtThe honest disclosureEvery deliverable

Each comes three ways: blank to copy, worked with a small illustrative example, and badly — the version you are most likely to write under deadline, and the one worth studying hardest. Worked examples use your own machine’s measurements or the book’s fictional, synthetic datasets, so no sentence here can be mistaken for a claim about anybody’s real silicon.


C.11.1 — report.docx

Blank

# ⟨Project N — Title⟩

**Tier targeted:**   Normal / Medium / Hard
**Workbench:**       L / B / D — and what that limited
**Machine:**         ⟨CPU, logical CPUs, RAM, OS⟩
**Build:**           ⟨compiler and exact flags⟩
**Conditions:**      ⟨power · thermal state · ambient · other load · shared host?⟩
**Headline result:** ⟨metric named first, then the finding, then the conditions⟩
**Confidence:**      high / medium / low — and one line on why

## 1. The question       ⟨and what is out of scope⟩
## 2. Method             ⟨what you ran, warm-up + timed reps, instrument⟩
## 3. Results            ⟨median, dispersion, n, conditions, tag — every number⟩
## 4. Interpretation — mechanism, not symptom   ⟨tied to a chapter by number⟩
## 5. The Four Questions
### Performance
### Energy
### Thermals
### Placement
## 6. Provenance summary | Category | Count | Notes |   ⟨MEASURED / DERIVED / CITED / MODELED⟩
## 7. Limitations, and the flattering choice
## 8. What I could not measure, and what I would need
⟨quantity · instrument or access required · the experiment · the question it settles⟩
## 9. Conclusion   ⟨metric before finding; tradeoff named; observable reversal condition⟩
## 10. Citations   | Figure | Value | Source (URL) | Retrieved | Tag |

Workeda small illustrative extract, on the student’s own machine

# Project 3 — The Memory Wall Lab

**Workbench:**  L. No counter access, so no MPKI this week.
**Machine:**    laptop, 8 logical CPUs (heterogeneity not reported), 16 GB, macOS
**Build:**      clang++ -O2 -std=c++17
**Conditions:** mains · idle 10 min before each sweep · ambient ~22 °C · hard desk
**Headline:**   Measured as median ns/access over 5 internal trials × 3 runs, the
                dependent chase costs 24× the strided walk at 64 MiB — the gap is
                prefetching and memory-level parallelism, not raw memory speed.
**Confidence:** medium — three clear plateaus, but I cannot distinguish the third
                from a system-level cache without associativity evidence.

## 3. Results
| Experiment | Working set | Median | Dispersion | n | Tag |
|---|---|---|---|---|---|
| cache_walk, stride 128 B | 64 MiB | 4.05 ns/access | range 3.91–4.40 over 3 runs | 5×3 | MEASURED |
| pointer_chase | 64 MiB | 96.2 ns/access | range 91.0–104.8 | 5×3 | MEASURED |
| matmul ijk, N=512 | — | 41.7 ms | IQR 0.9 ms | 31 (warm-up 3) | MEASURED |
| matmul ikj, N=512 | — | 12.4 ms | IQR 0.3 ms | 31 (warm-up 3) | MEASURED |

Checksums for both matmul orders agree to six decimals (`raw/matmul.txt`); raw
sweeps committed unmodified. The ijk/ikj difference is 29.3 ms against IQRs of
0.9 and 0.3 — larger than their sum — so I claim it: **ikj is 3.4× faster.**

## 7. Limitations, and the flattering choice
I cannot say whether the third plateau is an L3 or a system-level cache, and I
did not read the assembly, so I can name locality as a mechanism but cannot
apportion the win between locality and vectorization.

The choice that most helps my conclusion is building at -O2 rather than -O3. It
was defensible — Chapter 3 warns some compilers interchange loops at -O3 and
erase the effect — but it is the best case for my argument. At -O3 the ratio
falls to 1.9×: the direction survives, the magnitude does not. Both are logged.

## 8. What I could not measure, and what I would need
Per-level miss rates: no counter access here. On Linux with `perf` I would
capture cycles, instructions, L1-dcache-load-misses and LLC-load-misses across
each working-set size and compute MPKI, confirming my boundary hypotheses
directly rather than by inference from timing.

Badly

# Project 3

I ran the cache walk and the pointer chase and matmul.

The cache walk showed a nice staircase with L1 at 32K, L2 at 512K and L3 at 8MB.
The pointer chase was much slower, about 100ns. The ikj version was 3.4x faster
than ijk, which shows that locality matters.

Phones would be slower because they have less cache. The L2 on a phone is
usually about 4MB per core.

Conclusion: memory is the bottleneck.
The failureWhat it costs
No conditions anywhere — machine, compiler, flags, power state, thermal stateThe whole measurements.xlsx line; nobody can reproduce a row, so no number here is reportable
No repetition count, no dispersion (“about 100ns”)The median-and-dispersion line — and 3.4× becomes unclaimable, since nothing says whether the dispersion was 0.3 or 3.0
Cache sizes stated as fact, not as hypotheses with named evidence and a confidenceThe hierarchy-inference line: readings from a datasheet nobody opened
”The L2 on a phone is usually about 4MB per core”Uncited external figure — zero on its line; if it came from a model, an integrity failure rather than a deduction
No provenance tags; measured, cited and remembered figures share a paragraphThe provenance line — exactly the distinction Week 8 grades
”Conclusion: memory is the bottleneck”No metric, no condition, nothing falsifiable, nothing anyone could act on
No limitations, no flattering choice, no “what I could not measure”Three named lines at zero — and here, their absence is itself the diagnosis

The bad version is not lazy in the sense of being short. It is confident where the good one is specific: every sentence would pass a casual read, and not one could be checked.


C.11.2 — measurements.xlsx

The measurement log, the artifact this appendix is named for. Tabular, written as you go. Open it before your first run.

Blank

# Measurement Log — ⟨Project N⟩

## Environment
| Field | Value |
|---|---|
| Machine / OS | |
| Compiler + flags | |
| Workbench | L / B / D — shared host? |
| Placement control | available / not available on this platform |
| Counter access | available / not available |
| Power source | mains / battery |
| Ambient | ⟨°C, and how you know⟩ |
| Physical setup | ⟨surface, lid, case, position⟩ |
| Pre-run idle | ⟨minutes⟩ |
| Other load | ⟨what else was running⟩ |

## Predictions — recorded BEFORE any run
| # | Experiment | What I predict | Why |
|---|---|---|---|

## Runs
| # | What | Command | n (warm-up + timed) | Median | Dispersion | Min / Max | Conditions | Raw file | Tag |
|---|---|---|---|---|---|---|---|---|---|

## Soak runs
| # | Duration | Interval | P | t_onset | t_stable | S | S/P | Tail IQR | Conditions | Raw file |
|---|---|---|---|---|---|---|---|---|---|---|

## Notes and anomalies
⟨Everything that happened. A run you discarded and why. A machine that woke up.
A result you cannot explain. This is evidence of good faith, and it is read.⟩

Workedillustrative, soak section only

## Predictions — recorded BEFORE any run
| # | Experiment | What I predict | Why |
|---|---|---|---|
| P1 | Own soak, 10 min | falls, onset ~t=180 s, S/P ≈ 0.75 | fanned laptop: later and shallower than a phone |

## Soak runs
Protocol per Appendix C.9. Fixed **before** running: duration 600 s, interval
10 s, opening window 30 s, tail 300 s, onset threshold 0.95 P held 3 samples.

| # | Duration | Interval | P | t_onset | t_stable | S | S/P | Tail IQR | Conditions | Raw file |
|---|---|---|---|---|---|---|---|---|---|---|
| S1 | 600 s | 10 s | 2.41 chunks/s | 220 s | 300 s | 1.88 chunks/s | 0.78 | 0.06 (3.2% of S) | mains, 8 workers, ~22 °C, hard desk | raw/soak-1.csv |
| S2 | 600 s | 10 s | 2.39 chunks/s | 210 s | 290 s | 1.85 chunks/s | 0.77 | 0.05 (2.7% of S) | as S1, after 12 min cool-down | raw/soak-2.csv |

S/P differs by 0.01 across the two runs, inside the tail IQR, so I report
**S/P = 0.78, run-to-run spread ±0.01.** Prediction P1 was wrong on onset
(predicted 180 s, observed 210–220 s): more cooling headroom than I assumed.

## Notes and anomalies
- S1's sample at t=340 s dips to 1.42 chunks/s. It does not meet the
  three-consecutive-sample onset rule and I did not treat it as one. A system
  process ran then; I could not identify it.
- I discarded a first attempt at S1 because I closed the lid at t≈120 s out of
  habit. Recorded here rather than silently omitted.

Badly

# Measurements

cache_walk: 0.3 ns at 4K, 4 ns at 64M
chase: 100 ns
matmul ikj: 3 runs — 12.4, 12.6, 24.9 ms — average 16.6 ms
soak: throttled to about 78%

Ran on my laptop.
The failureWhat it costs
Bare single numbers — no n, no statistic named, no dispersionThe rubric line reads “median and dispersion and conditions, for every run.” Every row fails
A mean over three runs, one of them an outlier — 12.4, 12.6, 24.9 reported as “average 16.6”The worst single row here. Three repetitions cannot resolve an outlier from a result, and the mean is dragged 34% above every typical run. The median of those three is 12.6; the honest fix is 31 repetitions and a median with its IQR (C.8.1–C.8.2)
“Ran on my laptop.” No CPU, OS, compiler, flags, power source, thermal state, ambientThe environment block, and with it the stated bar: a stranger reproducing a row
No predictionsSeveral projects carry a named line for predictions recorded before the measurements — zero
”soak: throttled to about 78%“Collapses six required quantities — P, t_onset, t_stable, S, S/P, tail IQR — into one, with no duration, interval or windows. That 78% is incomparable to anybody else’s
No raw files referenced, no anomalies sectionRaw output is required; without it every figure is an assertion. The discarded run and the unexplained dip stay invisible

The tell of a bad log is that it was written afterwards, from memory: the numbers survived and every condition evaporated, because conditions exist only at the moment of the run.


C.11.3 — soc-architecture-review.docx

Opened in Project 1, extended as each chapter teaches a new block, finished in the capstone.

Blank

# SoC Architecture Review — ⟨part or product⟩

**Subject:** ⟨the part, or "designed part" for a Track 3 capstone⟩
**Workbench:** L / B / D   **Started:** Week 1   **Last extended:** Week ⟨N⟩
**Primary sources used:** ⟨count⟩   **Figures I could not source:** ⟨count⟩

## 1. Block inventory
| Block | What it is for | What it shares | What it contends for | Confirmed? | Source |
|---|---|---|---|---|---|
`Confirmed?` is `confirmed` (a primary source says this block is on this part) or
`inferred` (reasoning from the general shape of a mobile SoC). Both are fine.
Labelling an inference as a confirmation is not.⟩

## 2. CPU cluster configuration | Field | Value | Tag | Source |
⟨core classes and counts · cluster arrangement · private cache sizes⟩
## 3. Memory configuration      | Field | Value | Tag | Source |
⟨generation · interface width · data rate · peak bandwidth, DERIVED, with
`bytes/s = MT/s × bus bytes` shown⟩
## 4. Accelerator inventory     | Block | For | What it does NOT do | Tag | Source |
## 5. Storage and radios
## 6. The Four Questions, applied to ⟨N⟩ blocks  ⟨four sentences each, not adjectives⟩
## 7. Bottleneck predictions
⟨Per workload: which blocks run · which single shared resource becomes the
constraint and why that one · which of The Four Questions binds first and what a
user would observe · what would make you wrong, and what you would measure.⟩
## 8. What I could not find
| Figure I wanted | Why it matters | What kind of source would have it |
|---|---|---|

Workedbuilt on SoC-C, a fictional part from the synthetic soc-compare.csv

# SoC Architecture Review — SoC-C

**Subject:** SoC-C, a FICTIONAL part from soc-compare.csv, used only to show this
document's shape. Every figure below is MODELED. A real review names a real part
and cites vendor documentation.

## 3. Memory configuration
| Field | Value | Tag | Source |
|---|---|---|---|
| Interface width | 128 bits = 16 bytes | MODELED | soc-compare.csv |
| Data rate | 7500 MT/s | MODELED | soc-compare.csv |
| Theoretical peak bandwidth | **120 GB/s** | DERIVED | 7500e6 × 16 = 120e9 bytes/s |
| Private cache sizes | not specified by the dataset | — | — |

I will not supply the cache sizes the dataset does not carry. On a real part that
row reads either with a vendor citation or "not published."

The comparison the arithmetic exposes: SoC-A in the same file runs a *higher*
data rate (8533 MT/s) on a 64-bit bus and reaches 68.3 GB/s. SoC-C is not using
newer memory — it is using **twice the width**. Width buys bandwidth, and costs
package pins, board area, controller area and power, which is why a phone-class
part usually does not have it. Arithmetic, not a claim.

## 7. Bottleneck predictions
**Workload: sustained on-device token generation.**
- Blocks running: NPU (or GPU) streaming weights · memory controller · DRAM ·
  CPU orchestrating · display if the UI is live.
- Constraint: **DRAM bandwidth**, then the thermal envelope. Chapter 3's ceiling
  is `tokens/s ≤ bandwidth ÷ weight bytes`, and it does not move when you add
  multiply-accumulate units.
- Binds first: **thermals**. This part's sustained/peak ratio in the dataset is
  0.85 and its sustained power is the highest of the four — a *form-factor
  statement*, not a virtue. Nothing dissipating that much sits on a phone-class
  thermal path.
- I'd be wrong if the runtime is dominated by per-token overhead rather than
  weight streaming. To find out: measure tokens/s at two quantization levels —
  halving bytes-per-weight should roughly double a bandwidth-bound rate and
  barely move an overhead-bound one.

## 8. What I could not find
| Figure I wanted | Why it matters | What kind of source would have it |
|---|---|---|
| Last-level / system cache size | Decides how much traffic reaches DRAM at all | Vendor architecture documentation; frequently not published |
| Sustained thermal envelope | Every claim in §7 depends on it | Vendor thermal guidance, or my own soak on real hardware |

Badly

# SoC Review

The SoC has a CPU with 8 cores at up to 3.2 GHz, a GPU, an NPU rated at 45 TOPS,
16 MB of system cache, and LPDDR5X memory with 77 GB/s of bandwidth. It is built
on a 3nm process.

The GPU is very powerful and the NPU makes AI fast. The main bottleneck will be
the CPU when running heavy applications.
The failureWhat it costs
Six specific figures, zero citations — clock, core count, TOPS, cache size, bandwidth, process nodeEach scores zero on the citation line; a set this tidy and unsourced usually came from a language model, making it an integrity failure rather than a deduction
A TOPS rating quoted bareAt what precision, at what utilization, sustained how long, with what memory system. A number without a datatype is not a number
Bandwidth quoted rather than derivedThe rubric rewards showing MT/s × bus bytes; quoting a bandwidth you cannot source is what the arithmetic rule prevents
No inventory table, no Confirmed? columnThe block-inventory and honest-inference lines — nothing separates what was verified from what was assumed
”The GPU is very powerful and the NPU makes AI fast”Adjectives where the rubric asks what it is for, what it shares, what it contends for
”The main bottleneck will be the CPU”Names a block, not a shared resource — and real mobile bottlenecks are contention. No workload, no symptom, no falsifier
No “What I could not find” sectionA named line, and its absence here means nothing was looked for, so nothing was missing

Note what the bad review has that the good one does not: more numbers. Six confident unsourced figures are worth strictly less than two cited figures and four honest “not published” rows, because the second can be built on and the first cannot.


C.11.4 — placement-decision.docx

The placement table: workload → chosen processor → why → what it costs. The rule it applies — the cheapest processor that meets the deadline — comes from Chapter 2 and Chapter 4; this is the format, not the reasoning.

Blank

# Placement Decision — ⟨project / product⟩

**The requirement, stated before any choosing:**
⟨The latency budget, or per-workload budgets, justified from the product. A
60 fps frame budget is 1000/60 ≈ 16.7 ms. A wake-word response is a different
number. A background draft is a third. Say which and why.⟩
**Energy ceiling, if any:** ⟨and where it comes from⟩
**Data provenance:** ⟨name the dataset; if synthetic, say so HERE, not in a footnote⟩

| Workload | Processor | Why (and what it beat) | What it costs |
|---|---|---|---|

⟨Rules, all graded: every workload gets a row, including any whose answer is
"none of these"; Why names at least one **rejected** alternative and why it lost;
What-it-costs is a **number with a unit**, never an adjective; and at least one
row shows energy scaled to a real **duty cycle** — per-unit energy × how often it
runs, multiplication shown.⟩

## Defense   ⟨one paragraph per row, under all Four Questions⟩
## What this table does not model
## What I would measure to confirm it
⟨quantity · processor · tool · baseline · the result that would change a row⟩

Workedillustrative, using the six workloads of the synthetic placement-bench.csv

# Placement Decision — field clinic handheld

**The requirement, stated before any choosing:** a split budget. Anything the
clinician waits on: **50 ms**. Background work: **2000 ms**. The 50 ms is the
product's responsiveness target for a hand-held capture flow — looser than a
16.7 ms frame budget because nothing here is a continuous animation.
**Data provenance:** every figure below is **MODELED**, from placement-bench.csv
— synthetic teaching data, not a measurement of any product. The *ratios* are the
lesson; the absolutes are not a claim.

| Workload | Processor | Why (and what it beat) | What it costs |
|---|---|---|---|
| `image_classification` | NPU | 6.5 ms, lowest energy. Beat GPU (11.5 ms, 26.0 mJ — 3.7× the joules) and CPU (48.0 ms, over budget) | 7.0 mJ per inference |
| `video_encode_4k30_1s` | **None — needs a block absent from the dataset** | Best listed option is the DSP at 1450 ms for one second of video: it cannot keep up with real time, so capture is impossible. The NPU is not slow but architecturally unable — entropy coding is serial and bit-exact, not a tensor graph | A fixed-function encoder, costing die area that is dark whenever nobody records |
| `game_frame` | GPU | 9.4 ms, inside a 16.7 ms budget. NPU and DSP UNSUPPORTED — no rasterizer, no texture units | 44.0 mJ/frame → **at 60 fps: 44.0 × 60 = 2640 mJ/s = 2.64 W** for rendering alone — the arithmetic that decides whether this product can run a 3D viewer |
| `wake_word` | DSP (always-on sensor hub) | 2.4 ms, **0.06 mJ**. Beat the NPU, faster at 1.9 ms but 0.42 mJ — 7× the energy, because the main-SoC NPU forces the whole SoC out of its deepest idle state. The latency difference is invisible; the energy difference runs all day | 0.06 mJ per 1 s window → **86 400 × 0.06 = 5184 mJ ≈ 5.2 J per 24 h** |
| `llm_token_gen` | NPU | 24.0 ms, 9.5 mJ. GPU is faster (18.0 ms) at 21.0 mJ — 2.2× the joules for 6 ms nobody perceives | 9.5 mJ/token → a 200-token note is 1.9 J |
| `photo_pipeline` | DSP | 45.0 ms, 31.0 mJ. **The NPU is the trap**: 190 ms and 240 mJ, because two custom operators are unsupported, the graph splits, and three CPU round trips follow — **4.2× the latency and 7.7× the energy** | 31.0 mJ per capture |

## What this table does not model
Every figure is a per-unit number with no statement of thermal steady state.
`game_frame` is the most exposed — 2.64 W for rendering alone, continuously, on a
sealed handheld is a thermal event, and the placement that is right for one frame
may be wrong in the tenth minute. It also models each workload alone; nothing
accounts for two contending for one memory controller.

## What I would measure to confirm it
For `photo_pipeline`: run the graph on the accelerator path and on a CPU-only
control over the same 200 inputs, report median and IQR per Appendix C, and ask
the runtime which operators it actually placed. If more than one falls back, the
row stays on the DSP. That question — *which operators actually ran there* — is
the one an activity meter will never answer.

Badly

# Placement Decisions

| Workload | Processor |
|---|---|
| image_classification | NPU |
| video_encode | NPU |
| game_frame | GPU |
| wake_word | NPU |
| llm_token_gen | NPU |
| photo_pipeline | NPU |

The NPU is the AI accelerator so AI workloads go there. The GPU handles graphics.
Every workload above meets its latency target, so this assignment is optimal.
The failureWhat it costs
No stated requirement — no latency budget anywhereA placement table without a budget is not wrong, it is meaningless: nothing for it to be right or wrong about. The largest single deduction available
Defended in milliseconds only, with no energy anywhere in the documentThe whole point of the exercise. This is a battery-limited handheld: “meets its latency target” is a necessary condition, not a decision procedure. The rule is the cheapest processor that meets the deadline, and without joules there is no cheapest — half these rows are the fastest option and the wrong one
No rejected alternatives, no costs — two columns where the rubric names fourThe placement-table and energy-arithmetic lines together. A row with no loser is preference with a table around it
photo_pipeline → NPUThe operator-fallback trap the dataset exists to contain: 4.2× the latency and 7.7× the energy of the right answer, against a named rubric line
video_encode → NPUNot suboptimal — architecturally impossible, and the dataset marks it UNSUPPORTED with the reason. The notes column is where the engineering is
wake_word → NPUThe row where the fast answer loses on energy by 7×, all shift long — the row the rubric explicitly asks for
”…so this assignment is optimal”A claim the data does not support. Optimal against what objective, under what constraint, beating what? Nothing here was compared to anything
No duty-cycle arithmetic, no synthetic-data statement, no limitsThree more named lines at zero. A per-frame figure means nothing until you multiply by frames per second

Four of six rows say NPU, and the prose says why: “the NPU is the AI accelerator so AI workloads go there.” That is placement by the name of the block rather than the shape of the work — the exact reasoning Chapter 4 exists to break.


C.11.5 — threat-model-report.docx

Chapter 7’s deliverable. The graded core is the residual risk column — what each mechanism does not cover. A test of nerve rather than knowledge: you already know the answers, and the difficulty is writing them into a document somebody is paying for.

Blank

# Threat Model — ⟨device⟩ in ⟨scenario⟩

**Bad outcome for this user:** ⟨one sentence — it differs by scenario⟩
**Adversaries modeled:** ⟨three or more⟩   **Assets:** ⟨five or more⟩
**The single largest residual risk:** ⟨one sentence, no hedging⟩
**Verdict:** ⟨one sentence, to the decision-maker⟩
**Synthetic data used:** ⟨name it⟩

## 1. The engagement  ⟨what the device must do; what counts as a bad outcome⟩
## 2. Assets
| Asset | Why it has value **to the adversary** | What its exposure would cost |
|---|---|---|
⟨At least one asset is not data — a capability, an identity, or a credential that
lets the holder act as the device.⟩
## 3. Adversaries
| Adversary | Capabilities (explicit) | Not capable of |
|---|---|---|
⟨At least one has physical possession. That is what makes this a *mobile* threat
model rather than a server one.⟩
## 4. Mechanisms and residual risk
| Asset | Defending mechanism(s) | Which adversary it stops | **Residual risk — what it does not cover** |
|---|---|---|---|
⟨Every residual-risk cell is specific and falsifiable, naming *this* asset,
*this* mechanism, *this* adversary. "Some risk remains" earns zero.⟩
## 5. Chain analysis  ⟨injected faults, raw output, attacker gain, data provenance⟩
## 6. Measurement     ⟨the side-channel measurement per Appendix C, tied to this model⟩
## 7. Verdict         ⟨one sentence. If it takes three, you have not decided⟩

Workedillustrative and structural; no product named, no figure invented

# Threat Model — warehouse wrist scanner, fleet deployment

**Bad outcome for this user:** a unit that goes home in a coat pocket becomes a
working credential against the warehouse management system, and nobody notices
for a week.

## 2. Assets (extract)
| Asset | Why it has value **to the adversary** | What its exposure would cost |
|---|---|---|
| Fleet credential (**not data — a capability**) | It authenticates *as the fleet*; possession is the ability to act | Fleet-wide re-issue; unbounded fraudulent transactions until detected |
| Scan history on the device | Reveals inventory movement and staffing patterns | Commercial; low per unit, high in aggregate |

## 4. Mechanisms and residual risk (extract)
| Asset | Defending mechanism(s) | Which adversary it stops | **Residual risk — what it does not cover** |
|---|---|---|---|
| Fleet credential | Hardware-backed key store; the application asks for an *operation*, never for the key | The opportunist with an hour and ordinary tools — they cannot extract key material | **It does not stop that adversary from using the credential.** The key never leaves the secure subsystem, and a possessor of an unlocked, un-revoked unit can still ask it to sign. The defence against *use* is revocation, an operational control with a detection delay we have not measured |
| Scan history | Full-disk encryption, key released after user authentication | An adversary acquiring a powered-off unit | Does not protect a unit taken while unlocked and in use, nor data already synced to the WMS, nor backups |
| Device identity | Verified boot with rollback protection; attestation to the server | An adversary installing a modified or older image | Verified boot proves what *started*. It does not prevent a live exploit against a running, verified system from acting with that identity for as long as the session lasts |

## 7. Verdict
If a unit leaves the site and is not recovered, its stored scan history is
protected against everything short of die-level attack, and its fleet credential
is protected against extraction but **not** against use until revocation
completes — so the security of this deployment is the speed of your revocation
process, not the strength of the silicon.

Badly

# Threat Model

The device uses TrustZone, secure boot, and encryption, so it is secure.
Attackers include hackers and thieves. Data is protected by hardware.
Some risk always remains but the device implements multiple layers of
hardware-backed protection.
The failureWhat it costs
”Attackers include hackers and thieves”The rubric wants three or more adversaries with explicit capability lists, one with physical possession. “A person with possession for one hour, ordinary tools, no die-level equipment” is an adversary you can argue with; “hackers” is not
No asset table, so no non-data assetThe asset line — and it guarantees the report misses the credential-as-capability case, where the real loss lives
”Some risk always remains”The graded core, earning zero. A residual-risk cell names this asset, this mechanism, and what this adversary can still do
”So it is secure”Secure against whom, holding what, for how long. Write the narrow sentence instead, every time
Mechanisms listed with no asset mapped to themThe matrix is the deliverable; a list of mechanism names is the brochure the client already read
No chain analysis, no measurement, no synthetic-data statementThree named lines at zero, including the only place the author’s own evidence appears
No one-sentence verdictThe only part the person paying will remember. Three sentences means you have not decided; none means you have not been useful

The failure here is writing toward reassurance, the professional temptation of the whole discipline. Your value is not making people feel safe. It is telling them accurately what they are safe from, which is the only information that lets them decide anything.


C.11.6 — ai-usage.txt

Appendix D owns the policy and defines the note; this is the shape it takes on the page. Graded on honesty and specificity, not abstinence — “I did not use AI” is complete and acceptable when true. The rule, in five words: use it to explain, never to source.

Blank

# AI Usage — ⟨Project N⟩

**Models used:** ⟨name and version, or "none"⟩

**What I used them for:** ⟨specific — "explain why the IQR is preferred to the
standard deviation for timing data"; "review the prose in §4"; "check my algebra"⟩

**What I did NOT use them for:** No figure in this submission came from a model.
Every number is measured by me and logged in `measurements.xlsx`, derived by
arithmetic shown in `report.docx`, cited to a primary source I opened, or drawn
from a dataset shipped with this book and labelled synthetic where used.

**Where I overrode it, or caught it being wrong:** ⟨the part people skip, and the
part read most carefully. Be specific.⟩

**Signed:** ⟨your name⟩

Worked

# AI Usage — Project 3

**Models used:** ⟨assistant name and version⟩.

**What I used them for:** three things, all explanatory. (1) I did not follow why
a power-of-two stride is pathological, and asked for a walk-through of set
indexing; the explanation matched §3.3. (2) I asked it to review §4 for clarity
and cut two paragraphs on its suggestion. (3) I asked it to check my Little's Law
arithmetic, which it did correctly.

**What I did NOT use them for:** no figure here came from a model. Every number
is measured by me and logged with its conditions, derived by arithmetic shown in
the report, or drawn from `model-memory.csv`, labelled synthetic where used.

**Where I overrode it:** as an experiment for Rep 10 I asked for the L2 cache
size of a named shipping part. It answered with a specific figure, confidently,
with a plausible generation name attached. I could not find that figure on the
vendor's own page, and the vendor does not appear to publish it at all. I
therefore report "not published" and use my own measured plateau as evidence. I
could not have told from its tone that the answer was unreliable — which is the
point of the rep.

**Signed:** ⟨your name⟩

Badly

# AI Usage

I used AI for research and to help with some of the writing.
The failureWhat it costs
”For research”The one phrase the policy is written against. It straddles explaining and sourcing without saying which — it reads as a disclosure and functions as a concealment
No model named, no versionA disclosure that does not say what you disclosed is not one
No statement that no figure came from a modelRequired; its absence in a report containing external figures invites the integrity check rather than satisfying it
Nothing about overridesThe most-read line. “I accepted everything it said” is worse than a specific override; nothing at all is worse than both
”Help with some of the writing”Reviewing prose is permitted and worth stating plainly; generating the argument is not. The sentence leaves both readings available, which is why it earns nothing

An honest disclosure protects you. A vague one makes a grader guess about your process — and a grader guessing about your process will read your numbers the same way.


C.12 — The Pre-Submission Audit

Ninety seconds per deliverable, every week, and the capstone will not surprise you.

measurements.xlsx

  • Every row has a median, a dispersion figure, and a repetition count split into warm-up and timed.
  • Every row states the machine, the build flags, the power source, and the thermal condition.
  • Every row names its raw output file, and those files are committed unmodified.
  • Predictions are recorded, unedited, before the results they precede.
  • Every soak reports P, t_onset, t_stable, S, S/P and the tail IQR — six numbers, not one.
  • The anomalies section names every run you discarded and why.
  • A stranger with your machine could reproduce any row from the log alone.

report.docx

  • Search for every number. Each is MEASURED, DERIVED, CITED or MODELED. Anything else is deleted, not softened.
  • No bare single numbers anywhere, including in prose.
  • Every median is rounded to the place its dispersion supports.
  • Every claimed difference passes C.8.4’s test, or is explicitly labelled unresolvable.
  • Every chart carries its measurement window and thermal condition on the chart.
  • Synthetic data is labelled synthetic in the sentence where it is used, with what that limits.
  • The Four Questions are answered separately, under their own headings.
  • The conclusion names its metric before its finding.
  • The flattering choice is named, defended, and the headline restated with it reversed.
  • “What I could not measure and what I would need” names instruments and experiments, not feelings.

Everything else

  • Exactly the required filenames, spelled exactly. No seventh document.
  • Every external figure has a URL and a retrieval date, and you clicked each link.
  • ai-usage.txt names models, uses, and at least one override — or plainly states there was none.

Coach’s Note — The audit finds the same thing every term, and it is never the interesting number. It is the power source. The compiler flag. The run you discarded on Tuesday and forgot to mention. Those are the fields that turn a defensible measurement into an anecdote, and they are boring, which is precisely why they are missing. Do it before you are tired, not after.


C.13 — Where to Go From Here

  • “It will not build / what is Workbench B?”Appendix A.
  • “Where did this dataset come from, and what may I claim from it?”Appendix B.
  • “How is this scored, and what exactly is the AI policy?”Appendix D.
  • “What does this word mean?”Appendix E.
  • The mechanisms behind the methodologyChapter 3 for why memory decides most results, Chapter 5 for why the soak protocol exists, and Chapter 8 for counters, comparison, and the argument this appendix is the operational form of.
  • The tool landscape — Arm Performance Studio and Streamline for Arm targets (https://developer.arm.com/), Perfetto for Android and Linux system tracing (https://perfetto.dev/), simpleperf and the Jetpack benchmark libraries on Android (https://developer.android.com/), perf on Linux, Instruments and xctrace on Apple platforms, and portable C++ steady_clock everywhere.

Nobody who reads your report will rerun your experiment. That is not cynicism about your colleagues; it is arithmetic about their time. They will read a number and act on it, and the entire chain between what happened on your machine and what your organization decides is you. Every rule above exists to make that chain inspectable by somebody who cannot rerun it: the conditions attached to the number, the dispersion attached to the median, the provenance tag attached to the figure, the flattering choice named before anyone finds it.

State the conditions before you state the number. Report the median with its dispersion. Tag every figure. Name the choice that helped you. Do those four things every week for eight weeks and they stop being a checklist and start being how you think — which is the only version of this that survives a deadline.