QHPC Research & Project Handbook
Queen's High-Performance Computing · Companion volume

Research & Project Handbook

Every topic, written as a runbook a member can actually execute — what it is, why it matters, week by week, with the commands, the measurements, the failure modes, and what "done" looks like. Graduated across five difficulty tiers so nobody's first sprint is a GPU port and nobody's fourth is trivial.

How to use this handbook

The operating plan lists 65 topics. This document expands them into runnable form. It is written so that a Sprint lead can hand a topic to a team and walk away, and so that a member who has never used a cluster can follow along without asking what a word means.

The five tiers

TierWhoPrerequisiteSprintTypical outcome
1 · FoundationAny student, any faculty, first monthNone — can log into a computerSprint 1A plot, a short report, and confidence
2 · DevelopingCompleted one sprintComfortable in a shell; has submitted a batch jobSprint 2A real measurement with an explanation
3 · IntermediateSecond-year membersCan write or modify a small program; understands parallelism conceptuallySprints 3–4Conference-submittable write-up
4 · AdvancedSenior members, competition teamProfiling, build systems, MPI/OpenMP fluencyYear 2+Publishable result; competition-transferable skill
5 · FrontierWith a faculty or grad co-supervisorDomain knowledge or advanced systems backgroundMulti-termGenuine research contribution

The anatomy of every runbook

A note on difficulty labels

Tier is about prerequisites, not effort. A Tier 1 topic done well is more valuable than a Tier 4 topic done badly. Several Tier 1 topics have produced publishable results elsewhere — E9 (reproducibility) especially. Do not treat the ladder as a hierarchy of worth.

Universal rules for every sprint

  1. Record as you go. Every configuration, every result, in a shared sheet, the moment it happens. Reconstructing this at write-up time is the single biggest waste of effort in student research.
  2. Three runs minimum. One measurement is an anecdote. Report median and spread, not a single number.
  3. Write down what broke. The debugging log becomes the "lessons learned" section, which is often the most useful part of the report.
  4. Plot early. Make the graph after the third data point, not the thirtieth. If the plot is boring, change the experiment while there's still time.
  5. Version everything. Scripts, configs, and analysis in a git repo from day one.
  6. Not finishing is acceptable. Not documenting is not. The Sprint Model explicitly says an unfinished result is fine — an unrecorded one is not.
Tier 1Foundation— no prerequisites; any faculty; first sprint
E1 · Tier 1
How much faster is a cluster than my laptop?
No experienceGreat demo4 weeks

What this is

You take one identical computational task and run it in three places — a personal laptop, the QHPC Raspberry Pi cluster, and a node on Frontenac — then measure how long it takes in each, across a range of problem sizes. You produce one chart.

That sounds almost too simple to be a research project. It isn't, because the answer is not what anyone expects. At small problem sizes the laptop usually wins, sometimes by a lot. Understanding why takes you straight into the real concepts: job scheduling latency, data movement cost, startup overhead, and the difference between throughput and latency.

Why it matters

Tier
1 — Foundation
Team size
2–3
Prerequisites
None
Cost
$0
Hardware
Laptop + Pi cluster + Frontenac account
Output
2,000–3,000 words + 2 figures

What you'll need

Frontenac defaults will bite you here

If you omit resource flags, Slurm on Frontenac gives you 1 CPU, 1024 MB, and a 3-hour limit. A benchmark that silently ran on one core with a gigabyte of memory produces numbers that look like the cluster is slow. Always set -c, --mem, and -t explicitly, and confirm with sacct --format=JobID,AllocCPUS,ReqMem,Elapsed afterwards that you got what you asked for.

Also note some nodes are contributed and carry a hard 3-hour cap regardless of what you request.

Week by week

Week 1
  • Pick your benchmark task and write it once, cleanly. It must accept the size parameter as a command-line argument.
  • Get it running on your laptop. Time it at a few sizes to find a range where the smallest takes ~0.1s and the largest takes ~60s. That range is your experiment.
  • Request Frontenac access if you don't have it. Do this in week 1, not week 3.
  • Set up the shared results spreadsheet: columns for platform, size, run number, time, and notes.
Week 2
  • Get the same code running on the Pi cluster. Expect this to be the week something breaks — a missing library, a Python version difference, a path problem. Log every fix; this becomes your lessons-learned section.
  • Run the full size sweep on laptop and Pi cluster. Three repetitions per size, minimum.
  • Start plotting after your first complete sweep. Do not wait.
Week 3
  • Run on Frontenac. Note that you now have two times to record: how long the job took to run, and how long it sat in the queue before starting. Both matter to a real user.
  • Investigate the crossover — the problem size at which the cluster starts beating the laptop. Narrow it down with extra measurements near that point.
  • Try to explain the shape of the curves. Where is time going at small sizes?
Week 4
  • Finalise the figures — one runtime-vs-size chart, one showing queue-wait separately.
  • Write the report using the template at the end of this handbook.
  • Prepare the sprint presentation. Lead with the counter-intuitive result, not the methodology.

What to measure

QuantityHowWhy
Wall-clock runtimetime, or in-program timers around the compute sectionThe headline number
Queue wait (Frontenac)Slurm accounting: submit time vs start timeA user waiting 40 minutes for a 10-second job cares about this
Startup overheadTime the program at the smallest possible sizeIsolates fixed cost from scaling cost
Run-to-run varianceSpread across your 3+ repetitionsTells you whether differences are real

What good looks like

A single chart with problem size on the x-axis (log scale) and runtime on the y-axis (log scale), three lines, and a clearly marked crossover point where the cluster overtakes the laptop. Accompanied by a paragraph that correctly explains why the laptop wins on the left-hand side.

Failure modes

Comparing different code. If the laptop runs NumPy and the cluster runs pure Python, you've measured the library, not the machine. Same code, same versions, everywhere.

Forgetting queue time. Reporting only compute time makes the cluster look better than it is in practice. Report both.

Too narrow a size range. If everything finishes in under a second you'll see nothing but noise. Push the large end until runs take real time.

Single runs. Machines are noisy — especially shared ones. Three runs minimum, report the median.

Deliverable

If you finish early

Add a fourth platform — a Frontenac GPU node, or multiple Pi nodes rather than one. Or add an energy dimension: estimate energy per computation on each platform and ask which is most efficient, not just fastest. That extension turns E1 into a natural on-ramp to Research topic 02.

E2 · Tier 1
Measure Amdahl's Law with your own data
No experienceFoundational concept4 weeks

What this is

Amdahl's Law is the most important single idea in parallel computing. It says: if some fraction of your program can't be parallelised, that fraction sets a hard ceiling on how much faster you can ever go — no matter how many processors you add. If 10% of your program is inherently serial, you can never be more than 10× faster, even with a million cores.

In this project you take a program where you control the serial fraction, run it at increasing core counts, plot the measured speedup against the theoretical prediction, and explain the gap between them. The gap is where the real learning is.

Why it matters

Tier
1 — Foundation
Team size
2
Prerequisites
None; some programming comfort helps
Cost
$0
Hardware
Any multi-core machine; Frontenac for larger counts
Output
2,500–3,500 words + 2–3 figures

The theory, stated plainly

If p is the fraction of the program that can be parallelised and N is the number of processors, the best possible speedup is:

Speedup(N) = 1 / ( (1 - p) + p/N )

As N → ∞, this approaches 1/(1-p).
So with p = 0.90 the ceiling is 10×.
With p = 0.99 the ceiling is 100×.
With p = 0.50 the ceiling is 2× — no matter how many cores.

Week by week

Week 1
  • Write a program with two clearly separated phases: a serial phase and a parallel phase, where you can dial the ratio between them. A simple approach: a loop that does S units of serial work, then a parallel loop doing P units.
  • Verify correctness first — the parallel version must produce the same answer as the serial one. Check this before measuring anything.
  • Establish your single-core baseline time.
Week 2
  • Run at 1, 2, 4, 8, 16 cores (and 32/64 on Frontenac if available) with the parallel fraction fixed at ~90%.
  • Compute measured speedup: T(1) / T(N).
  • Plot measured against theoretical. They will diverge. Note where.
Week 3
  • Repeat with different parallel fractions — try 50%, 75%, 90%, 99%. You now have a family of curves.
  • Investigate the divergence: what is the extra cost the formula doesn't include? (Thread creation, synchronisation, memory contention, load imbalance.)
  • If time allows, measure the overhead directly — time a parallel region that does no work.
Week 4
  • Produce the final figures: measured vs theoretical for each parallel fraction.
  • Write up, with a section explicitly on why reality falls short of theory.
  • Present.

What good looks like

A plot with core count on the x-axis and speedup on the y-axis, showing dashed theoretical curves and solid measured curves for several parallel fractions. The measured curves should sit below the theoretical ones and the gap should widen with core count. A correct explanation of that widening gap is the finding.

Failure modes

Too little work per core. At 16 cores with a tiny problem, overhead dominates completely and the curve is meaningless. Make the parallel phase substantial.

Not verifying correctness. A parallel program that races produces wrong answers fast. Check output equivalence before timing.

Hyperthreading confusion. If the machine reports 16 "cores" but has 8 physical ones, your curve will bend oddly at 8. Know your hardware — lscpu tells you.

Timing the whole program including startup. Time the compute region, not interpreter launch.

Deliverable

If you finish early

Investigate Gustafson's Law — the counter-argument to Amdahl, which observes that in practice people grow the problem as they add cores rather than keeping it fixed. Measure weak scaling to demonstrate it, and discuss when each law is the right lens. This is a genuinely sophisticated addition and sets up Research topic 10 directly.

E3 · Tier 1
Compression benchmark for scientific data
No experienceImmediately useful4 weeks

What this is

Scientific computing generates enormous files, and moving or storing them is often more expensive than computing them. Compression trades CPU time for space. This project measures that trade-off systematically across several compression tools and several kinds of real data, and produces a practical recommendation.

Why it matters

Tier
1 — Foundation
Team size
2
Prerequisites
Basic shell comfort
Cost
$0
Software
gzip, bzip2, xz, zstd, lz4
Output
2,000–3,000 words + 2 figures

Week by week

Week 1
  • Assemble a test corpus of at least four distinct data types: a text/CSV file, a binary numerical array, an image or video, and a simulation output file. Diversity matters — compression behaviour is entirely data-dependent.
  • Install the tools. Confirm each runs and reverses correctly (compress then decompress, verify checksum matches).
  • Decide on file sizes — aim for something large enough that compression takes measurable seconds.
Week 2
  • Run the full matrix: every tool, at several compression levels, on every file type. Record compressed size, compression time, and decompression time.
  • Automate this with a shell script — doing it by hand is error-prone and unrepeatable.
  • Verify integrity on every single run. A tool that's fast because it corrupted your data is not fast.
Week 3
  • Plot the trade-off: compression ratio on one axis, throughput (MB/s) on the other. Each tool-and-level becomes a point. The useful ones form a Pareto frontier.
  • Add a practical dimension: given a network speed, calculate whether compressing before transfer is actually faster end-to-end. This is the question people really have.
Week 4
  • Write a clear recommendation table: "if you care about X, use Y."
  • Report and present.

What good looks like

A ratio-vs-speed scatter plot with the Pareto frontier marked, per data type, plus a one-page practical recommendation. Bonus if you can explain why a given tool wins on a given data type — that requires understanding what the data looks like structurally.

Failure modes

Only testing text. Text compresses beautifully and tells you nothing about scientific binary data. Diversity of corpus is the whole experiment.

Ignoring decompression. Data is written once and read many times. Decompression speed usually matters more.

Caching effects. The second read of a file comes from memory, not disk. Drop caches between runs or use files larger than RAM.

Not verifying round-trips. Always confirm decompressed output matches the original byte for byte.

Deliverable

If you finish early

Test parallel compression variants (pigz, pbzip2, multi-threaded zstd) and measure how the trade-off shifts with core count. Or investigate lossy scientific compressors such as ZFP or SZ, which allow bounded error in exchange for dramatically better ratios — a genuinely current research area.

E4 · Tier 1
Python vs NumPy vs C — the same computation, three ways
No experienceHigh teaching value4 weeks

What this is

You implement one identical numerical computation three (or more) ways — pure Python, Python using NumPy, and compiled C — then measure how long each takes across a range of problem sizes. The differences are not subtle. Pure Python is typically 100× to 1000× slower than C for numerical loops, and NumPy usually lands somewhere close to C because it is calling C underneath.

The project is really about answering a deeper question: where does the time actually go? Why is an interpreted language slow? What is NumPy doing differently? When does it stop mattering?

Why it matters

Tier
1 — Foundation
Team size
2–3
Prerequisites
Can write a for-loop in some language
Cost
$0
Software
Python 3, NumPy, GCC
Output
2,500–3,500 words + 2 figures

Choosing the kernel

Pick one computation and stick to it. Good choices, in increasing order of interest:

KernelWhy it's a good choiceWatch out for
Vector sum / dot productTrivial to write correctly in all three; memory-bandwidth-boundMay be too simple — NumPy will crush pure Python immediately
Matrix multiply (naïve triple loop)Compute-bound; the classic; connects directly to HPL laterDon't accidentally compare your naïve C against NumPy's tuned BLAS and call it a language comparison — see failure modes
Mandelbrot setCompute-heavy, no library shortcut, and produces a pictureIteration counts vary per pixel — that's a feature, it introduces load imbalance
1D heat equation / stencilRealistic scientific pattern; sets up later stencil workRequires care with boundary conditions

Recommendation for a first sprint: Mandelbrot. It is compute-bound, has no library that trivialises it, and the output is an image you can show at a club fair.

Week by week

Week 1
  • Write the pure-Python version. Get it correct first — save the output and verify it visually or by checksum.
  • Establish the size range. Pure Python will be slow; pick sizes where it takes 1–60 seconds so you're not waiting all night.
  • Set up timing properly (see the timing reference). Time only the computation, never interpreter startup or file I/O.
Week 2
  • Write the NumPy version. The key discipline: eliminate the explicit loop. If you're still writing for i in range(n) around NumPy operations, you haven't vectorised — you've just made each step slower.
  • Verify the NumPy output matches the pure-Python output. Floating-point differences of ~1e-15 are fine; visible differences are a bug.
  • Time both across the size range, three runs each.
Week 3
  • Write the C version. Compile at several optimisation levels: -O0, -O1, -O2, -O3. The difference between -O0 and -O3 is itself a finding — often 3–10×.
  • Verify output equivalence again.
  • Complete the timing matrix.
Week 4
  • Plot all implementations on one log-log chart.
  • Compute and tabulate the speedup ratios at each size.
  • Write up, with a section explaining why the interpreter is slow — per-iteration type checking, object boxing, no vectorisation, no compilation.

What to measure

What good looks like

A log-log runtime chart with clearly separated lines, a table of speedup ratios, and — critically — a correct explanation of the mechanism. A report that says "C is faster" is incomplete. A report that says "C is faster because the interpreter performs a type check and a dictionary lookup on every single iteration, whereas the compiler resolves those at compile time and additionally vectorises the inner loop" is a real finding.

Failure modes

Comparing naïve C to tuned BLAS. If your kernel is matrix multiply and you compare hand-written C against numpy.dot, NumPy will win — because it dispatches to a decades-optimised BLAS library. That is a real and interesting result, but it is a comparison of libraries, not languages. Say so explicitly, or pick a kernel with no library equivalent.

Not vectorising the NumPy version. The most common mistake. Looping in Python over NumPy scalars is slower than pure Python.

Forgetting compiler flags. Comparing unoptimised C (-O0) to NumPy is unfair to C and misleading.

Timing including startup. Python takes ~30ms to start. At small problem sizes that dominates everything.

Deliverable

If you finish early

Add more languages — Julia (designed for exactly this trade-off), Fortran (still dominant in HPC and often fastest), Rust, or Python accelerated with Numba or Cython. The Numba result is particularly striking: a single decorator often recovers most of C's performance. Alternatively, profile the pure-Python version with cProfile to show exactly where the time goes, which turns a benchmark into a genuine investigation.

E5 · Tier 1
What would our cluster cost on the cloud?
No experienceNo code requiredCommerce-friendly

What this is

A costing study. You determine what QHPC's compute — the Pi cluster, and a representative Frontenac allocation — would cost if purchased from AWS, Azure, or Google Cloud instead. Then you build a break-even analysis: at what utilisation does owning hardware beat renting it?

This is a business and economics project that happens to be about computers. It requires no programming whatsoever.

Why it matters

Tier
1 — Foundation
Team size
1–2
Prerequisites
Spreadsheet competence
Cost
$0
Tools
Spreadsheet; public cloud pricing pages
Output
3,000–4,000 words + cost model

Week by week

Week 1
  • Inventory what you have. For the Pi cluster: node count, cores per node, RAM, storage, network. For Frontenac: your typical allocation — cores, memory, GPU-hours, storage.
  • Establish purchase costs for the Pi cluster (receipts, or current retail prices) and estimate its useful life.
  • Interview two or three members about actual usage: how many core-hours does a typical sprint consume?
Week 2
  • Find the equivalent cloud instances. This is the analytically hard part — matching ARM Pi nodes to cloud ARM instances (AWS Graviton), and Frontenac x86 nodes to comparable VM types.
  • Record on-demand, reserved, and spot/preemptible pricing for each. The spread between these is large and is itself a finding.
  • Do not forget the hidden costs: storage per GB-month, data egress charges, IP addresses, and the fact that cloud VMs bill while idle.
Week 3
  • Build the model. Total cost of ownership for owned hardware: purchase + power + the (free, but real) space and labour. Versus cloud: rate × hours used.
  • Find the break-even utilisation — the fraction of the year the hardware must be busy for ownership to be cheaper.
  • Sensitivity analysis: how does the answer change if electricity prices double, or if you only use the cluster 10% of the time?
Week 4
  • Add the strategic dimension: what does Frontenac access actually save QHPC per year, priced at cloud rates? This number belongs in the sponsorship deck.
  • Write up with a clear recommendation and a reusable spreadsheet model.

What good looks like

A spreadsheet model anyone can re-run with new numbers, a break-even chart (utilisation on the x-axis, cost on the y-axis, two lines crossing), and a headline figure: "QHPC's Frontenac access is equivalent to $X per year of cloud compute." That single sentence is worth a great deal in a sponsorship or funding conversation.

Failure modes

Comparing on-demand cloud pricing to hardware purchase price. On-demand is the most expensive way to buy cloud. A fair comparison uses reserved or spot pricing for sustained workloads.

Ignoring egress. Getting data out of a cloud provider is often the largest line item and is routinely forgotten.

Ignoring idle time. Owned hardware costs the same whether used or not; cloud only bills when running. This is the entire crux of the break-even question.

Stale prices. Cloud pricing changes. Date-stamp every figure and cite the page you took it from.

Deliverable

If you finish early

Add a carbon dimension. Cloud regions publish differing carbon intensities, and Ontario's grid is unusually low-carbon (largely nuclear and hydro). Compare emissions per compute-hour for local versus cloud, and you have turned a cost study into a sustainability study — feeding Research topics 34 and 37 directly.

E6 · Tier 1
Write the definitive Pi cluster build guide
No experienceHighest club value4 weeks

What this is

You document the existing five-node Raspberry Pi cluster so completely that a stranger with no prior knowledge could rebuild it from scratch. Parts list with prices, physical assembly, network configuration, the head node setup, PXE/TFTP network boot, the NFS shared filesystem, SSH keys, Slurm installation and configuration — every step, every command, every mistake to avoid.

Then you test it: hand the guide to someone who wasn't involved and watch them try to follow it. Every place they get stuck is a defect in the guide.

Why it matters

Tier
1 — Foundation
Team size
2–3 (one must be a non-expert)
Prerequisites
None — non-expertise is an asset here
Cost
$0
Output
A complete build guide, 4,000–8,000 words
Why a non-expert should lead this

Documentation written by the person who built the system is almost always unusable, because they unconsciously skip the steps that are obvious to them. Putting a newcomer in charge of writing, with an expert available to answer questions, produces a dramatically better guide. The questions the newcomer has to ask are the missing steps.

Week by week

Week 1
  • Gather every existing artifact: the workshop docs already in the repo, any notes, the original Raspberry Pi cluster tutorial that was followed, purchase records.
  • Interview whoever built it. Record the session. Ask specifically: "what went wrong?" and "what would you do differently?"
  • Photograph the physical cluster from several angles, including cabling.
  • Draft the structure: Parts → Assembly → OS → Network → Boot → Storage → Users → Scheduler → Verification → Troubleshooting.
Week 2
  • Write the hardware sections. Include a parts table with model numbers, quantities, current prices, and why each part was chosen.
  • Document the physical build with photographs at each stage.
  • Note the power budget — PoE limits are a real constraint people hit.
Week 3
  • Write the software sections. Every command in a copy-pasteable block. Every config file shown in full, with the lines that matter annotated.
  • Include the verification steps — after each stage, how do you know it worked? "Run this; you should see that."
  • Write the troubleshooting section from the interview notes: symptom → likely cause → fix.
Week 4
  • The test. Give the guide to someone uninvolved. Have them attempt a rebuild — or at minimum, a full dry run on virtual machines. Sit with them and note every hesitation.
  • Revise based on where they got stuck. This revision pass is what separates a real guide from notes.
  • Publish it on the club website and commit it to the repo.

What good looks like

Someone who has never seen the cluster can follow the guide from an empty table to a working Slurm installation without asking anyone a question. Every command is copy-pasteable, every config file is shown in full, and every stage ends with a check that confirms success.

Failure modes

Assumed knowledge. "Configure DHCP as usual" is not a step. If you cannot copy and paste it, it isn't documented.

No verification steps. A guide that tells you what to type but not how to confirm it worked leaves the reader stranded at the first silent failure.

Skipping the failures. The troubleshooting section is often the most valuable part. Include the things that went wrong, not just the happy path.

Never testing it. An untested guide is a draft. The test is the project.

Deliverable

If you finish early

Turn the manual steps into automation — an Ansible playbook or shell scripts that perform the configuration, so a rebuild takes an hour instead of a weekend. That is the natural bridge from this Tier 1 project into the provisioning work in Tier 3. Alternatively, produce a video walkthrough, which is often more useful for physical assembly than text.

E7 · Tier 1
Where does your data live? Filesystem timing
No experience4 weeks

What this is

You measure how long file operations take across different storage locations — a laptop SSD, the Pi cluster's local storage, the cluster's NFS shared filesystem, and Frontenac's parallel scratch filesystem. You vary the access pattern: one enormous file versus thousands of tiny ones, sequential versus random, reads versus writes.

The result reliably surprises people. Storage is usually the real bottleneck in scientific computing, and the performance difference between access patterns on the same hardware can be a factor of a hundred.

Why it matters

Tier
1 — Foundation
Team size
2
Prerequisites
Basic shell
Cost
$0
Tools
dd, time, fio (optional)
Output
2,500–3,500 words + 3 figures

The experimental matrix

DimensionLevels
LocationLaptop SSD · Pi local SD/SSD · Pi NFS mount · Frontenac /global/scratch (GPFS) · Frontenac /global/home · Frontenac node-local $TMPDISK
OperationSequential write · sequential read · random read
File pattern1 × 10 GB · 100 × 100 MB · 100,000 × 100 KB
Concurrency1 process · 4 processes · 16 processes (optional extension)

Week by week

Week 1
  • Learn to use dd for simple sequential tests, and understand what each flag does — especially bs (block size) and conv=fsync, without which you are timing the write cache rather than the disk.
  • Establish your file sizes. They must be larger than system RAM, or the operating system will serve everything from cache and your numbers will be fiction.
  • Write a script that runs one full test sweep and outputs CSV.
Week 2
  • Run the sequential tests across all storage locations.
  • Learn to drop the page cache between runs (sync; echo 3 > /proc/sys/vm/drop_caches where you have permission; otherwise use files far larger than RAM).
  • Begin plotting throughput in MB/s.
Week 3
  • Run the many-small-files tests. This is where the dramatic results appear. Creating 100,000 small files on a network filesystem can be orders of magnitude slower than writing the same bytes as one file.
  • Measure metadata operations separately — file creation, stat, deletion — because on shared filesystems these are frequently the bottleneck rather than bandwidth.
Week 4
  • Produce the figures. A grouped bar chart by location and pattern communicates this best.
  • Write practical guidance: "if you are doing X, store your data in Y and structure it like Z."
  • Present — lead with the small-files result, it lands hardest.

What good looks like

Charts showing throughput by location and access pattern, with the many-small-files penalty clearly visible, plus a short practical guide that a researcher in another department could act on immediately.

Failure modes

Caching. The number one error. If your test file fits in RAM, the second read is from memory and you are measuring nothing. Use files larger than RAM, or drop caches between runs.

Not syncing writes. Without fsync, writes may still be in a buffer when you stop the clock. You will report impossible throughput.

Testing a shared filesystem during peak hours. Frontenac scratch is shared with 1,000+ users. Note the time of day, run at multiple times, and report the variance — that variance is itself a legitimate finding.

Filling a shared filesystem. Be careful with 10 GB test files on shared storage. Clean up after every run and check your quota first.

Deliverable

If you finish early

Move from dd to fio, the industry-standard flexible I/O tester, which gives you queue depth, mixed read/write ratios, and proper random-access patterns. Or add concurrency: how does throughput change when four processes write simultaneously? That is the direct on-ramp to Research topic 43.

E8 · Tier 1
Job arrays: running a hundred experiments at once
No experienceMost transferable skill4 weeks

What this is

Take any program with a tunable parameter. Instead of running it a hundred times by hand, you use a Slurm job array to submit all hundred variants as a single command, let the scheduler run them in parallel across the cluster, then collect and visualise the results as a surface or heatmap.

The technique is almost trivially simple — a two-line change to a batch script. Its impact on a researcher's working life is enormous.

Why it matters

Tier
1 — Foundation
Team size
2
Prerequisites
Has submitted one Slurm job
Cost
$0
Output
2,500–3,500 words + parameter surface

The core mechanic

#!/bin/bash
#SBATCH --array=0-99
#SBATCH --time=00:10:00
#SBATCH --output=results/run_%A_%a.out

# SLURM_ARRAY_TASK_ID is 0..99, different in every task.
# Use it to select this task's parameter value.
PARAM=$(sed -n "$((SLURM_ARRAY_TASK_ID + 1))p" parameters.txt)

./my_program --parameter "$PARAM" > "results/out_${SLURM_ARRAY_TASK_ID}.csv"

That is the entire idea. One sbatch submission creates a hundred independent tasks, each knowing its own index, which the scheduler runs as resources free up.

Week by week

Week 1
  • Choose the program and the parameter. Good candidates: a simulation with a physical constant to vary, a model with a hyperparameter, or a numerical method with a step size.
  • Make the program accept the parameter on the command line and write results to a file named by the parameter. Get this right first — everything downstream depends on it.
  • Run it manually a few times to establish timing, so you can set a sensible --time limit.
Week 2
  • Write the array script. Start with --array=0-4 — five tasks — and confirm it works before scaling up.
  • Learn squeue, sacct, and scancel. You will need all three.
  • Scale to the full sweep. Note how long the whole thing takes versus the sum of individual runtimes — the difference is what the scheduler bought you.
Week 3
  • Write the collection script that gathers a hundred output files into one tidy dataset.
  • Handle failures gracefully. Some tasks will fail — that is normal, and dealing with it is part of the skill. Identify which, why, and re-run only those (--array=17,43,88).
  • Extend to a two-parameter sweep if the science supports it, producing a surface rather than a line.
Week 4
  • Visualise: line plot for one parameter, heatmap or contour for two.
  • Write up both the scientific result (what the sweep revealed) and the methodological result (how much faster this was than serial execution).
  • Produce a short "how to run a parameter sweep" guide for the club — this becomes a reusable teaching asset.

What good looks like

A parameter surface or curve derived from ≥100 runs, a clear statement of wall-clock time saved versus running serially, and a reusable template script that another member can adapt in ten minutes.

Failure modes

Every task writing to the same output file. They will overwrite each other and you will lose everything. Always include $SLURM_ARRAY_TASK_ID in output filenames.

Requesting too much time or memory per task. The scheduler will start fewer tasks concurrently, and your sweep takes far longer than it should.

Submitting 10,000 tasks on the first attempt. Test with five. Cluster administrators notice array bombs, and Frontenac has array size limits.

Not checking exit codes. A task that crashed still produces an output file — an empty one. Verify completeness before analysing.

Deliverable

If you finish early

Compare against alternative approaches: GNU Parallel on a single node, or a workflow manager such as Snakemake or Nextflow. When is each the right tool? That comparison is a genuinely useful contribution, because most researchers only ever learn one of them. Or extend into adaptive sweeping — using early results to decide which parameters to explore next, which is the beginning of real optimisation.

E9 · Tier 1
Reproduce a figure from a published paper
No experiencePublishable as-isFailure is the finding

What this is

You pick one figure from a published undergraduate paper — the CUCAI proceedings archive gives you 72 to choose from — and try to regenerate it using only what the paper tells you. You document precisely where you succeed, where you get stuck, and what information was missing.

This is reproducibility research, and it is a real and currently very active field. You are not required to succeed. A careful, honest account of why a result could not be reproduced is a more valuable contribution than a successful re-run.

Why this is the most underrated topic in this handbook

It requires no original idea, no novel method, and no advanced skills — yet it produces genuinely publishable work, teaches critical reading better than any seminar, and instills the documentation habits that make the student's own future work reproducible. It is also psychologically safe: there is no way to "fail," because failure is the result.

Why it matters

Tier
1 — Foundation
Team size
2
Prerequisites
None; ability to read a technical paper
Cost
$0
Output
3,000–4,000 words

Choosing a target

Good candidates share these properties:

From the archived corpus, strong candidates include the benchmarking-oriented papers, anything using the NASA turbofan dataset, and any scaling study. Avoid papers depending on proprietary data or bespoke hardware.

Week by week

Week 1
  • Read three or four candidate papers properly. Choose one and justify the choice in writing.
  • Before touching a keyboard, write down exactly what the figure claims and what you would need to reproduce it: data, software, versions, parameters, hardware.
  • Make a checklist of what the paper actually provides versus what you need. The gaps in that checklist are your primary result.
  • Look for a code repository. Note whether one exists — many papers don't have one.
Week 2
  • Obtain the data. Record how long this took and how difficult it was — this is a finding.
  • Set up the software environment. Record every version. If the paper doesn't state versions, note that and pick current ones.
  • Begin implementation. Keep a running log with timestamps of every decision you had to make that the paper left ambiguous.
Week 3
  • Produce your version of the figure.
  • Compare quantitatively where possible: are the numbers within 5%? 50%? Different by an order of magnitude? Does the trend match even if the absolute values don't?
  • If you cannot reproduce it, systematically identify why. Test your hypotheses — e.g. "if they used a different normalisation, would that explain the gap?"
Week 4
  • Write up honestly and generously. The goal is not to criticise the original authors — most omissions are unintentional and universal. Frame findings as "information needed for reproduction" rather than "errors."
  • Produce a checklist: what should a paper in this area include to be reproducible?
  • Consider contacting the original authors. Many are Queen's or peer-club students, and it is a genuinely good way to open a collaboration.

What to record

CategoryWhat to note
Data availabilityWas it linked? Did the link work? How long to obtain?
Code availabilityRepository present? Does it run? Documented?
EnvironmentWere software versions specified? Hardware described?
ParametersEvery value you had to guess
AmbiguitiesEvery point where the text supported more than one interpretation
Result agreementQuantitative comparison of your figure to theirs
EffortHours spent. A genuinely useful and rarely reported number.

What good looks like

A side-by-side of the original figure and your reproduction, a structured account of every gap encountered, an honest verdict (fully / partially / not reproduced), and a reproducibility checklist other students can use on their own work. A "not reproduced" verdict, well documented, is a complete success for this project.

Failure modes

Choosing an over-ambitious target. A paper needing 10,000 GPU-hours cannot be reproduced in a sprint. Check feasibility in week 1.

Not logging as you go. The log is the paper. Reconstructing it afterwards loses exactly the detail that matters.

Silently filling gaps. When you guess a parameter, that guess is a finding. Record it rather than quietly proceeding.

An accusatory tone. This damages relationships QHPC needs and misrepresents the work. Reproducibility gaps are a systemic norm, not individual failings.

Deliverable

If you finish early

Reproduce a second figure from a different paper and compare reproducibility across them. With three or four, you have the beginnings of a systematic study of reproducibility in Canadian undergraduate computational research — which is genuinely novel and would be a strong CUCAI submission in its own right.

E10 · Tier 1
What does campus actually know about research computing?
No experienceNo codeFeeds recruitment

What this is

A survey and interview study of Queen's students and researchers: what compute do they have, what do they need, what do they know exists, and where do they get stuck? You produce quantified evidence about a gap that QHPC currently only asserts.

Why it matters

Tier
1 — Foundation
Team size
2–3
Prerequisites
None
Cost
$0
Output
3,500–5,000 words + survey instrument
Ethics — read before starting

Research involving human participants may require review by the Queen's General Research Ethics Board (GREB). Rules differ for coursework, club activity, and publishable research, and an exemption for one does not extend to another. Ask the faculty advisor before collecting any data, and be explicit that you intend to publish. Getting this wrong retroactively can render the data unusable. Build in time for it.

Week by week

Week 1
  • Define your research questions precisely. Not "what do people think about HPC" but e.g. "what fraction of upper-year science students have ever run a job on a shared cluster, and what prevented those who haven't?"
  • Draft the survey. Keep it under five minutes — completion rate collapses beyond that.
  • Resolve the ethics question with the faculty advisor.
  • Identify your sampling frame: which departments, which years, how will you reach them?
Week 2
  • Pilot the survey on 5–10 people. Watch them complete it. Fix every question they misread — and some will be misread.
  • Distribute. Use the ECE faculty-wide email, department listservs, class announcements, and the club's own channels. Track which channel produced which responses.
  • Schedule 5–8 semi-structured interviews with people who indicated they use or need compute.
Week 3
  • Conduct the interviews. Record with permission; take notes regardless.
  • Begin quantitative analysis — frequencies, cross-tabs by department and year.
  • Begin qualitative coding: group interview responses into recurring themes.
Week 4
  • Produce the figures — awareness by department, barriers reported, unmet need.
  • Write up. Include a short, actionable section: what should QHPC and the CAC do about these findings?
  • Share the results back with participants. It is courteous, and it is another recruitment touch.

Questions worth asking

What good looks like

A defensible sample (aim for 100+ survey responses across at least four departments), clear figures, honest treatment of limitations and sampling bias, and at least one genuinely surprising finding. The killer result, if it appears, is a quantified estimate of how many Queen's students have silently scaled down their work for want of compute.

Failure modes

Skipping ethics clearance. Can invalidate the entire dataset for publication.

Leading questions. "Do you agree that Queen's should provide more computing resources?" measures nothing. Ask about behaviour, not opinions.

Sampling only from computing. Then you've surveyed people who already know. The interesting population is everyone else.

Over-claiming. A 60-response convenience sample does not represent the university. State limits plainly — reviewers respect it.

Deliverable

If you finish early

Extend the same instrument to the peer clubs in the national network — QMIND, UTMIST, WAT.ai, Western AI, UVic AI. You then have a multi-institution study, which is exactly Research topic 35, and a strong CUCAI submission. The interview subjects are all in one room in Toronto in March.

E11 · Tier 1
OpenMP thread scaling — where does it stop helping?
Light programming4 weeks

What this is

OpenMP lets you parallelise a loop by adding a single line of code above it. In this project you take one such loop, run it with increasing thread counts, and find the point where adding threads stops helping — then work out why it stopped.

There are only a handful of possible reasons, and identifying which one applies is the entire skill: insufficient work per thread, memory bandwidth saturation, false sharing, load imbalance, or hyperthreading.

Why it matters

Tier
1 — Foundation (upper)
Team size
2
Prerequisites
Can compile a C program
Cost
$0
Software
GCC with OpenMP (-fopenmp)
Output
2,500–3,500 words + 3 figures

The mechanic

/* Serial */
for (int i = 0; i < N; i++) result[i] = compute(data[i]);

/* Parallel — one line */
#pragma omp parallel for
for (int i = 0; i < N; i++) result[i] = compute(data[i]);

Compile:  gcc -O2 -fopenmp prog.c -o prog
Control:  export OMP_NUM_THREADS=8

Week by week

Week 1
  • Establish your hardware honestly. Run lscpu. How many physical cores, versus logical threads? This single fact explains most confusing results later.
  • Write or adapt a compute-heavy loop. Mandelbrot from E4 works well; so does a numerical integration.
  • Verify parallel output matches serial output exactly.
Week 2
  • Sweep OMP_NUM_THREADS from 1 up to twice the physical core count. Three runs each.
  • Plot speedup versus thread count. Identify the knee.
  • Note whether the knee coincides with physical core count — usually it does, and the flat region beyond is hyperthreading giving little for compute-bound work.
Week 3
  • Vary the problem size. The knee moves. With a small problem, overhead dominates early; with a large one, scaling continues further. Produce a family of curves.
  • Experiment with scheduling: schedule(static) versus schedule(dynamic). For loops with uneven per-iteration cost — Mandelbrot is exactly this — dynamic scheduling can help substantially.
  • Try thread pinning (OMP_PROC_BIND=true) and observe the effect on variance.
Week 4
  • Diagnose the limiting factor and defend your diagnosis with evidence.
  • Write up: the curves, the knee, the mechanism, and the practical guidance.

Diagnosing the plateau

CauseSignatureTest
Not enough work per threadKnee at low thread count; improves with larger problemIncrease problem size and re-run
Memory bandwidth saturationPlateau regardless of problem size; low arithmetic intensityCompare against STREAM bandwidth
Load imbalanceHelped noticeably by schedule(dynamic)Switch scheduling policy
HyperthreadingKnee exactly at physical core countCompare lscpu physical vs logical
False sharingPoor scaling with adjacent per-thread writesPad shared arrays to cache-line size
Failure modes

Race conditions producing wrong answers. Always verify correctness before timing. A fast wrong answer is worthless.

Forgetting -fopenmp. Without it the pragma is silently ignored and you measure serial code at every thread count. If your speedup is exactly 1.0 everywhere, this is why.

Ignoring hyperthreading. Reporting "scaling stops at 8 threads" on an 8-physical-core machine as a mystery, when it is entirely expected.

Sharing the machine. Another user's job on the same node destroys your measurements. Use a dedicated allocation.

Deliverable

If you finish early

Deliberately construct a false-sharing example — have threads write to adjacent array elements — then fix it with padding and measure the difference. It is often dramatic (5–10×) and it is a genuinely memorable demonstration. Alternatively, move to a NUMA machine and investigate first-touch memory placement.

E12 · Tier 1
MPI from zero: hello world → ring → reduce
Light programmingGateway topic4 weeks

What this is

MPI — the Message Passing Interface — is how programs run across multiple machines. Unlike OpenMP, which shares memory within one node, MPI processes have entirely separate memory and must explicitly send each other messages. It is the foundation of essentially all large-scale scientific computing.

You write three programs in sequence, each building on the last, and time each. By the end you understand point-to-point messaging, collective operations, and — most importantly — why communication cost eventually dominates everything.

Why it matters

Tier
1 — Foundation (upper)
Team size
2
Prerequisites
Can compile and run a C program
Cost
$0
Software
OpenMPI
Output
3,000–4,000 words + 3 figures

The three programs

#ProgramConceptWhat you measure
1Hello world — every process prints its rank and hostnameRanks, communicators, launchingStartup time vs process count
2Ring — pass a message around all processes and backPoint-to-point send/receive; deadlockLatency per hop; message-size scaling
3Reduce — every process computes a partial sum; combine to one totalCollective operationsManual vs MPI_Reduce performance
/* Program 1 — the whole thing */
#include <mpi.h>
#include <stdio.h>

int main(int argc, char **argv) {
    MPI_Init(&argc, &argv);
    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    char host[256]; gethostname(host, 256);
    printf("Rank %d of %d on %s\n", rank, size, host);
    MPI_Finalize();
    return 0;
}

Compile:  mpicc hello.c -o hello
Run:      mpirun -np 4 ./hello

Week by week

Week 1
  • Get MPI working. On the Pi cluster, confirm processes actually land on different physical nodes — printing the hostname proves it.
  • Run hello world at 2, 4, 8, 16 processes. Time the whole launch. Note that startup itself costs something and grows with process count.
  • Understand the difference between running 4 processes on 1 node and 4 processes on 4 nodes. Measure both — this contrast is instructive.
Week 2
  • Write the ring program: rank 0 sends to 1, 1 to 2, … last back to 0.
  • You will probably deadlock. This is expected and educational — it happens when every process tries to send before any receives. Understanding and fixing it (ordering, or MPI_Sendrecv) is a core MPI lesson.
  • Time a full loop. Divide by process count to get per-hop latency.
  • Vary the message size from 8 bytes to several megabytes. Plot latency versus size — you should see a flat latency-dominated region and then a bandwidth-dominated slope.
Week 3
  • Write the reduce program two ways: first manually (every rank sends its partial result to rank 0, which sums them), then using MPI_Reduce.
  • Compare them. The library version should win, and increasingly so at larger process counts — because it uses a tree rather than a linear gather. Explaining that difference is the finding.
  • If time allows, compare MPI_Reduce against MPI_Allreduce.
Week 4
  • Consolidate the three experiments into one narrative about communication cost.
  • Produce the figures: startup scaling, latency vs message size, manual vs collective reduce.
  • Write a short "MPI for QHPC beginners" guide as a club teaching asset.

What good looks like

Three working programs, a latency-versus-message-size curve with the latency and bandwidth regimes both visible, and a demonstration that the library's collective beats the naïve implementation at scale — with a correct explanation of why (logarithmic tree versus linear gather).

Failure modes

All processes on one node. Then you are measuring shared-memory communication, not the network, and your latency numbers will be unrepresentatively good. Always print hostnames to confirm distribution.

Deadlock treated as failure. It's a learning opportunity — document it.

Timing without a barrier. Processes start at slightly different moments. Use MPI_Barrier before starting the clock.

Message sizes too small to see bandwidth effects. Go up to megabytes, not kilobytes.

Deliverable

If you finish early

Compare your hand-rolled measurements against the OSU Micro-Benchmarks, the standard tool for exactly this. How close did you get? Where do they differ, and why? That comparison is a direct on-ramp to Research topic 47 (collective-communication tuning) and to the standing benchmark suite.

ReferenceShared conventions— read once, apply to every project

Slurm quick reference

The commands members actually use, in the order they'll need them.

# Submit
sbatch script.sh                  # submit a batch job
sbatch --array=0-99 script.sh     # submit a job array
srun --pty bash                   # interactive session

# Monitor
squeue -u $USER                   # my queued/running jobs
squeue -j JOBID                   # one specific job
scontrol show job JOBID           # full detail incl. why it's pending
sacct -j JOBID --format=JobID,Elapsed,MaxRSS,State
                                  # after completion: time and memory used

# Control
scancel JOBID                     # cancel one
scancel -u $USER                  # cancel everything of mine

# Inspect the system
sinfo                             # partitions and node states
sinfo -N -l                       # per-node detail

A batch script skeleton

#!/bin/bash
#SBATCH --job-name=my_experiment
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=8
#SBATCH --mem=16G
#SBATCH --time=01:00:00
#SBATCH --output=logs/%x_%j.out
#SBATCH --error=logs/%x_%j.err

module load gcc openmpi          # adjust to the site's module names
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK

srun ./my_program --size 1000
The two habits that matter most

Always request less time than you think you need, then adjust. Shorter jobs are scheduled sooner via backfill. A job asking for 24 hours when it needs 20 minutes may wait days.

Always check sacct afterwards for MaxRSS. Requesting 64 GB when the job used 2 GB wastes the allocation and delays your own next job.

How to time things properly

Almost every flawed result in this handbook's failure-mode sections traces back to bad timing methodology. The rules:

  1. Time the computation, not the program. Put timers around the section of interest, excluding startup, input parsing, and file writing — unless those are what you're studying.
  2. Use a monotonic clock. In C, clock_gettime(CLOCK_MONOTONIC, …). In Python, time.perf_counter(). Never time.time(), which can jump backwards.
  3. Warm up. The first run pays for cache misses, page faults, and JIT compilation. Discard it, or report it separately as a distinct finding.
  4. Repeat at least three times. Report the median, not the mean — one outlier destroys a mean. Report the spread too.
  5. Note what else was running. On a shared machine, someone else's job changes your numbers. Record the time of day and, where possible, use an exclusive allocation.
  6. Check the timer resolution. If your operation takes 50 microseconds and your clock ticks every millisecond, you are measuring nothing. Loop the operation 1,000 times and divide.
  7. Beware the optimiser. A compiler at -O3 may delete a computation whose result you never use. Print or accumulate the result so it cannot be optimised away.
/* C */
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
/* ... work ... */
clock_gettime(CLOCK_MONOTONIC, &t1);
double sec = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;

# Python
import time
t0 = time.perf_counter()
# ... work ...
elapsed = time.perf_counter() - t0

Plotting conventions

Agree these once so every QHPC paper looks like it came from the same organisation.

Report template

Every sprint report uses this structure. It maps onto the IEEE conference format CUCAI expects, so converting a sprint report into a submission is mechanical rather than a rewrite.

SectionLengthContains
Abstract150–250 wordsThe question, the method in one sentence, the headline number, the implication. Write this last.
I. Introduction400–700Motivation, why anyone should care, and an explicit statement of what this report contributes
II. Background / Related work300–600What is already known. Cite real sources — the CUCAI archive is a legitimate corpus to cite.
III. Method600–1,200Hardware, software with versions, parameters, and procedure. Written so a stranger could repeat it. This is where E9 teaches you what to include.
IV. Results600–1,200Figures and tables with prose describing what they show. Description only — no interpretation yet.
V. Discussion400–900What the results mean. Why they came out this way. What surprised you.
VI. Limitations150–350What you did not control, what might be wrong. Include this always — it consistently makes a report read as more rigorous, not less.
VII. Conclusion & future work200–400Restate the finding; name the next question
References—IEEE numbered style, managed in Zotero
Appendix: reproduction—Repo link, exact commands, environment details
The writing order that works

Method → Results → Discussion → Background → Introduction → Conclusion → Abstract. Never start with the introduction; you cannot introduce a finding you haven't yet articulated. Figures before prose, always.

Tier 2Developing— one sprint completed; comfortable in a shell; has submitted a batch job

What changes at Tier 2

Tier 1 answered questions with a single number or curve. Tier 2 introduces controlled comparison — you are no longer measuring one thing, you are isolating the effect of one variable while holding others fixed. That is a genuine methodological step up, and it is where students start producing work that can survive review.

Three new expectations apply from here on:

T2.1 · Tier 2 · maps to §14 topic 06
What does containerisation actually cost?
Tier 2Directly useful to CAC4 weeks

What this is

Containers package an application together with its entire software environment — libraries, compilers, dependencies — so it runs identically anywhere. In HPC this solves a genuinely painful problem: scientific software is notoriously difficult to build, and "it worked on my machine" is the field's most expensive sentence.

But containers add a layer between your program and the hardware. This project quantifies that layer. You run identical workloads three ways — directly on the host ("bare metal"), inside Docker, and inside Apptainer (the HPC-native container runtime, formerly Singularity) — and measure the difference across several classes of workload.

Background you need before starting

Containers are not virtual machines. A VM emulates an entire computer including its own kernel, which is expensive. A container shares the host kernel and merely isolates the filesystem, process tree, and network view using kernel features (namespaces and cgroups). This is why container overhead is usually small — often under 5% — and why the interesting question is not "is it slower" but "slower at what."

The two runtimes differ in ways that matter for HPC:

DockerApptainer
Designed forMicroservices, web deploymentHPC and scientific computing
Runs asTypically requires a privileged daemonRuns as the calling user, no daemon
Why that mattersMost HPC centres will not permit it on compute nodesSafe on multi-tenant clusters — which is why it exists
Image formatLayered (overlay filesystem)Single SIF file
MPI supportAwkwardDesigned for it (host MPI can be bound in)
Filesystem defaultIsolated; mounts must be requestedHome and common paths bound by default

That last row predicts one of your likely findings in advance: Docker's overlay filesystem is where most measurable overhead lives, so I/O-heavy and metadata-heavy workloads should show the largest gap, while pure compute should show almost none.

Why it matters

Tier
2 — Developing
Team size
2–3
Prerequisites
Comfortable in a shell; has built software from source once
Cost
$0
Software
Docker, Apptainer, one compute benchmark, one I/O benchmark
Output
3,500–5,000 words + 3–4 figures

The experimental design

You need workloads that stress different subsystems, because container overhead is entirely subsystem-dependent. Use at least four:

Workload classSuggested benchmarkExpected overheadWhy
Pure computeHPL, or your Mandelbrot from E4Near zero (<1%)Instructions execute natively; nothing is intercepted
Memory bandwidthSTREAMNear zeroSame reason
Sequential I/Odd or fio, large filesSmall but measurableGoes through the container filesystem layer
Metadata-heavy I/OCreate/stat/delete 100k small filesLargestOverlay filesystems handle metadata poorly
StartupTime a trivial "hello world" containerSubstantial in relative termsImage mount and namespace setup are fixed costs
MPI (extension)OSU Micro-BenchmarksDepends entirely on configurationHost MPI binding vs container MPI is the whole question
What must be held constant

This is the part that decides whether your study is valid.

Identical software versions inside and outside. If the container has GCC 13 and the host has GCC 11, you are measuring compilers. Build the container from the host's versions, or install matching versions on the host.

Identical compiler flags. Same -O level, same architecture flags. A container built with -march=native on a different machine may produce different instructions entirely.

Same physical node. Run all three configurations on the same machine, ideally back to back and interleaved, so thermal state and system load are comparable.

Same storage location. If bare metal writes to local disk and the container writes to a bind-mounted network path, you are measuring the network.

Week by week

Week 1

Goal: a working, controlled baseline.

  • Days 1–2: Install both runtimes. On the Pi cluster you likely have root; on Frontenac you almost certainly do not have Docker, and that constraint is itself worth reporting.
  • Days 3–4: Build your container images. Write both a Dockerfile and an Apptainer definition file that install the same software versions. Pin every version explicitly — no apt install gcc without a version.
  • Day 5: Establish bare-metal baselines for all workloads. Five runs each. Record the run-to-run spread now, because it defines your noise floor — any container overhead smaller than this spread is not measurable with your setup, and you should say so.
  • Deliverable this week: a table of baseline timings with medians and spreads, and both container definition files committed.
Week 2

Goal: complete the compute and memory measurements.

  • Run the compute and memory-bandwidth workloads in all three configurations, interleaved (bare, docker, apptainer, bare, docker, apptainer …) rather than in blocks. Interleaving protects against drift from thermal throttling or changing system load.
  • Five runs per configuration minimum.
  • Compute overhead as a percentage relative to bare metal, with a confidence interval or at minimum a min–max range.
  • Expect to find nothing here. If compute overhead is under 1%, that is the result, and it is the reassuring half of the story.
Week 3

Goal: find where the cost actually lives.

  • Run the I/O workloads. Sequential first, then the metadata-heavy small-files test.
  • For Docker, run the small-files test twice: once writing into the container's overlay filesystem, once into a bind-mounted host directory. The difference isolates the overlay cost specifically and is one of the most instructive measurements in the whole project.
  • Measure container startup latency: time 100 sequential trivial container launches, divide.
  • Begin the mechanism investigation. If you see overhead, what causes it? Form a hypothesis, then design a test that would distinguish it from alternatives.
Week 4

Goal: analysis, mechanism, and practical guidance.

  • Produce the figures — a grouped bar chart of relative overhead by workload class is the clearest presentation.
  • Write the mechanism section. This is what separates Tier 2 from Tier 1.
  • Write practical guidance for a Queen's researcher: when should you containerise, which runtime, and what to watch out for.
  • Send the guidance to the CAC. Even if they already know, the gesture opens the relationship.

What good looks like

A chart showing relative overhead by workload class, with compute near zero and metadata-heavy I/O visibly worse, error bars throughout, and a mechanism explanation that survives scrutiny. Plus a one-page practical guide. If you additionally demonstrate that the Docker overlay penalty disappears with a bind mount, you have a genuinely useful and non-obvious result.

Failure modes

Different software versions inside and out. The single most common invalidating error. Pin everything.

Reporting overhead smaller than your noise floor. If bare-metal runs vary by ±3% and you report 2% container overhead, you have measured nothing. State your noise floor explicitly and only claim differences exceeding it.

Running the three configurations at different times of day. On a shared machine, load changes. Interleave.

Testing only compute. You will conclude "containers are free," which is true for compute and false in general. The I/O workloads are the point.

Cold vs warm image cache. The first container launch pulls and mounts the image. Subsequent ones may not. Decide which you're measuring and be consistent.

Deliverable

If you finish early

Add MPI across containers — the genuinely hard case. Multi-node MPI inside containers requires the container's MPI to be compatible with the host's interconnect libraries, and getting this wrong silently falls back to TCP over Ethernet instead of using the fast fabric, which can cost an order of magnitude. Demonstrating that failure mode and then fixing it is a strong result and directly transferable to competition work.

Alternatively, compare against Spack or EasyBuild environments, which solve the same reproducibility problem without containers. When is each appropriate?

T2.2 · Tier 2 · maps to §14 topic 40
How much performance comes from the compiler?
Tier 2Consistently surprising4 weeks

What this is

You write one program, then compile it many different ways — different compilers (GCC, Clang, and vendor compilers where available), different optimisation levels, different architecture targeting flags — and measure how much the identical source code varies in speed.

The answer routinely exceeds what people expect. Differences of 2–5× between -O0 and -O3 are ordinary. Differences of 20–40% between compilers on the same code at the same optimisation level are common. On a competition where every percent counts, this is free performance that costs only knowledge.

Background you need

Optimisation levels are bundles of individual transformations:

FlagWhat it doesRisk
-O0Nothing. Straightforward compilation, easy to debug.None — but slow
-O1Basic: dead code elimination, simple register allocationNone in practice
-O2The standard production level. Inlining, loop optimisations, vectorisation in modern GCC.Essentially none
-O3Aggressive: more inlining, more loop transformation, more vectorisationCan occasionally be slower — larger code, worse instruction cache behaviour
-march=nativeEmit instructions specific to this exact CPU (AVX-512, etc.)Binary may not run on other machines. Critical caveat on heterogeneous clusters.
-ffast-mathPermit floating-point reassociation and other unsafe algebraChanges results. Can break numerical correctness. Must be validated, never assumed.
-fltoLink-time optimisation: optimise across compilation unitsLong build times; occasional bugs
The -ffast-math discipline

This flag is the single most tempting and most dangerous item in the list. It permits the compiler to treat floating-point arithmetic as if it were associative, which it is not. It can deliver real speedups — and it can silently change your numerical results.

If you test it, you must validate correctness against the un-optimised build and report the numerical difference. A performance result obtained with -ffast-math and no correctness check is not a result. This is exactly the kind of methodological care that distinguishes a Tier 2 report from a Tier 1 one.

Why it matters

Tier
2 — Developing
Team size
2
Prerequisites
Can build a C/C++/Fortran program from source
Cost
$0
Software
GCC, Clang; optionally Intel oneAPI or NVIDIA HPC SDK
Output
3,500–4,500 words + 3 figures

Week by week

Week 1

Goal: build matrix defined and correctness harness working.

  • Choose 2–3 benchmark programs spanning different characteristics: one compute-bound (matrix multiply, Mandelbrot), one memory-bound (STREAM), one branch-heavy (a sort, or a sparse solver).
  • Build the correctness harness first. Before measuring anything, write a script that runs a binary and checks its output against a reference. You will need this at every step, and especially for -ffast-math.
  • Define the build matrix explicitly as a table: {compilers} × {optimisation levels} × {arch flags}. Even a modest matrix is 2 × 4 × 2 = 16 binaries per program.
  • Automate the builds. Doing 48 builds by hand guarantees inconsistency.
Week 2

Goal: the optimisation-level sweep.

  • Build and run every optimisation level with a single compiler. Five runs each.
  • Verify correctness at every level.
  • Plot runtime by optimisation level per benchmark. Look specifically for cases where -O3 is slower than -O2 — this happens, and finding one is a genuinely interesting result worth investigating.
  • Record binary sizes too. Aggressive inlining inflates code size, which is the usual mechanism behind an -O3 regression.
Week 3

Goal: cross-compiler comparison and architecture flags.

  • Repeat the sweep with the second compiler. Compare like for like at each optimisation level.
  • Add -march=native and measure the gain. On modern x86 with AVX-512 this can be substantial for vectorisable loops; on ARM it may be minimal.
  • Test portability explicitly: build with -march=native on one node, then try to run that binary on a different node type. If it crashes with an illegal instruction, you have demonstrated the risk concretely — and that demonstration is worth including.
  • If testing -ffast-math: measure both the speedup and the numerical deviation from the reference.
Week 4

Goal: mechanism and recommendations.

  • Investigate at least one result mechanistically. Use perf stat to compare instruction counts, cache misses, and branch mispredictions between two builds. Why is the faster one faster?
  • Optionally inspect the generated assembly for a hot loop (objdump -d, or Compiler Explorer for small examples) to confirm vectorisation actually occurred.
  • Produce a recommended default flag set for QHPC's benchmark suite, with justification.
  • Write up.

What to measure

What good looks like

A grouped bar chart of runtime by compiler and optimisation level across benchmarks, at least one mechanistically explained result, an explicit portability caveat demonstrated rather than asserted, and a concrete recommended flag set. The strongest possible version also finds and explains an -O3 regression.

Failure modes

Not verifying correctness. A binary that computes the wrong answer quickly is not faster.

Dead code elimination. At -O2 and above, a compiler may delete a computation whose result is never used — producing an impossibly fast "result." Always consume the output (print it, checksum it, accumulate it).

Comparing compilers at different flag levels. GCC -O2 against Clang -O3 tells you nothing.

Ignoring build time. LTO can multiply compile time. At a competition that matters.

Assuming vectorisation happened. Use -fopt-info-vec (GCC) or -Rpass=loop-vectorize (Clang) to confirm, rather than inferring it from timing.

Deliverable

If you finish early

Add profile-guided optimisation (PGO): build with instrumentation, run a representative workload, rebuild using the collected profile. Gains of 10–20% are common and almost nobody at student level tries it. Alternatively, extend to Fortran — still the dominant language in much of HPC, and Fortran compilers often beat C compilers on numerical loops for reasons worth explaining (pointer aliasing rules).

T2.3 · Tier 2 · maps to §14 topics 10 & 01
Strong and weak scaling of a real scientific application
Tier 2Sprint Model coreCompetition-transferable

What this is

You take a genuine scientific application — not a benchmark, not a toy — and characterise how its performance changes as you add computational resources. This is exactly what the Sprint Model's applications track asks for, and exactly what a cluster competition judge will probe.

Two distinct questions, often confused:

Real applications almost always strong-scale poorly beyond some point and weak-scale better. Understanding why for your chosen application is the research contribution.

Choosing the application

ApplicationDomainWhy choose itDifficulty to build
GROMACSBiomolecular dynamicsExcellent docs, ships benchmark cases, very widely used, good scaling behaviour to studyModerate
LAMMPSMolecular dynamics (materials)Highly modular, ships many example inputs, connects to the Queen's nuclear-materials workModerate
OpenFOAMComputational fluid dynamicsDirectly relevant to Hyperloop/QADT/Relectric consulting; tutorial cases includedHard — large, complex build
HPCGSparse solver benchmarkEasiest build; competition-scored; but it is a benchmark, not an applicationEasy

Recommendation for a first Tier 2 scaling study: LAMMPS. It builds more easily than OpenFOAM, ships with well-documented benchmark inputs designed for exactly this, and its performance characteristics are well understood so you can check your results against published expectations.

Tier
2 — Developing
Team size
3
Prerequisites
E12 (MPI basics); has built software from source
Cost
$0
Compute
Frontenac — needs multiple nodes
Output
4,000–5,500 words + 4 figures

Week by week

Week 1

Goal: a working build. Budget the whole week — this is genuinely the hard part.

  • Build the application from source on Frontenac. Use the site's module system for compiler and MPI rather than fighting your own toolchain.
  • Keep a detailed build log. Every dependency, every error, every fix. This becomes both the lessons-learned section and — importantly — direct practice for the timed build drills in competition preparation.
  • Run the smallest supplied example and verify it produces physically sensible output.
  • Choose your benchmark case. It must be large enough that a single node takes several minutes, or scaling measurements will be dominated by noise.
Week 2

Goal: the strong scaling curve.

  • Fix the problem size. Run at 1, 2, 4, 8, 16, 32, 64 cores — spanning within-node and across-node.
  • Distinguish intra-node from inter-node scaling. The curve usually bends at the node boundary, where communication moves from shared memory to the network. Marking that point on your plot is a key part of the analysis.
  • Three runs per configuration. Record both total runtime and, if the application reports it, the breakdown by phase.
  • Plot speedup with the ideal line overlaid.
Week 3

Goal: weak scaling, and the beginnings of mechanism.

  • Construct the weak-scaling series: double the problem size each time you double the core count. For molecular dynamics this usually means replicating the simulation cell; for CFD, refining the mesh proportionally.
  • Plot efficiency (time at N cores relative to time at 1 core with proportional work). Flat is perfect.
  • Begin diagnosing. Most applications report internal timing breakdowns — for LAMMPS, the Loop time summary splits pair, neighbour, communication, and output. Watch the communication fraction grow with core count. That growth is your explanation.
Week 4

Goal: analysis and write-up.

  • Produce four figures: strong-scaling speedup, strong-scaling efficiency, weak-scaling efficiency, and a stacked bar of time-breakdown by core count.
  • Identify and defend the limiting factor. Communication? Load imbalance? Serial I/O at the end? Insufficient work per core?
  • Estimate the parallel fraction from your data by fitting Amdahl's Law, and compare to what the time breakdown suggests. Agreement between two independent estimates is a strong result.
  • Write practical guidance: for this application and this problem class, how many cores should a user request?

What good looks like

Both scaling curves with the ideal overlaid and the node boundary marked; a time-breakdown stacked bar showing communication share growing; a defended diagnosis; and a concrete recommendation of the efficient core count for this workload. That last item is what a real user wants and what almost no student report provides.

Failure modes

Problem too small. The most common failure. If the single-core run takes 20 seconds, then at 64 cores you are measuring startup and communication only. Make the base case take minutes.

Changing more than one thing. If you change both problem size and core count while intending strong scaling, you have measured nothing interpretable.

Ignoring the decomposition. Many applications partition the domain across processes. A bad partition at a particular core count can produce an anomalous point. Check whether the decomposition is sensible before declaring a scaling anomaly.

Including I/O in the timing. If the application writes a large output file at the end, and that write is serial, it will dominate at high core counts and mask the real compute scaling. Time the phases separately.

Not checking correctness across core counts. A correctly parallelised application should produce equivalent physics regardless of core count. Verify this — if it doesn't, that is a much more interesting finding than the timings.

Deliverable

If you finish early

Repeat on a second application and compare. Two applications with different communication patterns will show visibly different scaling behaviour, and explaining that difference in terms of their algorithms is a genuinely strong undergraduate result — the kind that would stand out in the CUCAI proceedings given how few systems papers appear there.

Alternatively, add the GPU dimension: most of these applications have GPU-accelerated builds. How does the scaling story change when each node has an accelerator? That is the natural bridge into Tier 3.

T2.4 · Tier 2 · underpins §14 topics 01, 39, 43
The memory wall: why your CPU is usually waiting
Tier 2Explains everything else4 weeks

What this is

Modern processors can perform arithmetic far faster than memory can supply data. A CPU core might execute on the order of a hundred billion floating-point operations per second while its share of memory bandwidth delivers only a few billion values per second. For most real scientific code, the processor spends most of its time idle, waiting for data to arrive. This is the memory wall.

In this project you measure your machines' actual memory bandwidth using STREAM — the standard benchmark for exactly this — then use that number to explain performance results you have already collected in other projects. It is the single most explanatory measurement in this handbook.

Background: arithmetic intensity and the roofline

Every computational kernel has an arithmetic intensity: the number of floating-point operations it performs per byte of data it moves from memory. This single number predicts whether a kernel will be limited by compute or by memory.

KernelRoughly, FLOPs per byteLimited by
Vector addition (c = a + b)~0.08Memory, overwhelmingly
STREAM Triad~0.08Memory — by design; that's the point
Sparse matrix-vector multiply~0.25Memory
Dense matrix multiply (blocked)Grows with block size — can reach 10sCompute, if well implemented
N-body (all pairs)Very highCompute

The roofline model plots achievable performance against arithmetic intensity. It has two regions: a sloped section where memory bandwidth is the ceiling, and a flat section where peak compute is the ceiling. The corner where they meet is the machine's balance point. Any kernel to the left of that corner cannot possibly reach peak FLOPs — no matter how well written.

This explains a great deal that is otherwise mysterious: why HPCG scores are typically only a few percent of HPL scores on the same machine (HPCG is memory-bound, HPL is compute-bound), and why adding cores stops helping in E11 for bandwidth-hungry loops.

Why it matters

Tier
2 — Developing
Team size
2
Prerequisites
E11 (OpenMP) strongly recommended
Cost
$0
Software
STREAM; a compiler; optionally likwid or perf
Output
3,500–4,500 words + roofline plot

Week by week

Week 1

Goal: a trustworthy bandwidth number.

  • Download and build STREAM. It is a single small C file — but building it correctly requires care.
  • Set the array size properly. STREAM requires each array to be at least four times the size of the last-level cache, or you measure cache bandwidth rather than memory bandwidth and get impossibly good numbers. Find your cache size (lscpu), then size accordingly.
  • Compile with optimisation and OpenMP enabled. Run at increasing thread counts up to physical core count.
  • Record all four STREAM kernels: Copy, Scale, Add, Triad. Triad is the one to quote — it is the most representative of real code.
  • Compare your measured bandwidth to the theoretical peak from the memory specification (channels × transfer rate × width). Achieving 70–85% of theoretical is typical and healthy; if you get 20%, something is wrong with your setup.
Week 2

Goal: bandwidth across platforms and thread counts.

  • Repeat on every platform available: laptop, Pi cluster node, Frontenac node, and a Frontenac GPU node if accessible.
  • Plot bandwidth versus thread count for each. The curve saturates — usually well before all cores are in use, because a few cores can already saturate the memory controller. Identify the saturation point.
  • This saturation point is a genuinely important practical result: beyond it, adding cores to a memory-bound job achieves nothing.
  • On a multi-socket machine, investigate NUMA effects — memory attached to a different socket is slower to reach. numactl lets you control placement and demonstrate this.
  • Frontenac gives you a free NUMA testbed. Its AMD EPYC nodes (frnt140–147 EPYC 7443, frnt154 EPYC 7H12, frnt190–191 EPYC 7742, frnt204–205) present as 4 NUMA nodes in Slurm per the CAC's own documentation. Run the same STREAM sweep on an EPYC node and an Intel Xeon node (frnt116–139, Xeon 6338) and the NUMA topology difference shows up directly in the bandwidth-versus-threads curve. That contrast is a strong result and costs nothing but two job submissions.
Week 3

Goal: build the roofline and place real kernels on it.

  • Determine peak compute for your machine: cores × clock × FLOPs per cycle (accounting for vector width and FMA). This is arithmetic, not measurement — but check it against a well-tuned HPL run.
  • Draw the roofline: log-log axes, arithmetic intensity on x, achievable GFLOP/s on y, with the memory-bandwidth slope and compute ceiling.
  • Estimate arithmetic intensity for kernels you have measured in earlier projects — your E4 Mandelbrot, your E11 loop, HPL, HPCG — and plot each as a point.
  • Points close to the roofline are well optimised; points far below have headroom. That distinction is the practical payoff of the whole exercise.
Week 4

Goal: demonstrate the model's predictive power.

  • Take one memory-bound kernel and apply a data-layout optimisation — loop blocking/tiling, or converting array-of-structures to structure-of-arrays. Measure the improvement and check it against what the roofline predicted.
  • Explain a previously puzzling result from an earlier project using the model. This retrospective explanation is often the most satisfying part of the report.
  • Write up.

What good looks like

A measured bandwidth figure within a plausible fraction of theoretical peak, a saturation curve showing where extra cores stop helping, a roofline plot with real kernels placed on it, and at least one case where the model correctly predicted an optimisation outcome. The strongest reports use the roofline to explain an earlier project's result that previously seemed arbitrary.

Failure modes

Arrays too small. The classic STREAM error. If your arrays fit in cache you will report bandwidth several times higher than the hardware can actually deliver, and every downstream conclusion will be wrong. Check against theoretical peak — if you exceed it, your arrays are too small.

Quoting Copy instead of Triad. Copy flatters the machine. Triad is the honest number.

Ignoring NUMA. On a two-socket node, threads reaching across sockets see much lower bandwidth. Report placement explicitly.

Confusing bits and bytes. Memory specifications are often quoted in different units than benchmark output. Getting this wrong by a factor of eight is common and embarrassing.

Treating the roofline as exact. It is a first-order model. Real machines have cache hierarchies it doesn't capture. Present it as a bound and a guide, not a prediction.

Deliverable

If you finish early

Build a hierarchical roofline that includes L1, L2, and L3 cache bandwidth as additional ceilings, not just main memory. This shows why blocking works: it moves a kernel's effective intensity up against a faster ceiling. It is a more sophisticated model and a considerably stronger result.

Alternatively, measure bandwidth on a GPU. High-bandwidth memory changes the balance point dramatically, and comparing the two roofline shapes explains why some kernels benefit enormously from GPUs while others don't — setting up Tier 3's GPU work.

T2.5 · Tier 2 · maps to §14 topic 09
Does the same code give the same answer?
Tier 2Publishable genreBuilds on E9

What this is

You take one program and run it many ways — different compilers, different optimisation levels, different core counts, different machines, different runs of the identical binary — and check whether the numerical results are bitwise identical. Frequently they are not. This project characterises when, why, and by how much.

Background: why identical code produces different numbers

Floating-point addition is not associative. In exact mathematics, (a + b) + c = a + (b + c). In floating point, these can differ, because each operation rounds. Once you accept that, several consequences follow:

None of these is a bug. All of them mean that "reproducible" needs definition. This project's real contribution is establishing, for a given code, which level of reproducibility is achievable and at what performance cost.

Three levels of reproducibility — define which you mean

Bitwise: identical bits, every time, everywhere. Achievable, but usually costs performance (deterministic reductions, disabled vectorisation).

Numerical: results agree to within a stated tolerance appropriate to the science. Usually what actually matters.

Scientific: conclusions are unchanged even if numbers differ. The weakest and most important standard.

Why it matters

Tier
2 — Developing
Team size
2
Prerequisites
T2.2 (compilers) helpful; E12 for the parallel dimension
Cost
$0
Output
4,000–5,000 words + variation analysis

Week by week

Week 1
  • Choose a program whose output is numerical and non-trivial — an iterative solver, a Monte Carlo integration, or a molecular dynamics simulation. Avoid anything whose output is a single small integer.
  • Build the comparison infrastructure: a script that runs the program, captures full-precision output, and compares two outputs reporting maximum absolute difference, maximum relative difference, and whether they are bitwise identical.
  • Print with full precision. Printing to 6 decimal places will hide exactly the differences you're studying. Use %.17g for doubles, or dump binary.
  • Establish the trivial baseline: run the same binary twice on the same machine. Is it bitwise identical? For most deterministic programs it should be — if not, you have found something interesting immediately (uninitialised memory, or a random seed you didn't control).
Week 2
  • Vary the parallel dimension. Run at 1, 2, 4, 8, 16 threads or ranks. Compare all against the serial result.
  • Quantify the divergence: how large is the difference, and does it grow with core count? For iterative or chaotic systems it can amplify substantially.
  • Investigate whether the difference is in the last few bits (harmless rounding) or larger (potentially a real problem).
Week 3
  • Vary the toolchain: different compilers, optimisation levels, with and without FMA (-ffp-contract=off), with and without -ffast-math.
  • Vary the hardware: same source, same flags, different machine architectures (x86 vs the Pi cluster's ARM).
  • Build the results matrix. Which factors matter most? Usually: -ffast-math ≫ core count ≈ FMA > compiler > hardware.
Week 4
  • Attempt to achieve bitwise reproducibility. Deterministic reduction ordering, -ffp-contract=off, disabled fast-math. Then measure the performance cost. That trade-off — reproducibility versus speed, quantified — is the strongest result this project can produce.
  • Write up with a practical recommendation: for this class of code, what should a researcher do?

What good looks like

A matrix showing which factors cause divergence and by how much, an analysis of whether the divergence is scientifically meaningful for the application, and a quantified cost of enforcing bitwise reproducibility. A report that concludes "differences were at the 1e-15 level and do not affect the physical conclusions" is a perfectly good and honest outcome — provided you demonstrated it rather than assumed it.

Failure modes

Insufficient output precision. The commonest error. Six decimal places hides everything.

Uncontrolled randomness. If your program uses a random number generator, seed it explicitly and confirm the generator itself is deterministic across platforms — many are not.

Confusing "different" with "wrong." Bitwise divergence at the 1e-16 level is expected and harmless. Panic here is a misreading. The question is always whether it matters for the science.

Not distinguishing the amplification case. In chaotic or iterative systems, tiny differences grow. Distinguishing "small and stays small" from "small and grows" is the analytically important part.

Deliverable

If you finish early

Investigate compensated summation (Kahan summation) — an algorithm that dramatically reduces accumulated rounding error at modest cost. Measure both its accuracy benefit and its performance cost. It is a genuinely elegant result and it is the kind of thing that impresses a competition judge, because it demonstrates you understand the arithmetic rather than just the tooling.

Tier 3Intermediate— second-year members; can modify a program; understands parallelism

What changes at Tier 3

Tiers 1 and 2 measured systems. Tier 3 modifies them. You are now writing or substantially altering code, configuring services, and producing results that could plausibly appear in conference proceedings. Three further expectations apply:

Tier 3 projects also routinely overrun a single four-week sprint. Plan for two sprints, or scope deliberately to fit one and treat the rest as future work — explicitly, in the report.

T3.1 · Tier 3 · maps to §14 topic 03
Scheduling policy: who gets the cluster, and when?
Tier 3QDES partnership4–8 weeks

What this is

A cluster has finite nodes and more jobs than nodes. The scheduler decides what runs next. That decision is a genuine algorithmic problem with real trade-offs between throughput, fairness, and responsiveness — and Slurm exposes enough configuration to study it empirically.

You construct a synthetic but realistic workload, then run it repeatedly under different scheduler configurations on the Pi cluster, measuring makespan, wait times, utilisation, and fairness.

Use the Pi cluster. Never experiment on Frontenac.

This project requires reconfiguring the scheduler and draining queues. Doing that on a production cluster serving 1,000+ researchers is not acceptable under any circumstances. The Pi cluster exists precisely so that this kind of work is safe — which is exactly the teaching-versus-research split the operating plan's §17c argues for.

Background: the policies you're comparing

PolicyRuleStrengthWeakness
FIFOStrictly first-come, first-servedPerfectly fair by arrival; trivially predictableA single wide job blocks everything behind it while nodes sit idle
BackfillFIFO, but smaller jobs may jump ahead if they finish before the blocked job's resources are readyDramatically better utilisation; no job is delayed relative to FIFODepends entirely on accurate walltime estimates from users
Fair-sharePriority adjusted by recent usage — heavy users are deprioritisedPrevents monopolisation on multi-user systemsCan starve legitimate heavy users; needs tuning
Priority/QoS tiersExplicit classes with different weightsSupports policy goals (e.g. teaching before research)Administrative overhead; political

The backfill insight is the interesting one. Its entire advantage comes from filling gaps around wide jobs — which means it only shows benefit if your synthetic workload actually contains wide jobs and heterogeneous durations. A workload of uniform single-node jobs will show no difference between FIFO and backfill, and a team that constructs such a workload will wrongly conclude backfill doesn't work.

Why it matters

Tier
3 — Intermediate
Team size
3
Prerequisites
E8 (job arrays); comfortable with Slurm; some scripting
Cost
$0
Hardware
Pi cluster with administrative access
Output
5,000–7,000 words + 4–5 figures

Designing the synthetic workload

This is the analytically critical step and where most of the intellectual work lies. A realistic HPC workload has:

Generate the workload once, save it as a fixed file, and replay the identical workload under every policy. If the workload differs between runs, the comparison is meaningless.

Week by week

Weeks 1–2

Goal: reproducible experimental harness.

  • Build the workload generator. Parameters: job count, width distribution, duration distribution, arrival process, number of users, walltime over-request factor.
  • Write the replay harness — submits the workload according to its arrival timestamps.
  • Write the metrics collector, pulling from sacct: submit time, start time, end time, nodes, user.
  • Validate the whole pipeline on a tiny workload (20 jobs) before scaling up.
  • Establish the baseline: run the workload twice under identical configuration. How reproducible is the measurement itself? That variance sets your detection threshold.
Weeks 3–4

Goal: the policy comparison.

  • Configure and run each policy in turn: FIFO, backfill, fair-share. Drain the queue completely between runs.
  • Collect the full metric set for each: makespan, mean and 95th-percentile wait, node utilisation over time, per-user fairness.
  • Plot utilisation as a timeline — the visual difference between FIFO's idle gaps and backfill's filled ones is often the most persuasive figure in the report.
Weeks 5–6 (if extending)

Goal: sensitivity analysis — where the real insight is.

  • Vary the walltime over-request factor from 1× (perfect estimates) to 5× (typical user pessimism). Backfill's advantage should degrade as estimates worsen, because the scheduler cannot find gaps it believes are too small. Quantifying that degradation is a genuinely valuable result.
  • Vary the job-width mix. At what fraction of wide jobs does backfill start to matter?
  • Vary system load — is the policy difference larger under light or heavy load?
Final week
  • Produce the figures and the recommendation.
  • Write the related-work section citing actual scheduling literature.
  • Send the findings to the CAC — this is the deliverable that makes the project a consulting engagement rather than an exercise.

Metrics to collect

MetricDefinitionWhose interest it represents
MakespanTime from first submission to last completionThe administrator
Mean wait timeAverage of (start − submit)The average user
95th-percentile waitThe tailThe unluckiest user — often what people actually complain about
Node utilisationFraction of node-seconds actually usedThe funder
Slowdown(wait + runtime) / runtimeFairness to short jobs specifically
Per-user fairnessSpread of resources delivered across usersThe community

Report several. They conflict — a policy optimising makespan often worsens the wait-time tail — and demonstrating that conflict is more valuable than declaring a winner.

Failure modes

A homogeneous workload. All jobs the same size and duration means all policies perform identically and you have learned nothing. Heterogeneity is the entire experiment.

Not draining between runs. Leftover jobs from the previous configuration contaminate the next.

A workload too short. Scheduling effects need queueing. If jobs never wait, there is nothing to schedule.

Reporting a single metric. Declaring backfill "best" on makespan alone hides its effects on fairness.

Ignoring measurement variance. Establish your noise floor in week 2 and only claim differences that exceed it.

Deliverable

If you finish early

Add a carbon-aware dimension, connecting to §14 topic 37: defer delay-tolerant jobs into low-carbon-intensity windows using public Ontario grid data, and measure the emissions reduction against the makespan cost. Published work in this area reports substantial reductions for bounded delay, and replicating that at small scale with a real scheduler would be a strong and very current contribution.

Alternatively, replay a real workload trace rather than a synthetic one. Several public HPC workload archives exist, and using one substantially strengthens the realism claim.

T3.2 · Tier 3 · maps to §14 topic 39
Sparse matrices: the kernel underneath everything
Tier 3Explains HPCG4–8 weeks

What this is

Most large scientific problems produce matrices that are overwhelmingly zero. A finite-element mesh with a million nodes yields a million-by-million matrix — a trillion entries — of which perhaps ten million are non-zero. Storing it densely is impossible; storing only the non-zeros is routine.

The core operation on such matrices is sparse matrix-vector multiplication (SpMV): y = A·x. It is the innermost loop of virtually every iterative solver, which means it is the innermost loop of most computational science. It is also notoriously inefficient — typically achieving a few percent of a machine's peak FLOPs.

In this project you implement SpMV in several storage formats, measure them across matrices with different structures, and explain the performance differences in terms of memory access patterns.

Background: why SpMV is slow

Three reasons, all of which your measurements should demonstrate:

  1. Low arithmetic intensity. For each non-zero you load a value, load a column index, load an element of x, and perform one multiply-add. That is roughly 12–16 bytes moved per 2 FLOPs — an intensity around 0.15. Per T2.4's roofline, such a kernel is hopelessly memory-bound and can never approach peak compute.
  2. Irregular access to x. The column indices are arbitrary, so reads from the input vector jump around memory unpredictably. Caches and hardware prefetchers, which assume sequential access, are defeated.
  3. Load imbalance. Rows have wildly differing numbers of non-zeros. In a naïve parallel decomposition by row, some threads get far more work than others.

Understanding these three explains why HPCG — which is essentially a sparse solver benchmark — scores so much lower than HPL on identical hardware. That connection makes this project directly competition-relevant.

The storage formats

FormatHow it storesGood forBad for
COO
Coordinate
Three arrays: row index, column index, value — one entry per non-zeroSimplicity; easy construction; easy to convert fromWasteful (stores row index redundantly); poor locality
CSR
Compressed Sparse Row
Values and column indices, plus a row-pointer array marking where each row startsThe default. Compact, good row-wise access, well supported everywhereLoad imbalance when row lengths vary a lot
CSC
Compressed Sparse Column
CSR transposed — compressed by columnColumn operations; transpose productsSpMV requires either atomics or a reduction
ELL
ELLPACK
Pads every row to the length of the longest, storing a rectangular arrayRegular access; excellent on GPUs where uniformity mattersCatastrophic if one row is much longer than the rest — the padding dominates
HYB
Hybrid
ELL for the bulk, COO for the overflowing long rowsCombines the strengths; common on GPUsMore complex; a tuning parameter to choose
The central hypothesis worth testing

No format is universally best — the winner depends on the matrix's structure. ELL should beat CSR on matrices with uniform row lengths and lose badly on matrices with a few very dense rows. Demonstrating that empirically, and predicting the winner from a structural statistic before measuring, is the strongest possible outcome for this project.

Tier
3 — Intermediate
Team size
2–3
Prerequisites
T2.4 (roofline) strongly recommended; comfortable writing C
Cost
$0
Data
SuiteSparse Matrix Collection — the standard public repository of real sparse matrices
Output
5,000–7,000 words + 4–5 figures

Week by week

Weeks 1–2

Goal: correct implementations and a matrix corpus.

  • Download 8–12 matrices from the SuiteSparse collection, chosen to span structural diversity: a structured mesh matrix, a power-network matrix, a social-graph matrix (highly irregular), and a circuit-simulation matrix. Diversity is the experiment — a corpus of similar matrices proves nothing.
  • Write a Matrix Market file reader, or use an existing one.
  • Implement SpMV in COO, CSR, and ELL. Verify all three produce identical results against a reference before timing anything.
  • Characterise each matrix: dimensions, non-zero count, density, mean row length, standard deviation of row length (this is the statistic that will predict ELL's performance), and bandwidth.
Weeks 3–4

Goal: the performance matrix and its explanation.

  • Time all formats on all matrices, single-threaded first. Report GFLOP/s (counting 2 FLOPs per non-zero) — this normalises across differently sized matrices.
  • Compute achieved memory bandwidth for each run and compare against your STREAM number from T2.4. SpMV should be achieving a large fraction of peak bandwidth while achieving a tiny fraction of peak FLOPs. Demonstrating that directly is the core insight of the project.
  • Plot performance against your structural statistics. Does row-length variance predict the CSR/ELL crossover?
  • Place the results on your roofline from T2.4.
Weeks 5–6 (if extending)

Goal: parallelisation and load balance.

  • Parallelise CSR SpMV with OpenMP. The naïve approach assigns equal rows per thread; a better one assigns equal non-zeros.
  • Measure both. On irregular matrices the difference can be substantial, and it is a clean demonstration of load imbalance as a distinct phenomenon from communication or bandwidth.
  • Investigate whether reordering the matrix (e.g. Reverse Cuthill-McKee) improves locality and therefore performance. This is a classic technique and testing it is a genuine contribution at undergraduate level.
Final week
  • Produce a format-selection heuristic: given a matrix's structural statistics, which format should you choose? Validate it by holding out matrices from your corpus.
  • Write up with a proper related-work section — sparse formats have a substantial literature.

What good looks like

A performance table across formats and matrices; a demonstration that SpMV saturates memory bandwidth while achieving single-digit percentages of peak FLOPs; a structural statistic that predicts format choice; and a validated selection heuristic. Connecting the result explicitly back to why HPCG behaves as it does elevates the report considerably.

Failure modes

A homogeneous matrix corpus. If all your matrices come from structured meshes, every format performs similarly and the study has no findings. Deliberately include a highly irregular matrix — social graphs are ideal.

Not verifying correctness. ELL padding in particular is easy to get subtly wrong.

Timing the format conversion. Converting COO to CSR is a one-time preprocessing cost. Unless you're studying that specifically, time only the SpMV itself, and say so.

Ignoring the vector. If x fits entirely in cache, the irregular-access penalty vanishes and your results won't generalise. Use matrices large enough that x exceeds cache.

Reporting FLOPs without saying how you counted. The convention is 2 FLOPs per non-zero. State it.

Deliverable

If you finish early

Port CSR SpMV to the GPU and compare against cuSPARSE. GPUs change the format calculus entirely — ELL's regularity becomes far more valuable because GPU threads execute in lockstep. Demonstrating that the best CPU format is not the best GPU format is a strong result and a natural bridge into Tier 4.

Alternatively, connect to the Queen's geomechanics or nuclear-materials work in §14 topics 48–49 — those simulations are full of sparse solvers, and characterising a matrix from a real local research problem turns this into a partnership.

T3.3 · Tier 3 · maps to §14 topic 43
Parallel I/O: the bottleneck nobody measures
Tier 3Directly useful to CAC4–8 weeks

What this is

When a thousand processes all want to write results at once, the filesystem becomes the bottleneck — and the difference between a good and bad I/O strategy can be two orders of magnitude. This project characterises parallel filesystem behaviour on Frontenac using the standard tools, then demonstrates the practical consequences of common I/O patterns.

It extends E7 from single-process timing into genuine parallel I/O, and it addresses what is, in practice, the most common performance problem real researchers hit.

Background: the patterns and why they differ

PatternWhat happensTypical outcome
File-per-processEach of N processes writes its own fileSimple and often fast — until N is large, when metadata operations overwhelm the filesystem
Single shared file, independentAll processes write to different offsets in one fileFewer files, but can cause lock contention depending on alignment
Single shared file, collectiveMPI-IO coordinates processes, aggregating small writes into large contiguous onesUsually the best for large runs — this is what collective I/O exists for
Serialised through rank 0All processes send data to one rank, which writesSimple to reason about, and a severe bottleneck at scale. Common in student code.

Parallel filesystems (Lustre, GPFS, BeeGFS) distribute a file across multiple storage targets. Stripe count controls how many; stripe size controls the chunk granularity. These are tunable per-file and per-directory, and the defaults are frequently wrong for a given workload — which makes them an easy source of real improvement.

Etiquette on a shared filesystem

This project generates heavy I/O on infrastructure shared with 1,000+ researchers. Before starting: check quotas, agree a testing window with CAC staff if the load will be significant, clean up test data immediately, and never run large metadata tests during business hours. Being a considerate tenant is also how QHPC builds the CAC relationship that §16 depends on — a team that trashes the scratch filesystem will not be invited to collaborate.

Tier
3 — Intermediate
Team size
2–3
Prerequisites
E7, E12; comfortable with batch jobs at multi-node scale
Cost
$0
Software
IOR, mdtest (both from the ior repository); optionally Darshan
Output
5,000–7,000 words + 5 figures

Week by week

Weeks 1–2

Goal: baseline characterisation.

  • Build IOR and mdtest. Learn the key flags — particularly -a (API: POSIX vs MPIIO), -b (block size), -t (transfer size), -F (file-per-process), and -c (collective).
  • Establish single-node baselines: sequential read and write bandwidth at several transfer sizes. Transfer size matters enormously — small transfers are dominated by per-operation overhead.
  • Establish the noise floor by repeating an identical test several times at different times of day. On a shared filesystem this variance is large, and quantifying it is necessary before any comparison is meaningful.
Weeks 3–4

Goal: scaling and pattern comparison.

  • Scale process count: 1, 2, 4, 8, 16, 32, 64 processes across multiple nodes. Plot aggregate bandwidth. It will rise then plateau — find the plateau.
  • Compare access patterns at fixed process count: file-per-process versus shared-file-independent versus shared-file-collective.
  • Run mdtest for metadata: file creation, stat, and removal rates at scale. Metadata is usually where things fall over, and the numbers are often startling — a filesystem sustaining gigabytes per second may manage only a few thousand file creations per second.
Weeks 5–6 (if extending)

Goal: tuning, and a real-application demonstration.

  • Vary stripe settings if the filesystem exposes them. Measure the effect of stripe count on large-file bandwidth — the relationship is usually clear and the improvement can be several-fold.
  • Then demonstrate it on something real. Take an application from T2.3 that writes output, measure its I/O time, apply what you've learned (better transfer sizes, collective I/O, appropriate striping), and measure the improvement. This converts a benchmark study into a demonstrated result.
  • If available, use Darshan to profile the application's actual I/O behaviour — it reveals patterns developers are often unaware of.
Final week
  • Produce a practical I/O guide for Frontenac users: recommended transfer sizes, when to use collective I/O, what to avoid.
  • Write up and send the guide to the CAC.

What good looks like

Bandwidth-versus-process-count curves showing the plateau; a clear pattern comparison; metadata rates that put the small-files problem in perspective; and — ideally — a real application whose I/O time you measurably reduced. That last item is what makes this a consulting deliverable rather than an exercise.

Failure modes

Page cache contamination. Reading a file just written returns it from memory. Use IOR's reorderTasks option, or files far larger than aggregate node memory.

Testing during peak load. Results will be dominated by other users. Record time of day, repeat, and report variance honestly — or better, negotiate a quiet window.

Transfer size too small. Testing only 4 KB transfers measures per-operation overhead, not the filesystem's capability. Sweep from kilobytes to tens of megabytes.

Filling the filesystem. Multi-node IOR runs generate a great deal of data quickly. Check quota first, clean up immediately, and set a hard size cap in your scripts.

Comparing across filesystems without saying so. Home, scratch, and project directories often have entirely different backing hardware and policies. Label everything.

Deliverable

If you finish early

Investigate higher-level I/O libraries — HDF5 or NetCDF, which most scientific applications actually use. They sit on top of MPI-IO and their tuning parameters interact with the filesystem in ways that are poorly documented and genuinely worth characterising. Comparing raw MPI-IO against HDF5 for the same access pattern is a useful and rarely-done comparison.

T3.4 · Tier 3 · maps to §14 topic 38
Predicting job failure from scheduler logs
Tier 3ML meets systemsCUCAI-shaped

What this is

Every job a scheduler runs leaves a record: requested resources, actual usage, exit state, queue time, user, partition, walltime requested versus consumed. Across thousands of jobs this is a substantial operational dataset — and a large fraction of jobs fail, often for predictable reasons (out of memory, exceeded walltime, bad path).

You apply machine learning to that data to predict, at submission time, whether a job will fail. Then — more usefully — you interpret the model to identify which submission characteristics predict failure, and turn that into actionable guidance.

Why this one is strategically well-chosen

It sits exactly at the intersection of ML and systems, which means it is legible to the CUCAI audience — a conference whose corpus is overwhelmingly ML-application work — while being genuinely HPC. It is the single most natural bridge between QHPC and QMIND, and a strong candidate for a joint submission.

It also requires no scientific domain knowledge, which makes it accessible to a broad set of members.

Where the data comes from

Three options, in descending order of value and ascending order of ease:

  1. Frontenac accounting data via the CAC. Real, local, and directly actionable — but requires their cooperation and careful attention to privacy. Ask early; this is a conversation, not a download.
  2. Public HPC workload traces. Several archives of anonymised scheduler logs from real supercomputing centres exist and are used routinely in scheduling research. Immediately available and citable.
  3. Your own Pi cluster logs, augmented by deliberately submitting jobs designed to fail in various ways. Smallest and least realistic, but entirely under your control and useful for validating the pipeline.
Privacy — settle this before requesting data

Scheduler logs contain usernames, job names, and sometimes file paths — all of which can identify individuals and reveal what research they are doing. If you obtain real data from the CAC:

  • Request it pre-anonymised — user IDs hashed, job names and paths stripped — rather than anonymising it yourself.
  • Agree in writing what may be published. Aggregate statistics, yes; anything traceable to an individual researcher, no.
  • Confirm with the faculty advisor whether ethics review applies. Operational data about identifiable people is not obviously exempt.
  • Store it appropriately and delete it when the project ends.

Getting this right is also how QHPC demonstrates it can be trusted with institutional data — which matters far more to the §16 consulting strategy than any single result.

Tier
3 — Intermediate
Team size
3
Prerequisites
Basic ML (any intro course or equivalent); Python/pandas
Cost
$0
Output
5,000–7,000 words + model evaluation

Week by week

Weeks 1–2

Goal: data secured and understood.

  • Open the data conversation with the CAC in week 1. In parallel, download a public trace so you are never blocked.
  • Exploratory analysis. What fraction of jobs fail? What are the failure states (FAILED, TIMEOUT, OUT_OF_MEMORY, CANCELLED)? These are different phenomena and should probably be modelled separately — a user cancelling a job is not the same event as a job being killed for exceeding memory.
  • Establish the base rate. If 15% of jobs fail, a model that always predicts "success" is 85% accurate and completely useless — which is why accuracy is the wrong metric here.
Weeks 3–4

Goal: a model that beats the baseline, evaluated honestly.

  • Feature engineering — and this is where the care is required. Only use features available at submission time. Requested memory, requested nodes, requested walltime, partition, user's historical failure rate, time of day, job name patterns.
  • Do not use post-hoc features. Actual memory used, actual runtime, and exit code are only known after the job ran. Including them produces a model with excellent metrics and zero utility. This is the single most common error in this genre and reviewers will catch it.
  • Split by time, not randomly — train on earlier jobs, test on later ones. Random splits leak information across the temporal boundary.
  • Train a simple baseline (logistic regression) and a tree ensemble (random forest or gradient boosting). Report precision, recall, F1, and AUC — not accuracy.
Weeks 5–6 (if extending)

Goal: interpretation — the part that actually helps anyone.

  • Extract feature importances. What actually predicts failure? Common findings: a user's own recent failure rate is highly predictive; requesting unusual resource combinations correlates with failure; very short walltime requests predict timeouts.
  • Translate into guidance. "Jobs requesting more than X GB per core fail at 4× the base rate" is something a centre can act on.
  • Consider the deployment question honestly: if this warned users at submission, what precision would be needed before the warnings became noise people ignore? A model with 40% precision would cry wolf constantly.
Final week
  • Write up with a proper related-work section — HPC failure prediction has an established literature worth citing.
  • Prepare the CUCAI submission version. This project is well suited to it.

What good looks like

A model that meaningfully outperforms the base rate on temporally held-out data, evaluated with appropriate metrics, using strictly submission-time features — plus an interpretation section producing concrete guidance. An honest negative result ("failure is largely unpredictable from submission metadata alone, and here is the evidence") is also a perfectly publishable outcome, provided the methodology is sound.

Failure modes

Target leakage. Using post-hoc features. Produces a model with 99% AUC that cannot be deployed. Audit every feature against the question "would I know this at submission time?"

Reporting accuracy on imbalanced data. Meaningless when the base rate is skewed.

Random train/test split on temporal data. Leaks future information; inflates results.

Treating all failures as one class. Timeouts, OOM kills, and user cancellations have different causes and different predictability. Model separately or justify combining them.

Publishing anything traceable to an individual. Beyond being an ethical failure, it would end the CAC relationship permanently.

Deliverable

If you finish early

Extend from failure prediction to runtime prediction — estimating how long a job will actually take. This is directly valuable to the scheduler: T3.1 showed that backfill's effectiveness depends entirely on walltime-estimate accuracy. A model that improves those estimates would measurably improve cluster utilisation, and combining T3.1 and T3.4 into a single study — "can ML-improved walltime estimates improve backfill performance?" — is a genuinely strong piece of work and an excellent joint QHPC/QMIND submission.

T3.5 · Tier 3 → 4 bridge · maps to §14 topic 12
Your first GPU port
Tier 3→4Most competition-relevantMost commonly done badly

What this is

You take a CPU kernel you already understand — ideally one you measured in an earlier project — and port it to run on a GPU. You then measure the speedup honestly, find the problem size below which the GPU loses, and explain the result in terms of the hardware.

This is the bridge topic. It is the last runbook in this volume and the prerequisite for most of the Advanced Volume. It is also, by a wide margin, the topic most often done badly — for one specific reason covered below.

The error that invalidates most student GPU papers

Comparing an optimised GPU implementation against an unoptimised CPU baseline.

It is extremely common to see "we achieved 50× speedup" where the CPU version was single-threaded, compiled at -O0, and written without regard for cache behaviour — while the GPU version was carefully tuned. That comparison measures effort, not hardware.

An honest baseline is multi-threaded (OpenMP across all cores), compiled at -O3 -march=native, and vectorised. Against such a baseline, realistic speedups for memory-bound kernels are typically 2–10×, not 50×. Reporting a smaller, honest number with a defensible baseline is worth far more — to a reviewer, to a competition judge, and to anyone who has to make a purchasing decision — than a large dishonest one.

State your baseline explicitly and prominently. Reviewers look for this first.

Background: why GPUs are fast, and when they aren't

A CPU has a few dozen sophisticated cores optimised for latency — deep pipelines, large caches, aggressive branch prediction. A GPU has thousands of simple cores optimised for throughput, executing in lockstep groups (warps of 32 on NVIDIA hardware), paired with very high memory bandwidth.

That architecture implies exactly when GPUs win:

Property of your problemGPU outcomeWhy
Massive data parallelism (thousands of independent items)ExcellentFills the machine; that's what it's for
High arithmetic intensityExcellentCompute units stay busy
Regular, coalesced memory accessExcellentAdjacent threads reading adjacent addresses is a single wide transaction
Heavy branching that diverges within a warpPoorBoth branch paths execute serially for the whole warp
Irregular / random memory accessPoorUncoalesced access can cost 32 transactions instead of 1
Small problem sizeLoses to CPUTransfer over PCIe plus launch overhead exceeds the compute saved
Sequential dependenciesPoorNo parallelism to exploit

Choosing your kernel

For a first port, pick something with obvious data parallelism and no tricky dependencies. In rough order of suitability:

Tier
3 → 4 bridge
Team size
2–3
Prerequisites
T2.4 (roofline); comfortable in C; ideally one prior kernel measured
Cost
$0 — Frontenac: 88 GPUs, 25 A100s
Software
CUDA toolkit; nsys and ncu for profiling
Output
5,000–7,000 words + 4 figures

Week by week

Week 1

Goal: an honest CPU baseline. Do not skip this week.

  • Take your kernel and optimise the CPU version properly: OpenMP across all physical cores, -O3 -march=native, confirmed vectorisation (-fopt-info-vec).
  • Measure it thoroughly across your problem-size range. This is the number every subsequent claim is measured against.
  • Place it on your roofline from T2.4. How close to the CPU's ceiling are you already? If the CPU version is at 80% of its roofline, a large GPU speedup is implausible and you should expect a modest one.
  • Get a Frontenac GPU allocation and confirm you can compile and run a trivial CUDA program on it. Pick your node deliberately — the hardware is heterogeneous and the choice changes your results:
NodeGPUsUse for
frnt1071× A100 40 GBStart here. Single modern GPU, no multi-GPU complications
frnt1101× V100 32 GBOlder architecture — good for a generational comparison
frnt140–1472× A30 24 GBEight such nodes — best availability, least queue wait
frnt1568× RTX8000 48 GBLargest GPU memory on the system. For anything memory-hungry.
frnt1548× A100 40 GBMulti-GPU work; the distributed-training bridge (Vol II T5.5)
frnt190–191DGX A100, 8× A100Purpose-built DGX systems — the strongest nodes available
  • Report which node you used. A speedup measured on an A30 is not comparable to one measured on a DGX A100, and reviewers will ask.
Week 2

Goal: a correct GPU version. Correct first, fast later.

  • Write the naïve port: allocate device memory, copy input, launch one thread per data element, copy results back.
  • Verify correctness against the CPU output immediately. Expect small floating-point differences (see T2.5) — but the results must agree to within tolerance.
  • Time it, including transfers. Your first version may well be slower than the CPU. That is normal and is a legitimate data point.
  • Learn cudaGetLastError() — CUDA fails silently by default, and a kernel that never launched returns instantly with untouched output, which looks like an impossibly fast result.
Week 3

Goal: optimise, guided by the profiler rather than by guesswork.

  • Profile with nsys (timeline: where does wall time go — transfer or compute?) and ncu (kernel detail: occupancy, memory throughput, warp efficiency).
  • Address the biggest item first. Typically in order: reduce transfers (keep data resident on device across iterations), coalesce memory access (adjacent threads → adjacent addresses), tune block size, then use shared memory for data reused within a block.
  • Measure after each change. Keep a log of what each optimisation bought — that log is a substantial part of the report's value.
  • Separate the timings explicitly: kernel time, transfer time, total time. Reporting only kernel time is a form of the baseline error — real users pay for transfers.
Week 4

Goal: the crossover, and honest analysis.

  • Sweep problem size across several orders of magnitude and find the crossover point — where GPU total time (including transfers) drops below the optimised CPU time. Below it, the GPU is the wrong tool. This is the single most practically useful number in the report.
  • Compute achieved memory bandwidth and achieved FLOPs; place the GPU result on a GPU roofline. How close to that ceiling did you get?
  • If your kernel has a library equivalent (cuBLAS, cuFFT, cuSPARSE), compare against it. You will probably lose — vendor libraries embody enormous tuning effort. Reporting that honestly, and explaining what they do that you didn't, is a mature and valuable conclusion.
  • Write up.

What to report

QuantityWhy it must appear
CPU baseline configurationCores used, flags, vectorisation confirmed. Without this the speedup is uninterpretable.
Kernel time vs total timeSeparating them shows how much of the win transfers consume
Speedup vs problem sizeA single speedup number hides the whole story
Crossover pointThe actionable result
Achieved bandwidth / occupancyShows whether you used the hardware well or merely used it
Optimisation logWhat each change bought — the most instructive part for future readers
Library comparisonContextualises your result against what is achievable

What good looks like

A speedup-versus-problem-size curve with the crossover marked, measured against a genuinely optimised multi-threaded CPU baseline, with transfers included in the headline number and separated in the analysis. Plus a profiler-guided optimisation log showing what each change achieved. A modest, well-defended 4× beats an undefended 40× every time.

Failure modes

The unoptimised baseline. Covered above. The defining error of the genre.

Excluding transfer time. Unless data genuinely stays resident on device for the whole application, transfers are part of the cost.

Not checking CUDA errors. A silently failed launch looks like infinite speed.

Not synchronising before timing. Kernel launches are asynchronous. Without cudaDeviceSynchronize() you time the launch call, not the work — and will report microsecond kernels.

Ignoring warp divergence. If threads in a warp take different branches, both paths execute. Mandelbrot has this property inherently; noticing and quantifying it is a good result.

Reporting only the best run. Same standards as every other project: median of several, with spread.

Deliverable

If you finish early — and where this leads

Port a second kernel with opposite characteristics. If your first was compute-bound and regular, do SpMV from T3.2 — memory-bound and irregular. The contrast between an 8× speedup and a 1.5× speedup, explained in terms of coalescing and arithmetic intensity, is a far stronger result than either alone.

Alternatively compare programming models: CUDA against OpenMP target offload or OpenACC. The directive-based approaches require far less code change — how much performance do they leave on the table? That question opens directly into the Advanced Volume's portability work.

Where this volume ends, and what comes next

Tiers 1–3 take a member from "has never used a cluster" to "can execute a controlled performance study, modify systems, and write a conference-submittable report." That is the full arc of a first and second year in QHPC, and it covers everything the Sprint Model needs to run.

Tiers 4 and 5 are published separately as the Advanced Volume, because they differ in kind rather than degree: they assume a faculty or graduate co-supervisor, they routinely span multiple terms, and several require domain knowledge from outside computing. That volume covers:

Tier 4 · AdvancedTier 5 · Frontier
Mixed precision and HPL-MxPRadiation damage in zirconium alloys (RMTL / MAX-Nuclear)
Performance portability across programming modelsRock mass modelling for deep mining (QGGG / Buchan)
Collective communication tuningCardiovascular CFD and reduced-order models
Carbon-aware schedulingParticle-astrophysics data reduction (SNO Institute)
Quantum circuit simulationDistributed training at scale
Digital twin of the clusterMulti-institution reproducibility studies
A note on sequencing

Do not route members into Tier 4 before they have completed at least one Tier 2 and one Tier 3 project. The Advanced Volume assumes the measurement discipline, baseline rigour, and profiling fluency this volume builds. A student who arrives at mixed-precision work without having internalised T2.4's roofline or T3.5's baseline standards will produce results that do not survive review.

ReferenceNarrowing a topic into a question— the skill that decides whether a sprint produces anything

Every runbook above is a subject area. Week 1 turns it into a question.

The runbooks are written broadly on purpose: a topic has to accommodate several teams, several cohorts, and several levels of ambition. But a team that starts work still holding the broad version will not finish. Breadth is what causes a sprint to end with twelve half-measurements and no figure — the team kept finding new things worth trying because nothing told them what was out of scope.

Narrowing is a skill, it takes about an hour, and it should happen in week 1 before any code is written.

How to narrow, in four moves

  1. Find the number. Turn the topic into a quantity you will end up reporting. "Compression" becomes "compression ratio at fixed throughput." "Scaling" becomes "the core count at which efficiency drops below 70%." If you cannot name the number, you cannot design the experiment.
  2. Pick one thing to vary. Everything else is held fixed and written down as held fixed. Two variables is a much harder study than one and rarely twice as interesting; three is how sprints die.
  3. Write the answer you expect. One sentence, before measuring. If it feels too obvious to bother writing, the question is too easy — narrow toward the part you genuinely cannot predict.
  4. Write what would prove you wrong. If nothing would, you have a demonstration rather than an experiment. This is the move most teams skip and the one that most improves the eventual report.
The test that catches a too-broad question

Say the question out loud and ask: could a competent team answer this in four weeks and be done? "How does container overhead affect HPC applications" — no, that is a career. "Does containerising this specific workload cost more runtime than run-to-run noise on this node" — yes, that is four weeks, and it has a real answer that could go either way.

The narrower question is not the lesser one. It is the one that produces a figure, a finding, and a report someone will read.

Worked narrowings for every topic in this volume

These are starting points, not assignments. A team that sharpens further after its first measurements has done exactly the right thing.

Tier 1 · Foundation

TopicToo broadThe question to actually run
E1"Is the cluster faster than a laptop?"At what problem size does total time-to-answer on the cluster including queue wait fall below the laptop — and how far does that crossover move between a quiet queue and a busy one?
E2"Demonstrate Amdahl's Law."At a fixed parallel fraction of 90%, at what core count does measured speedup fall 20% below Amdahl's prediction, and does that point move when the problem size grows?
E3"Benchmark compression tools."For scientific binary array data, which compressor sits on the Pareto frontier at a 1 Gb/s link and at a 10 Gb/s link — and does compress-then-transfer beat raw transfer at either speed?
E4"Compare Python, NumPy and C."At what problem size does NumPy's advantage over pure Python stop growing, and how much of the remaining gap to C is interpreter overhead rather than memory layout?
E5"Analyse cloud costs."For one representative club workload, at what monthly utilisation does owning the Pi cluster cost less than renting equivalent cloud capacity, once electricity is included?
E6"Write a build guide."Can a member who has never used the cluster get a first job running in under 60 minutes from the written guide alone — and at which specific step do most of them stall?
E7"Time the filesystems."At what file count does the home-versus-scratch difference for many-small-file workloads exceed 2×, and does the gap depend on file size or only on count?
E8"Learn job arrays."For a sweep of N jobs, at what N does a job array beat N individual submissions on total time-to-completion — and is the difference scheduler overhead or queue policy?
E9"Reproduce a paper."Working only from the paper and its published artifacts, how close can the headline figure be reproduced — and what specific missing information accounts for the residual?
E10"Survey campus compute."Among surveyed research groups, what fraction describe themselves as compute-bound, and of those, what fraction are unaware that a national allocation is available to them?
E11"Study OpenMP scaling."At what thread count does this kernel stop scaling, and is the limit memory bandwidth, synchronisation, or load imbalance? Evidence required for whichever you claim.
E12"Learn MPI."For a fixed problem size, at what process count does communication time exceed computation time — and does changing the domain decomposition move that point?

Tier 2 · Developing

TopicToo broadThe question to actually run
T2.1"Measure container overhead."Does containerising this specific workload cost measurably more runtime than bare metal — that is, more than run-to-run noise on the same node — and does the answer change for I/O-heavy versus compute-heavy phases?
T2.2"Compare compilers and flags."How much of the performance difference between two optimisation levels on this kernel survives across three compilers — and does any flag among them change the numerical output?
T2.3"Study scaling."At what node count does strong-scaling efficiency for this application drop below 70%, and does weak scaling hold out to the same node count or further?
T2.4"Investigate the memory wall."Where does this kernel sit relative to the roofline, and how much of the gap closes with a single blocking transformation? Predict the gain before making the change.
T2.5"Look at reproducibility."Does this application produce bit-identical results across repeated runs, across process counts, and across compilers — and which of those three breaks first?

Tier 3 · Intermediate

These are the topics expected to reach a conference submission, so the question matters most here.

TopicToo broadThe question to actually run
T3.1"Study scheduling policy."Under this site's policy, how much does requesting a shorter walltime reduce median queue wait — and is the relationship linear, or is there a threshold where the benefit jumps?
T3.2"Look at sparse matrices."Across a chosen matrix collection, does the fastest storage format correlate with any single structural property, or is it predictable only from a combination of them?
T3.3"Investigate parallel I/O."At what process count does one-file-per-process I/O become slower than a single shared file with collective I/O — and does the crossover depend on write size?
T3.4"Predict job failures."Using only features available at submission time, can failed jobs be identified with precision above 0.5 — and how much does adding the first minute of runtime telemetry improve that?
T3.5"Port something to the GPU."What speedup does the GPU port achieve over a properly tuned multithreaded CPU baseline — and how much of the headline speedup people usually quote is actually CPU under-tuning rather than GPU advantage?
Once the question is settled

The Paper Kit is the companion for everything after this point — finding the prior work, verifying citations, handling noisy measurements, and building each section of the manuscript. Open it in week 2, not at the end: several of its sections change what you should be recording while the experiment is still running.

One page, signed, at the front of the repository

Before any code: the team writes the question, what is held fixed, what is varied, the expected answer, and what would refute it. One page. The sprint lead signs it. It goes at the front of the project repository and is the first thing compared against the results at write-up.

Where the final question differs from the signed one — normal, and often a sign the team learned something — the report says what changed and why. That paragraph is regularly the most interesting methods content in the whole write-up, and it is impossible to write if nobody wrote the original question down.

ReferenceGrading a sprint report— so feedback is consistent across sprint leads and cohorts

The sprint report rubric

Every runbook in this volume ends with a deliverable, but a deliverable without a standard is graded on whoever happens to read it. That produces inconsistent feedback across teams, and it makes it impossible to tell whether a cohort improved year over year.

Use this rubric for every sprint report, in every tier. It is deliberately weighted so that method quality outranks result quality — a careful negative result should score higher than a lucky positive one, because that is the behaviour the club wants to reinforce.

DimensionWeightWhat full marks looks likeWhat loses marks
Method soundness30% Comparisons are like-for-like; confounds are named and controlled; repetitions are sufficient and reported with spread; the baseline is the strongest available rather than the most convenient Single runs; different code across platforms; an untuned baseline; a varying parameter left unstated
Reproducibility20% A reader with access to the same hardware could re-run this from the repository alone: versions pinned, scripts committed, raw results included, environment documented Results with no accompanying code; "we ran it and got"; an undocumented manual step
Interpretation20% The report explains why the curve has the shape it has, and the explanation is supported by evidence rather than plausibility Describing the plot in prose without explaining it; asserting a cause that was never measured
Figures15% One figure carries the finding; axes labelled with units; scales chosen honestly; the reader understands it without the caption Default styling; unlabelled axes; a truncated axis that exaggerates a difference; six figures where one would do
Scope honesty10% An explicit sentence stating what was tested, on what, and what therefore cannot be concluded Generalising past the evidence; omitting the failures; presenting a single machine's behaviour as a general law
Writing5% The finding appears in the first paragraph; jargon is defined on first use; a reader from another faculty can follow it Burying the result in section four; unexplained acronyms

How to use it

The threshold that matters

A report scoring well on method, reproducibility, and scope honesty is worth submitting externally even if the result is negative or partial. A report scoring well only on figures and writing is not, however impressive the number in it. Sprint leads should be explicit about this when giving feedback, because the instinct in the other direction is strong and it is the single habit most likely to produce work that fails peer review later.

ReferenceWhere each topic leads— the progression across all three volumes

The progression map

The tier ladder describes prerequisites. This map describes continuity: which Volume I topic sets up which later project, so a sprint lead can route a member deliberately rather than by whatever is unclaimed.

Start here (Vol. I)What it teaches that transfersLeads to
E1 Cluster vs laptopQueue time is part of the cost; crossover thinkingVolume II's T6.7, whose entire thesis is that acquisition cost is invisible if you time from the wrong moment
E2 Amdahl measuredMeasured speedup versus predicted, and explaining the residualThe CPU-only track's T-CPU6, which applies exactly this method to quantized inference
E3 Compression benchmarkPareto reasoning; trade-offs rather than winnersVolume II's T4.1, whose headline figure is an accuracy-versus-time Pareto frontier
E4 Python vs NumPy vs CSerial performance, and why a fair baseline is hardVolume II's T4.2 and T6.6, both of which live or die on baseline quality
E9 Reproduce a figureWhat reproducibility actually costsVolume II's T5.6 multi-institution reproducibility work
T2.3 Strong and weak scalingScaling methodology properT-CPU6, and any competition benchmarking work
T2.4 The memory wallRoofline reasoning; bandwidth versus computePrerequisite for all of Volume II's Tier 4
T2.5 Numerical reproducibilityPrecision, tolerance, and equivalence checkingVolume II's T4.1 mixed precision, directly
T3.1 Scheduling policyHow a batch scheduler actually decidesVolume II's T6.3 and T6.8, both scheduling-method contributions
T3.5 First GPU portHonest cross-platform comparisonVolume II's T4.2 performance portability; gates all Tier 4 GPU work
If the GPU allocation does not arrive

Several routes above pass through GPU work. If a term's GPU allocation falls through, the CPU-only track is not a downgrade — it is a parallel path that keeps a member progressing on method rather than stalling on hardware. E2, T2.3, and T2.4 lead into it as naturally as they lead into Volume II, and a member who has done T-CPU6 arrives at Tier 4 with the scaling discipline intact.

Handbook Volume I complete — Tiers 1–3, 22 runbooks, 7 reference sections (Slurm, timing, plotting, report template, topic-narrowing worksheet with a worked question for all 22 topics, sprint rubric, progression map). Volume II (Advanced) covers Tiers 4–6 and the Zero-GPU / Single-Node Track covers CPU-only work; both are published separately.

2026-08-08 · Generated ResearchProject Status — Generated 2026-08-08

T-CPU2 Project Status Summary

Last Updated: 2026-08-08 Session: Plotting pipeline (synthetic-first) + grid resolution + cluster pivot + PDF/DOCX compilation + T-CPU1/3/4 full pipeline + Volume II curriculum


COMPLETED (All Consistent & Verified)

Manuscript Package (Phase 7)

  • phase7-final-manuscript.md — Final Markdown with [MEASURED] slots, abstracts, AI disclosure
  • phase7-manuscript.tex — LaTeX (IEEEtran, IEEE numeric citations, Chinese content removed)
  • phase7-references.bib — 23 entries, all verified (Carry A closed)
  • phase7-cover-letter.md — IGSC submission cover letter
  • phase7-final-quality-checklist.md — PASS on all gates

Artifact Package (artifact/)

  • README.md — Updated with Husom model grid, quantization mapping, tier coverage notes
  • LICENSE — CC BY 4.0 legal text
  • zenodo-metadata.json — Pre-reserve DOI, creator placeholders
  • environment-pinning.json — Runtime versions pinned (ollama 0.32.6, llama.cpp 10326, Python 3.11.4)
  • configs/grid.json — Husom 28-model sweep populated + mid/large tiers added (mistral:7b, qwen2.5:7b, llama3.1:8b, granite3.2:8b)
  • configs/prompt.txt — "Explain quantum computing in simple terms."
  • scripts/run_grid.sh — Grid orchestrator (randomized, calibration, cross-check)
  • scripts/measure_cell.py — Per-cell runner (Ollama/llama.cpp, RAPL/SoC/estimate)
  • scripts/analyze.py — Pre-registered analysis (D1-D4, BH-FDR, verdict)

Supporting Documents

  • t-cpu2-measurement-execution-checklist.md — Phases A-G runbook
  • phase7-final-quality-checklist.md — PASS (format, citations, compliance)
  • external-review-revision-log.md — Carry A closed, all 22 refs verified
  • citation-audit-report.md — Clean (1 cross-artifact fix applied)
  • verified-bibliography-quality-matrix.md — 23/23 verified

Cluster Access & Pipeline Verification

  • SSH bridge v3 working (live OTP prompt, persistent session)
  • ollama v0.32.6 + llama.cpp v10326 built & working on login node
  • llama3.2:3b model pulled (2GB)
  • RAPL verified on frnt201 (Intel Xeon Gold 6430, 2×32-core, package/DRAM/psys domains)
  • 4 GPU measurement jobs cancelled (queue blocked: frnt201 busy, ~100 jobs ahead)

Cluster Root Cause (2026-08-08, CLOSED)

  • Root cause found: JobSubmitPlugins = lua — Frontenac controller-side Lua submit filter (puppet-managed, invisible from login node) rewrites EVERY job from sg6079000_cpu to hidden partition gpu-rgrant.
  • Evidence chain: explicit --partition=cpubase_interac|gpu-L4|teaching|cpubase_6hrs all land gpu-rgrant PENDING; --nodelist=frnt153 → "Requested nodes not in this partition" (server-side rewrite proven); gpu-rgrant hidden from scontrol show partition (sinfo -a only), account-restricted to def-hpcg1971.
  • No bypass possible: no cli_filter/SPANK/plugstack on login node; /etc/slurm/slurm.conf controller-only.
  • Decision (user): skip cluster measurement entirely — no CAC support email; pivot to local dual-leg measurement (Pi 5 + INA219 vs x86 Linux + RAPL) + synthetic-first plotting.

Plotting Pipeline (2026-08-08, VERIFIED)

  • artifact/scripts/plot_lib.py — pure-function loaders + log-normal ratio SE
  • artifact/scripts/synthetic_data.py — schema-compliant draft data (24 primary cells × 2 legs, 30 runs + 1 warmup, D1 estimate fallback on Pi fp16)
  • artifact/scripts/plots.py — 5 figures + table1: fig1_crossover, fig2_heatmap, fig3_joint, fig4_robustness, fig5_margin
  • artifact/scripts/make_table1_tex.py — booktabs LaTeX table generator
  • artifact/figures/figure-includes.tex — LaTeX figure blocks for §4.1–4.5 + §6.1 verdict templates
  • artifact/tests/test_plot_pipeline.py — 15 tests, all pass
  • Grid resolved: 24 primary cells (6 models × 4 quants) + 4 sensitivity (qwen2.5:1.5b; gemma2:2b contributes 0 — 3-bit only). grid_role field added to grid.json; run_grid.sh/synthetic_data.py filter by role, --include-sensitivity flag.
  • Synthetic run verified: 9 Pi-favorable crossovers (small/quantized), 8 server-superior (large/fp16) — matches pre-registered mechanism. Figures non-blank; LaTeX compiles to 7-page PDF (pdflatex, MiKTeX).
  • Robustness hardening (2026-08-08): heterogeneous-cell path exercised via data/synthetic_edge (2 missing-leg cells, 2 boundary-ambiguous cells forced with --calibration-bound-j 5.0). Fixed 2 real bugs: (1) analyze.py crashed when cells.csv mixed tested with non-tested rows (DictWriter fieldnames derived from row 0) — now union_fieldnames() in plot_lib.py; (2) plots.py table1 + make_table1_tex.py dropped reason for non-tested cells (silent 12-char truncation into Ch column) — now a proper Status column, emitted only when non-tested rows exist. fig5_margin renders non-tested cells as grey bars (pixel-verified). Regression tests added (15 total, all pass).
  • Final integrity sweep (2026-08-08): figure QA via pixel/border analysis on all 5 synthetic PNGs — zero edge clipping, no text-band overlap (fig5 27 bands = 24 labels + axes + title, min gap 1px; fig4 dense region = y-axis spine, not overlap). Manuscript cross-check: §4.1–4.5 slots + [CROSSOVER VERDICT] present, include order matches (table+heatmap → crossover → joint → robustness → margin), 9 [MEASURED] slots preserved (registered-report integrity). §4.5 prose now describes margin-map grey-cell/boundary-ambiguous semantics; fig5 caption aligned. Execution checklist gained Phase F.5 automated-pipeline commands; README documents Status-column behavior. 15/15 tests pass.

Compilation (2026-08-08, VERIFIED)

  • Toolchain installed: pandoc 3.10.1 (winget) + MiKTeX 25.12 (winget, background-completed). pdflatex/mpm/initexmf at C:\Users\[Author]\AppData\Local\Programs\MiKTeX\miktex\bin\x64\. [MPM]AutoInstall=1 set; ieeetran + 17 dependency packages pre-installed via mpm (first pdflatex run had hung on interactive package-install prompt — killed, root-caused, fixed). Note: all manuscripts compile with pdflatex (English-only corpus).
  • PDF: phase7-manuscript.pdf — 7 pages, 158,815 bytes, IEEEtran. Pass 1 → bibtex (22 entries, 0 warnings) → pass 2. No LaTeX warnings; only cosmetic underfull hbox/vbox (badness ≤ 10000). 22/22 citations resolved in .aux.
  • DOCX: phase7-manuscript.docx (26,759 bytes, pandoc from markdown). Verified: affiliation block, all 9 [MEASURED] slots + [CROSSOVER VERDICT] preserved, References rendered (Husom et al., Grange, Koslovski, et al.).

T-CPU1 / T-CPU3 / T-CPU4 — FULL PIPELINE COMPLETE (2026-08-08)

Overview

All three remaining QHPC topics completed to the same phase1–7 depth as T-CPU2 (11 files each: phase1, phase2, phase3, phase4, phase5a, phase5b, phase6, phase7-final-manuscript.md, phase7-references.bib, phase7-manuscript.tex, phase7-final-quality-checklist.md, repo root, tcpuN-* prefix). Volume II curriculum expanded to full lesson plans.

T-CPU4 — Green-Aware Scheduling (SIMULATION, real results)

  • artifact/scripts/tcpu4_simulator.py — discrete-event simulator (FCFS+backfill / naive threshold / lookahead schedulers, 3 renewable profiles, 4 arrival rates, 2 workload shapes), pure Python.
  • artifact/scripts/tcpu4_sweep.py — full-factorial sweep (48 rows), artifact/results/tcpu4/sweep.json + experiment.json.
  • artifact/scripts/tcpu4_plots.py — 3 figures (artifact/figures/tcpu4/).
  • artifact/tests/test_tcpu4_simulator.py — 14 tests, all pass.
  • Energy model finding: renewable availability must be time-averaged over the job interval (0.25 h sampling); start/finish-time attribution overstates deferral benefit for long jobs. Flat-profile control (all schedulers identical) validates no scheduler bias.
  • Key results: naive green helps LLM jobs (−7.1% grid) but regresses on HPC jobs (rate=4.0/low: +2.2% grid, 22% drops, EDP 16716→22136); green_la lookahead recovers (−1.6% HPC grid, no drops, delay ≈ FCFS). Single seed=42 deterministic.
  • tcpu4-phase7-manuscript.pdf — 4 pages, compiled clean (figure added for fig:edp), 11 verified citations. DOCX via pandoc.

T-CPU3 — Portable CPU-Only RAG (CODE + real results)

  • artifact/scripts/rag_retriever.py — backend-pluggable retrieval pipeline (MiniLMBackend via sentence-transformers 5.7.0 + torch CPU; HashEmbeddingBackend for tests), persistent store.
  • artifact/data/tcpu3/corpus.jsonl (20 docs) + queries.jsonl (20 queries); artifact/results/tcpu3/eval.json + eval_hash.json.
  • artifact/scripts/rag_eval.py + tcpu3_plots.py (2 figures).
  • artifact/tests/test_rag_retriever.py — 11 tests (10 pass + 1 integration-skipped; MiniLM integration test passed: 384-dim on CPU).
  • Results: recall@1=0.818, recall@5=1.000, MRR=0.942; 2.13 docs/s index, 29.05 q/s, 12.7 ms/query. Hash baseline: recall@1=0.500, MRR=0.691. GPU-baseline comparison = [MEASURED] slot (Frontenac blocked).
  • Citation compliance: MiniLM described in prose only — no fabricated citation (not in verified matrix).
  • tcpu3-phase7-manuscript.pdf — 3 pages, compiled clean, 4 verified citations. DOCX via pandoc.

T-CPU1 — CPU-Only Toolchain Optimization (REGISTERED REPORT, measurement blocked)

  • Pre-registered protocol: 6 models × 4 quantizations = 24 primary cells × 2 hardware targets (Frontenac Xeon Gold 6430 + Pi 4); toolchains Ollama/llama.cpp/vLLM-CPU; quality gate perplexity ≤1.05×baseline + accuracy ≥baseline−0.02; 10% speedup threshold; BH-FDR q=0.05; all results explicit [MEASURED] slots.
  • tcpu1-phase7-manuscript.pdf — 3 pages, compiled clean (fixed: torring2022analyzing @article→@inproceedings; qphc2026track citation replaced with prose), 12 verified citations. DOCX via pandoc.
  • Measurement blocked on cluster (same gpu-rgrant rewrite) and on Pi 5 (hardware not acquired) — registered-report slots remain [MEASURED].

Volume II Curriculum — Full Lesson Plans

  • volume-ii-curriculum.md — expanded from outline to full lesson plans: Tier 4 (Benchmarking & Empirical Methods), Tier 5 (Optimization Strategy), Tier 6 (Green & Heterogeneous Systems); 6 sessions × 2 h per tier; each session has Objectives / Content / Hands-on / Checkpoint / Homework; tier artifacts + exit criteria + assessment summary table.

Test Suite (aggregate)

  • 40/40 pass (python -m unittest discover -s tests; 1 skipped integration): 15 plot-pipeline + 14 tcpu4-simulator + 10 rag-retriever (+1 skipped) + 1 MiniLM integration (run separately).

FULL-DIRECTORY FRESHNESS UPDATE (2026-08-08)

Per user request, all stale documents scanned and updated. Summary:

Stamped as historical pipeline artifacts (frozen content + provenance banner, 16 files): phase1-literature-search-report.md, phase2-paper-outline.md, phase3-argument-blueprint.md, phase4-complete-draft.md, phase5a-citation-compliance-report.md, phase5b-abstract.md, phase6-peer-review-report.md, phase6-revision-log.md, phase6-peer-review-round2.md, external-review-eic-report.md, external-review-methodology-report.md, external-review-domain-report.md, external-review-perspective-report.md, external-review-devils-advocate-report.md, external-review-editorial-decision.md, external-review-revision-log.md.

Refreshed shared sources (content updated, 5 files):

  • verified-bibliography-quality-matrix.md — added 4-topic citation-subset map (T-CPU4 → rows 4–9, 18–23; T-CPU3 → rows 1,3,4,10; T-CPU1 → rows 1,8–17,20); MiniLM prose-only note.
  • hpc-reference-corpus.md — added Volume II lesson-plan grounding (Tiers 4–6 source mapping).
  • qphc-cpu-four-topic-track.md — per-topic status lines (all 4 complete; measurement slots open).
  • citation-audit-report.md — added 2026-08-08 follow-up note (new manuscripts cite only verified matrix; per-manuscript audits clean).
  • cpu-only-curriculum-publication-pathways.md — added per-topic status + gate-status table (gates 1,2,4,5 DONE; gate 3 PARTIAL).

Re-verified live deliverable: phase7-final-quality-checklist.md — added 2026-08-08 re-verification note (recompiled after final edits, all gates PASS).

HTML exports stamped (7 files, 2026-08-08): artifact-1.html … artifact-7.html — frozen HTML comment inserted after <title> (inside <head>, no render impact): <!-- STATUS: archived snapshot (saved 2026-08-03/04). Source referenced by citation-audit-report.md; content NOT regenerated 2026-08-08. -->. Content left untouched (original source artifacts; rewriting would falsify provenance). phase7-references.bib left current (used in compilation — not stale).

AUTHOR-SIDE CARRIES (Need Your Input)

Item File(s) Action Needed
Affiliation + ORCID phase7-final-manuscript.md, phase7-manuscript.tex, phase7-cover-letter.md, artifact/zenodo-metadata.json Affiliation DONE (2026-08-08) — "Queen's University, Kingston, ON, Canada" filled in all 4 files. ORCID still pending — optional, author to add (placeholder retained).
Model grid (mid/large tiers) artifact/configs/grid.json DONE — mid: mistral:7b, qwen2.5:7b; large: llama3.1:8b, granite3.2:8b. fp16 cells server-only + Pi estimation fallback (pre-registered memory-pressure clause; protocol's "fp16 within 8GB" for >8B is arithmetically impossible, resolved via fallback, documented in grid.json _notes and README). Also DONE (2026-08-08): grid_role field added — 24 primary cells (6 models × 4 quants) + 4 sensitivity (qwen2.5:1.5b; gemma2:2b contributes 0 cells, 3-bit only).
Environment pinning (post-measurement) artifact/environment-pinning.json Fill OS/kernel/runtimes/model hashes/governors after measurement on each leg
Zenodo metadata artifact/zenodo-metadata.json Fill affiliation, ORCID, manuscript DOI after submission
Prompt token count artifact/environment-pinning.json DONE — 7 tokens ("Explain quantum computing in simple terms."), verify with pinned tokenizer at measurement time

MEASUREMENT EXECUTION (Pivoted to Local — see notes)

Cluster blocker (resolved as dead-end, not pursued): Account sg6079000_cpu is pinned to hidden gpu-rgrant partition by a controller-side Lua submit filter (root cause, 2026-08-08). frnt201 busy with ~100 MPI jobs ahead; login node has no RAPL. Decision: skip cluster — CAC email template retained below only as fallback.

Local dual-leg plan (active):

  • Edge leg: Raspberry Pi 5 + INA219 SoC power measurement (or CPU-utilization estimation fallback per D1)
  • Server leg: x86 Linux desktop/node with RAPL (/sys/class/powercap/intel-rapl:0/)
  • Both legs: ollama (or llama.cpp cross-check), same grid, 30 runs + 1 warmup per cell
  • Draft figures already generated from synthetic data; swap real data in with same commands

Fallback template (only if cluster reconsidered):

Subject: Partition access request for account sg6079000_cpu

Hi CAC team,

Account sg6079000_cpu (user sa6079052) is currently restricted to gpu-rgrant partition (single node frnt201). 
This forces all jobs to queue behind heavy MPI workloads.

Request: Please grant access to cpubase_interac and/or teaching partitions for CPU-based energy measurement work (RAPL power monitoring on Intel Xeon nodes).

Use case: T-CPU2 protocol server-leg energy measurement — requires RAPL (intel-rapl) on CPU nodes, not GPU.

Thanks,
[Your Name]

If cluster access ever granted: Run measurement on idle CPU nodes (frnt129, frnt136-137, frnt150-153) with RAPL.


PIPELINE STATUS (Verified)

Component Status
Synthetic data generator 24 primary cells × 2 legs ✓
analyze.py (D1–D4, BH-FDR) 28→24 cells, verdict JSON ✓
plots.py (5 figs + table1) All rendered, non-blank ✓
Unit tests 15/15 pass ✓
LaTeX figure includes Compiled (IEEEtran, 7 pages) ✓
SSH bridge v3 Working (live OTP prompt)
ollama v0.32.6 ✓
llama.cpp v10326 (commit 3653e6d6d) ✓
llama3.2:3b model Pulled (2GB) ✓
RAPL on frnt201 Verified (package/DRAM/psys) ✓
ollama inference Working (login node test) ✓
llama.cpp Needs GGUF model file (download separately)
Login node RAPL Not available (expected)

FILES TO REVIEW / FINALIZE

High priority (author input):

  1. phase7-final-manuscript.md — ORCID (affiliation filled: Queen's University)
  2. phase7-manuscript.tex — ORCID (affiliation filled)
  3. phase7-cover-letter.md — ORCID (affiliation filled)
  4. artifact/zenodo-metadata.json — ORCID + DOI (affiliation filled)
  5. artifact/environment-pinning.json — post-measurement values
  6. artifact/configs/prompt.txt — token count → environment-pinning.json
  7. artifact/figures/synthetic/*.png — eyeball figures (label clipping/legend overlap)

Local measurement (when Pi + x86 node available):

  • artifact/scripts/run_grid.sh --leg pi (Pi 5 + INA219)
  • artifact/scripts/run_grid.sh --leg server (x86 Linux + RAPL)
  • artifact/scripts/analyze.py --pi-dir data/pi --server-dir data/server --out results/real
  • artifact/scripts/plots.py --pi-dir ... --server-dir ... --cells results/real/cells.csv --crossover results/real/crossover.csv --verdict results/real/verdict.json --grid configs/grid.json --out figures/real

NEXT STEPS (Suggested Order)

All pipeline work for T-CPU1/2/3/4 is COMPLETE. Remaining items are author-side / hardware / venue:

  1. Fill ORCID in all 5 manuscript sets (T-CPU1/2/3/4 + cover letter) + zenodo-metadata.json (only remaining author placeholder)
  2. Acquire Pi 5 + INA219 for edge leg (or confirm estimation-fallback-only edge leg) — needed for T-CPU1 and T-CPU2 [MEASURED] fills
  3. Reconsider cluster access (CAC request submitted; gpu-rgrant rewrite still blocks CPU-node RAPL work) — needed for T-CPU1 server leg + T-CPU3 GPU-baseline [MEASURED] slot
  4. Run local measurement on both legs → fill [MEASURED] slots in T-CPU1/T-CPU2, regenerate figures, recompile PDF/DOCX
  5. Submit T-CPU2 to IGSC (or alternate venue); T-CPU1/3/4 as registered report + methodology papers

Bridge Status: Currently alive in your PowerShell window (CONNECTED). Leave open if continuing soon; close if pausing for days (needs fresh MFA on restart).

Imported from PROJECT_STATUS.md (2026-08-08). Full pipeline status: T-CPU1-4 complete through Phase 7, measurement execution pending cluster access, files to review, and next steps. Complements the Research & Project Handbook above.