Skip to content

Commit 323382f

Browse files
dzzz2001claude
andcommitted
perf(gint): tail-waste-min BLK_N ladder for gemm_nn_vbatch
Extend the NN dispatch from a 3x2 ladder to 3x4: BLK_N now ranges over {16, 32, 48, 64} and is picked by lexicographically minimizing (tail_waste, grid_blocks) against the runtime bxyz. Because THR_N = BLK_N / DIM_Y is unrolled at compile time, a BLK_N that does not divide bxyz produces fully-computed but mostly-masked register tiles on the last grid-y block -- pure FMA waste. The new ladder lands bxyz=48 on BLK_N=48 (0 waste, 1 block) and bxyz=80/100 on BLK_N=16 (0 waste, many blocks), eliminating the regressions the fixed-{32,64} ladder showed on those shapes. Also add CLAUDE.md (project instructions and benchmark workflow) and docs/gemm_vbatch_tuning_guide.md (template-parameter tour for vbatched_gemm_{nn,tn}_impl). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 09cffa8 commit 323382f

3 files changed

Lines changed: 612 additions & 24 deletions

File tree

CLAUDE.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Project: abacus-gemm-opt
2+
3+
Working repo for optimizing the batched-GEMM kernels in ABACUS's Gint module
4+
(`gemm_nn_vbatch` / `gemm_tn_vbatch` in
5+
`source/source_lcao/module_gint/kernel/dgemm_vbatch.cu`).
6+
7+
The code under optimization is invoked from `phi_operator_gpu.cu`:
8+
- `phi_mul_phi` (Hamiltonian build, V_loc) → **`gemm_tn_vbatch`**
9+
- `phi_mul_dm` (charge density from DM) → **`gemm_nn_vbatch`**
10+
11+
`M` of the vbatch GEMMs scales with `mgrids_num_ ≈ bxyz = bx*by*bz`.
12+
`N`/`K` scale with the per-element orbital count `nw`. So a meaningful benchmark
13+
must vary **both** `bxyz` *and* the species mix.
14+
15+
---
16+
17+
## Builds
18+
19+
| name | binary | purpose |
20+
|-----------|----------------------------------------------------------|------------------------|
21+
| baseline | `/home/dzc/abacus/abacus-gemm-opt/build-baseline/abacus_2g` | reference (do not modify) |
22+
| test | `/home/dzc/abacus/abacus-gemm-opt/build-test/abacus_2g` | the build under optimization |
23+
24+
After editing `dgemm_vbatch.cu`, rebuild **only** the test build:
25+
26+
```bash
27+
cmake --build /home/dzc/abacus/abacus-gemm-opt/build-test -j
28+
```
29+
30+
---
31+
32+
## Benchmark suite
33+
34+
Location: `~/abacus/test-examples/ABACUS-test/LCAO_test/gemm_vbatch_bench/`
35+
36+
Six 108-atom cases derived from the `008_Li27Ni9O54Mn9Co9` reference structure.
37+
Same lattice / k-point / cutoff in every case — only `bx,by,bz` and the species
38+
mix change, so cross-case deltas isolate to the vbatch GEMM shapes.
39+
40+
| # | Case dir | bx,by,bz | bxyz | ntype | Composition (108 atoms) |
41+
|---|---------------------------|---------|------|-------|------------------------------------------|
42+
| 1 | `case1_bxyz27_5species` | 3,3,3 | 27 | 5 | Li27 Ni9 O54 Mn9 Co9 |
43+
| 2 | `case2_bxyz48_6species` | 4,4,3 | 48 | 6 | Li27 Ni9 O54 Mn6 Fe3 Co9 |
44+
| 3 | `case3_bxyz64_6species` | 4,4,4 | 64 | 6 | Li27 Ni6 Al3 O54 Mn9 Co9 |
45+
| 4 | `case4_bxyz80_7species` | 5,4,4 | 80 | 7 | Li27 Ni6 Fe3 O54 Mn6 Al3 Co9 |
46+
| 5 | `case5_bxyz100_7species` | 5,5,4 | 100 | 7 | Li24 Na3 Ni6 Cu3 O54 Mn9 Co9 |
47+
| 6 | `case6_bxyz125_8species` | 5,5,5 | 125 | 8 | Li24 Na3 Ni6 Cu3 O54 Mn6 Fe3 Co9 |
48+
49+
All cases run with `scf_nmax=2`, `cal_force=1`, `cal_stress=1`, `nspin=2`,
50+
`gint_precision=double`, `ks_solver=genelpa`, Γ-only.
51+
This exercises `cal_gint_vl` four times, `cal_gint_rho` twice, `cal_gint_fvl`
52+
once per run.
53+
54+
### Running the suite
55+
56+
The runner is **serial** (one case after another, baseline before test):
57+
58+
```bash
59+
cd ~/abacus/test-examples/ABACUS-test/LCAO_test/gemm_vbatch_bench
60+
./run_bench.sh
61+
```
62+
63+
The launch line, identical for every run, is:
64+
65+
```
66+
OMP_NUM_THREADS=7 mpirun -n 1 --bind-to socket <abacus_2g>
67+
```
68+
69+
Each invocation produces, inside the case dir:
70+
71+
```
72+
run_baseline.log run_test.log # stdout
73+
wall_baseline.txt wall_test.txt # wall time captured by the harness
74+
OUT.baseline/ OUT.test/ # ABACUS output dirs (renamed from OUT.ABACUS)
75+
└── running_scf.log # source of all kernel timings
76+
```
77+
78+
### Where to look in the timing table
79+
80+
Inside each `running_scf.log`, the `TIME STATISTICS` section near the end has
81+
the rows that matter:
82+
83+
| timer | what it covers | which kernel |
84+
|-----------------------------|-----------------------------------------------|--------------|
85+
| `Gint cal_gint_vl` | `phi_mul_phi` during H build | `gemm_tn_vbatch` |
86+
| `Gint cal_gint_rho` | `phi_mul_dm` during `dm2rho` | `gemm_nn_vbatch` |
87+
| `Gint cal_gint_fvl` | force/stress contribution from V_loc | both flavors |
88+
| `HamiltLCAO updateHk` | superset of `cal_gint_vl` | (sanity) |
89+
| `HSolverLCAO solve` | SCF inner loop (includes `cal_gint_rho`) | (sanity) |
90+
| `Driver atomic_world` | end-to-end | (sanity) |
91+
92+
`updateHk`, `elpa_solve`, `cal_force_stress` should not move under this
93+
optimization — if they do by more than ±2%, suspect noise or a build mismatch
94+
before declaring victory.
95+
96+
### Generating the report
97+
98+
`run_bench.sh` only runs the binaries; it does not parse. To produce a Markdown
99+
report from the existing `OUT.{baseline,test}/running_scf.log` files, the
100+
parsing recipe is:
101+
102+
1. Read the `TIME STATISTICS` block from each log.
103+
2. For each case, pull out the 8 timers in the table above for both builds.
104+
3. Compute `speedup = baseline / test`.
105+
4. Tabulate per-kernel and end-to-end.
106+
107+
A reference report is at:
108+
109+
```
110+
~/abacus/test-examples/ABACUS-test/LCAO_test/gemm_vbatch_bench/REPORT.md
111+
```
112+
113+
and a machine-readable parse dump at:
114+
115+
```
116+
~/abacus/test-examples/ABACUS-test/LCAO_test/gemm_vbatch_bench/_parsed.json
117+
```
118+
119+
---
120+
121+
## Known baseline characterization (as of 2026-04-09 sweep)
122+
123+
The "test" build at the time the suite was created had this profile vs
124+
baseline (single-shot, RTX 3090, GPU0):
125+
126+
- **`gemm_tn_vbatch` (`cal_gint_vl`):** uniformly **+3.4 % to +5.5 %** across all
127+
6 cases. Treat this kernel as the *known-good* template.
128+
- **`gemm_nn_vbatch` (`cal_gint_rho` / `cal_gint_fvl`):** highly shape-sensitive.
129+
Best case +3.5 %, **worst case −27 % on `cal_gint_rho` and −24 % on `cal_gint_fvl`**.
130+
The worst regressions hit `bxyz=27` and `bxyz=80`. The two cases that
131+
*improved* were `bxyz=64` (4³) and `bxyz=125` (5³) — both perfect cubes.
132+
Strong hint: the optimized `gemm_nn_vbatch` makes a tile/block choice that
133+
assumes `M = bxyz` is a "nice" multiple of the kernel tile and falls off a
134+
cliff for awkward sizes.
135+
136+
This characterization will become stale as you optimize. Re-run
137+
`run_bench.sh` and refresh `REPORT.md` after any non-trivial change to
138+
`dgemm_vbatch.cu`.
139+
140+
---
141+
142+
## Workflow checklist for each optimization iteration
143+
144+
1. Edit `source/source_lcao/module_gint/kernel/dgemm_vbatch.cu`.
145+
2. `cmake --build build-test -j` (do **not** rebuild `build-baseline`).
146+
3. `cd ~/abacus/test-examples/ABACUS-test/LCAO_test/gemm_vbatch_bench && ./run_bench.sh`
147+
4. Re-parse the 12 `running_scf.log` files and update `REPORT.md`.
148+
5. **Verify correctness** before claiming a speedup: diff the SCF energies in
149+
`OUT.baseline/running_scf.log` vs `OUT.test/running_scf.log` for at least
150+
the cases that improved — a kernel that "speeds up" by skipping work will
151+
show up as an energy mismatch here.
152+
153+
---
154+
155+
## Notes / gotchas
156+
157+
- The runner deletes `OUT.ABACUS` and renames the result to `OUT.baseline` /
158+
`OUT.test`. Don't put precious artifacts in `OUT.ABACUS` between runs.
159+
- All `.upf` / `.orb` files in each case dir are **symlinks** into
160+
`~/abacus/test-examples/PP_ORB`. If that directory moves, the cases break.
161+
- GPU 0 is shared across the system. If a number jitters, re-run with
162+
`CUDA_VISIBLE_DEVICES=0` or pin the run to a known-idle device.
163+
- `scf_nmax=2` is intentional — the goal is to measure kernels, not converge
164+
the SCF. Don't bump it without a reason; it changes the call counts of every
165+
Gint timer in the table above.

0 commit comments

Comments
 (0)