Devices and scaling¶
diffBloch is optimized for GPU use. It checks for CUDA automatically and uses it if available; otherwise it falls back to CPU.
uv run diffbloch refine <experiment_dir> --device cuda
uv run diffbloch refine <experiment_dir> --device cpu
Both devices perform the same calculation to the same accuracy; only speed differs. The fastest
coupling_mode and solver are not the same on CPU as on GPU. The config defaults
(coupling_mode: union) are tuned for GPU and are not optimal for CPU.
CPU¶
union batches tilts into fewer, larger matrices. That pays off on GPU, where it improves
parallelism and amortizes launch overhead, but not on CPU, where the larger matrices simply cost
more per solve with no batching benefit. per_tilt is faster than union on CPU once the
structure matrix is larger than quartz’s (N≈40); union is faster only at that small a scale.
bloch_eigen is faster than matrix_exp in every case measured, sometimes by 4-5x. matrix_exp’s
backward pass carries a fixed penalty (see Forward vs backward time
below) that is worst on CPU.
Measured on one rotation, three refinement epochs, steady-state (excludes the first, cache-warm epoch):
Structure |
|
|
|
|
|---|---|---|---|---|
Quartz (tiny, N≈40) |
20.9 ms |
24.1 ms |
75.8 ms |
96.2 ms |
Borane (N≈450) |
4144 ms |
16483 ms |
2311 ms |
4853 ms |
CsPbBr₃ (N≈680) |
4268 ms |
23483 ms |
2221 ms |
6784 ms |
The fastest combination per structure is union for quartz and per_tilt for borane and CsPbBr₃.
GPU¶
union (the default) is faster than per_tilt almost everywhere on GPU, since fewer, larger
batched calls suit GPU parallelism. One exception was measured: CsPbBr₃ with matrix_exp, where
per_tilt is faster (390 ms vs 499 ms). union’s larger matrices make matrix_exp’s backward
penalty worst there, and it outweighs the batching benefit.
bloch_eigen is faster than matrix_exp under union. Under per_tilt, matrix_exp is faster
than bloch_eigen for borane and CsPbBr₃: the smaller matrices shrink matrix_exp’s backward
penalty enough that its cheaper forward pass, which does not need a full eigendecomposition, wins
overall.
Same methodology as the CPU table:
Structure |
|
|
|
|
|---|---|---|---|---|
Quartz (tiny, N≈40) |
14.3 ms |
18.2 ms |
106.9 ms |
115.7 ms |
Borane (N≈450) |
218.7 ms |
419.1 ms |
901.5 ms |
521.9 ms |
CsPbBr₃ (N≈680) |
289.9 ms |
499.5 ms |
624.8 ms |
389.7 ms |
bloch_eigen requires a Hermitian, non-absorptive structure matrix and is not the refine default
even where it is faster: its backward pass is ill-conditioned near degenerate eigenvalues, which
symmetric crystals routinely produce. matrix_exp remains necessary for absorptive structures, and
for non-absorptive structures where bloch_eigen gives unstable refinement.
Forward vs backward time (GPU)¶
matrix_exp’s backward pass costs several times its own forward pass; bloch_eigen’s does not.
This tracks structure-matrix size, the number of dynamically coupled beams, not the number of
refined parameters.
Structure |
Trainable params |
Structure matrix N |
|
ratio |
|
ratio |
|---|---|---|---|---|---|---|
Quartz |
24 |
≈40 |
9.6 / 8.6 ms |
0.9x |
6.7 / 7.7 ms |
1.15x |
CsPbBr₃ |
48 |
≈680 |
72 / 428 ms |
5.97x |
136 / 154 ms |
1.13x |
Borane |
240 |
≈450 |
60 / 359 ms |
5.98x |
102 / 117 ms |
1.15x |
CsPbBr₃ has fewer trainable parameters than borane (48 vs 240) but a larger structure matrix, and
its matrix_exp backward pass is correspondingly slower (428 ms vs 359 ms). Parameter count does
not predict backward cost; matrix size does. bloch_eigen’s ratio stays close to 1x regardless of
matrix size, since it does not carry matrix_exp’s backward penalty.
The rows above use union coupling; the per_tilt figures in the CPU/GPU tables show the same
forward/backward split scaled by that coupling choice.
GPU and CPU timings, full runs¶
The single-rotation figures above isolate the coupling and solver effect. The following are full experiment timings measured on the same machine with identical settings:
Experiment |
GPU |
CPU |
CPU/GPU time |
|---|---|---|---|
Quartz, absorptive refinement, 40 epochs |
57.1 s |
72.8 s |
1.3x |
CsPbBr₃, absorptive preprocessing, 59 rotations |
16 min 57 s |
5 h 00 min |
18x |
CsPbBr₃, absorptive refinement, 40 epochs |
20 min 52 s |
approximately 15.3 h |
44x |
CPU execution is reasonable for small structure matrices. GPU acceleration becomes increasingly
important as the number of dynamically coupled beams increases, and so does the choice of
coupling_mode and solver.
Profiling refinement¶
--profile reports the time spent calculating structure factors, solving each rotation, computing
gradients, and updating the structural parameters:
uv run diffbloch refine <experiment_dir> --device cuda --profile
Profiling synchronizes the GPU during each measured section and therefore slows the run. It is intended for diagnosis, not routine refinement.