Skip to content

Commit 193b49e

Browse files
author
Han Wang
committed
perf(dpmodel): stop broadcasting DPA4 so3 linear weights across nodes
Both so3 channel mixers spelled their einsum as a batched matmul with the NODE/EDGE axis as the matmul BATCH and the weight carrying a dummy leading axis: matmul(x[:, :, :, None, :], weight_expanded[None, ...]) matmul broadcasts batch axes, so this expands the weight to (N, D, F, Cin, Cout). For examples/water/dpa4 that turns a 165K-element parameter into 191M elements -- about 0.8 GB -- on every call, and autograd must then reduce the whole expanded gradient back to the parameter shape. An op-level CUDA profile of a DPA4 training step attributed 45.6 ms per call to that ExpandBackward0 reduce over a [1152, 9, 1, 32, 576] operand, three calls per step, making it the most expensive kernel in the run; the ChannelLinear twin cost a further ~7-9 ms per call over [102510, 1, 32, 64]. The pt backend spells the same contraction as torch.einsum and never expands the weight. Batch over the small (D, F) / (F,) axes instead, which keeps N as matmul ROWS. The weight is then used in place and its gradient is an ordinary matmul. The transposes this adds touch only the (N, D, F, C) operands, which are orders of magnitude smaller than the expanded weight.
1 parent 01c58e6 commit 193b49e

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

  • deepmd/dpmodel/descriptor/dpa4_nn

deepmd/dpmodel/descriptor/dpa4_nn/so3.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,19 @@ def call(self, x: Any) -> Any:
131131
xp, self.weight[...], device=array_api_compat.device(x)
132132
)
133133
weight = xp.reshape(weight, (self.in_channels, self.n_focus, self.out_channels))
134-
# einsum "bfi,ifo->bfo" as a broadcast batched matmul:
135-
# (B, F, 1, Cin) @ (1, F, Cin, Cout) -> (B, F, 1, Cout)
134+
# einsum "bfi,ifo->bfo" as a matmul batched over the FOCUS axis.
135+
#
136+
# NOT as ``matmul(x[:, :, None, :], weight[None, ...])``: that makes B a
137+
# batch axis, so matmul broadcasts the weight to (B, F, Cin, Cout) --
138+
# inflating a few-hundred-KB parameter into hundreds of millions of
139+
# elements per call, whose gradient autograd must then reduce back down
140+
# (an ``ExpandBackward0`` reduce that measured as the single most
141+
# expensive kernel of a DPA4 training step). Batching over F instead
142+
# keeps B as matmul ROWS, so the weight is used in place and its
143+
# gradient is an ordinary matmul.
136144
weight = xp.permute_dims(weight, (1, 0, 2)) # (F, Cin, Cout)
137-
out = xp.matmul(x[:, :, None, :], weight[None, ...])[..., 0, :]
145+
out = xp.matmul(xp.permute_dims(x, (1, 0, 2)), weight) # (F, B, Cout)
146+
out = xp.permute_dims(out, (1, 0, 2)) # (B, F, Cout)
138147
if self.use_bias:
139148
bias = xp_asarray_nodetach(
140149
xp, self.bias[...], device=array_api_compat.device(x)
@@ -439,12 +448,21 @@ def call(self, x: Any) -> Any:
439448
weight_expanded = xp.take(weight, expand_index, axis=0) # (D, Cin, F, Cout)
440449

441450
# === Step 2. Per-focus, per-degree channel mixing ===
442-
# einsum "ndfi,difo->ndfo" as a broadcast batched matmul:
443-
# (N, D, F, 1, Cin) @ (1, D, F, Cin, Cout) -> (N, D, F, 1, Cout)
451+
# einsum "ndfi,difo->ndfo" as a matmul batched over the (D, F) axes.
452+
#
453+
# NOT as ``matmul(x[:, :, :, None, :], weight_expanded[None, ...])``:
454+
# that makes N a batch axis, so matmul broadcasts the weight to
455+
# (N, D, F, Cin, Cout) -- for the water DPA4 example, a 165K-element
456+
# parameter expanded to 191M elements (~0.8 GB) on every call, whose
457+
# gradient autograd then reduces back down. That ``ExpandBackward0``
458+
# reduce measured at 45.6 ms per call, three calls per training step:
459+
# the most expensive kernel in the run. Batching over the small (D, F)
460+
# axes keeps N as matmul ROWS, so the weight is never expanded.
444461
weight_expanded = xp.permute_dims(
445462
weight_expanded, (0, 2, 1, 3)
446463
) # (D, F, Cin, Cout)
447-
out = xp.matmul(x[:, :, :, None, :], weight_expanded[None, ...])[..., 0, :]
464+
out = xp.matmul(xp.permute_dims(x, (1, 2, 0, 3)), weight_expanded)
465+
out = xp.permute_dims(out, (2, 0, 1, 3)) # (N, D, F, Cout)
448466

449467
# === Step 3. Add l=0 bias ===
450468
if self.mlp_bias:

0 commit comments

Comments
 (0)