---
layout: '@/layouts/Doc.astro'
title: 'Benchmarking ALCF systems with torch-hammer'
date: 2026-11-11
date-created: 2026-11-11
date-modified: today
description: 'Same 9 benchmarks, same tool, two completed ALCF systems: Sunspot (PVC Max 1550) and Sirius (A100). Aurora and Polaris remain queued. The results include one benchmark where Intel wins 8×.'
---
import ScalingChart from '@/components/ScalingChart.astro'
[torch-hammer] is an HPE-maintained GPU benchmark suite — 9 workloads from batched GEMM to stencil PDEs to sparse matmul, with built-in telemetry (NVML / ROCm-SMI / xpu-smi), compact CSV + HTML fleet reports, and multi-GPU aggregation via `multiprocessing.spawn`. It runs on CUDA, ROCm, XPU, and MPS from one codebase.
This post is about making it run **everywhere at ALCF** and what came out.
[torch-hammer]: https://github.com/HPE/torch-hammer
[alcf]: https://www.alcf.anl.gov
## TL;DR — the numbers
Same benchmarks, same flags (`--stress-test`, HBM-scaled), two machines that actually scheduled. Aurora and Polaris are still queued behind reservations — same PBS, same staging, they'll land when `debug` frees up.
**1-GPU stress (HBM-scaled, float32):**
| Benchmark | Unit | Sunspot XPU
Max 1550 | Sirius A100
40GB SXM4 | Winner |
|-----------|------|------------------------|--------------------------|--------|
| Batched GEMM | GFLOP/s | 18,907 | 18,861 | Tie |
| Convolution | img/s | 1.64M | 9.36M | A100 5.7× |
| 3D FFT | GFLOP/s | — | — | — |
| Einsum Attention | GFLOP/s | 14,240 | 17,058 | A100 1.2× |
| Memory Traffic | GB/s | 27.7 | 29.6 | A100 1.07× |
| Heat Equation | MLUPS | 10,613 | 15,501 | A100 1.46× |
| Schrödinger Equation | iter/s | 43.2 | 58.8 | A100 1.36× |
| Atomic Contention | Mops/s | 1,734 | 9,176 | A100 5.3× |
| **Sparse MM** | **GFLOP/s** | **2,028** | **243** | **XPU 8.4×** |
**All-GPUs fleet (stress, per-node):**
| System | GPUs | Batched GEMM | Convolution | Einsum | Mem BW | Verdict |
|--------|------|-------------|-------------|--------|--------|---------|
| Sunspot | 24 (2×12 tiles) | 18.0 TFLOP/s | 1.49M img/s | 13.9 TFLOP/s | 27.0 GB/s | 2/2 PASS · CV 0.7–5.7% |
| Sirius | 4 visible (2 allocated) | 18.9 TFLOP/s | 9.39M img/s | 17.1 TFLOP/s | 29.6 GB/s | 1/1 PASS · CV ≤0.2% |
The one surprise is **Sparse MM**: PVC Max 1550 beats A100 by 8.4× on unstructured sparse matmul. Everything else favors A100 or ties — which is the expected story for dense GEMM / attention / atomics.
FFT is missing on both: XPU hits an MKL DFT "unimplemented" for the configured shape; Sirius OOMs at 11.75 GiB under stress fill on 40 GB (prior benches already hold ~30 GiB).
## What was needed per machine
The interesting part is not the fastest GPU — it is what it costs to make one script work across different ALCF systems.
| | Sunspot | Aurora | Polaris | Sirius |
|---|---------|--------|---------|--------|
| GPU | PVC Max 1550 · 12 tiles/node | PVC Max 1550 · 12 tiles/node | A100 40GB · 4/node | A100 40GB · 4/node |
| Queue | `workq` · `datascience` | `debug` · `AuroraGPT` | `debug` · `datascience` | `workq` · `datascience` |
| Filesystems | `home:tegu` | `home:flare` | `home:eagle` | `home` |
| Env | `ezpz_setup_env` → `frameworks/2026.1.0` | `ezpz_setup_env` or venv | `conda base` | `miniconda3/envs/cu122-pt221` |
| MPI | `mpiexec --ppn` | `mpiexec --ppn` | `mpiexec --ppn` | none — head-node fallback |
| Telemetry | placeholder (no `xpu-smi`) | placeholder | NVML (full) | NVML (full) |
Sirius was the hardest per line of PBS: Python 3.6 as the system default with `from __future__ import annotations` as a `SyntaxError`, no `mpirun`/`srun`/`pbsdsh` at all, and the hammer report itself failing under 3.6. The fix was explicit `miniconda3/envs/cu122-pt221` (Py 3.11 + torch 2.2) and a single-node `--all-gpus` fallback that still exercises all 4 A100s via `torch.multiprocessing.spawn`.
For the larger machines the same PBS template works with three substitutions: account, queue, and filesystem. Staging is shared — one `rsync` of `torch-hammer.py` + `reports/` + `config-examples/` to `~/thammer-staging` on each host.
## How the run is structured
Every job does the same four phases:
1. **Single-GPU quick** — all 9 benchmarks, small sizes, `--device-index 0`. Cheap sanity check; also exercises the CSV/JSON/HTML paths.
2. **Single-GPU stress** — same 9 benchmarks, `--stress-test` to fill ~50% of free HBM (the `0.3 GB/s` → `27.7 GB/s` fix — without it the memory bench runs on 4 KiB and measures `randint` overhead).
3. **Per-node all-GPUs** — the 4-benches subset (`gemm`/`conv`/`einsum`/`memory-traffic`) under stress, one `mpiexec` rank per node, each rank fanning out to its GPUs.
4. **Reports** — `hammer_report.py` on each CSV/JSON plus a fleet rollup over `all-benchmarks/`.
Phase 3 is what produces the fleet verdict. Mixing quick + stress CSVs in one directory gives a spurious `0/N OUTLIER` report (different problem sizes) — isolated per-phase outputs are the correct inputs to the fleet report.
## Detailed results
### Sunspot — Intel Data Center GPU Max 1550
2 nodes, 12 PVC tiles each, 24 GPUs total. Job `12483256`, walltime 00:12:18, via `ezpz_setup_env` → `frameworks/2026.1.0` (torch 2.13 + XPU).
Single-GPU stress (head tile):
| Benchmark | mean | unit | min | max |
|-----------|------|------|-----|-----|
| Batched GEMM | 18,907 | GFLOP/s | 18,900 | 18,921 |
| Convolution | 1,640,940 | img/s | 1,635,519 | 1,662,918 |
| Einsum Attention | 14,240 | GFLOP/s | 13,977 | 16,456 |
| Memory Traffic | 27.7 | GB/s | 27.3 | 28.0 |
| Heat Equation | 10,613 | MLUPS | 5,606 | 10,720 |
| Schrödinger Equation | 43.2 | iter/s | 42.7 | 43.3 |
| Atomic Contention | 1,734 | Mops/s | 1,732 | 1,735 |
| Sparse MM | 2,028 | GFLOP/s | 2,010 | 2,054 |
Fleet (4 benches, stress, both nodes):
```
Batched GEMM 18.0 TFLOP/s CV 0.7%
Convolution 1.49M img/s CV 1.0%
Einsum 13.9 TFLOP/s CV 5.7%
Memory Traffic 27.0 GB/s CV 4.6%
FLEET VERDICT: 2/2 PASS
```
No power/temp columns — `xpu-smi` / Level Zero telemetry is not yet wired on the compute image; `IntelTelemetry` currently returns N/A for those fields.
### Sirius — NVIDIA A100-SXM4-40GB
1 node, 4 visible A100s (2 allocated; head-node fallback). Job `33426` (retry after `pbsdsh` fix), walltime 00:02:08 / 00:01:14, via `miniconda3/envs/cu122-pt221` (torch 2.2, CUDA 12.1, driver 535).
Single-GPU stress (head GPU):
| Benchmark | mean | unit | min | max | power | temp |
|-----------|------|------|-----|-----|-------|------|
| Batched GEMM | 18,861 | GFLOP/s | 18,849 | 18,870 | 323 W | 48 °C |
| Convolution | 9,364,516 | img/s | 9,310,987 | 9,421,163 | 390 W | 52 °C |
| Einsum Attention | 17,058 | GFLOP/s | 17,012 | 17,084 | 347 W | 52 °C |
| Memory Traffic | 29.6 | GB/s | 29.6 | 29.6 | 194 W | 49 °C |
| Heat Equation | 15,501 | MLUPS | 15,420 | 15,510 | 289 W | 45 °C |
| Schrödinger Equation | 58.8 | iter/s | 57.0 | 59.0 | 201 W | 45 °C |
| Atomic Contention | 9,176 | Mops/s | 9,134 | 9,179 | 124 W | 41 °C |
| Sparse MM | 243 | GFLOP/s | 243 | 243 | 221 W | 44 °C |
Fleet (4 benches, stress, 4 GPUs):
```
Batched GEMM 18.9 TFLOP/s CV 0.2% 313 W 48 °C
Convolution 9.39M img/s CV 0.2% 386 W 52 °C
Einsum 17.1 TFLOP/s CV 0.2% 339 W 53 °C
Memory Traffic 29.6 GB/s CV 0.0% 191 W 52 °C
FLEET VERDICT: 1/1 PASS
```
Full NVML telemetry — power, temp, SM util, clocks — in every row.
### Aurora & Polaris — queued
Same PBS, same staging, same reports path. Both are `Q` in `debug` behind unrelated reservations and a 58-deep `prod` routing queue. No code change needed; they'll produce identical HTML when `debug` frees up.
## What to do with this
As a one-off, the table above is the takeaway: A100 wins dense, XPU wins sparse, and `torch-hammer` is a cheap way to check that a machine is still the machine it claims to be.
As infrastructure, the value is the **template**:
- One staging dir per host (`~/thammer-staging`), one PBS template with per-system account/queue/filesystem/env/MPI substitutions.
- Four-phase job with isolated report inputs so the fleet verdict is never polluted by a scale mismatch.
- One `hammer_report.py` invocation per artifact — single-GPU, stress, and fleet — so regressions show up as HTML without needing to parse logs.
Aurora and Polaris remain queued and will be added when they run.
## Artifacts
- Code: [`saforem2/torch-hammer`](https://github.com/saforem2/torch-hammer) (branch `feature/intel-xpu-support`, includes the XPU backend + `--stress-test` HBM fix)
- HTML report snapshots: combined [1-GPU comparison](/posts/2026/11/11/alcf-combined-1gpu.html) and [stress comparison](/posts/2026/11/11/alcf-combined-stress.html); Sunspot [compact 1-GPU](/posts/2026/11/11/sunspot-1gpu-stress.html), [full 1-GPU](/posts/2026/11/11/sunspot-stress-1gpu.html), [24-GPU fleet](/posts/2026/11/11/sunspot-24gpu-fleet.html), and [fleet rollup](/posts/2026/11/11/fleet-report-sunspot.html); Sirius [compact 1-GPU](/posts/2026/11/11/sirius-1gpu-stress.html), [full 1-GPU](/posts/2026/11/11/sirius-stress-1gpu.html), and [4-GPU fleet](/posts/2026/11/11/sirius-4gpu-fleet.html).
- Jobs: Sunspot `12483256` (F 00:12:18), Sirius `33426` (E 00:02:08 → R 00:01:14 after fix)
---
*Thanks to the ALCF support staff who keep `debug` moving. If you want this on your allocation, the PBS template is three lines of `sed` away from yours — PRs welcome.*