Skip to content

Commit f179f6a

Browse files
committed
perf(pt): unify DPA4 CuTe inference dispatch
1 parent e93ba33 commit f179f6a

107 files changed

Lines changed: 3126 additions & 1536 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

deepmd/dpmodel/descriptor/dpa4.py

Lines changed: 147 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,10 +1199,10 @@ def __init__(
11991199
# Accelerated backends may replace the distance-to-radial chain and the
12001200
# packed Wigner-D construction. The array-API reference leaves these
12011201
# hooks unbound and always retains the dense Wigner matrices.
1202-
self._cuda_radial_fn = None
1203-
self._cuda_wigner_fn = None
1204-
self._wigner_free_conv = False
1205-
self._packed_wigner_train = False
1202+
self.cuda_infer_l_1_radial = None
1203+
self.cuda_infer_l_1_wigner = None
1204+
self.cuda_infer_l_2_covers_all_blocks = False
1205+
self.cuda_train_covers_all_blocks = False
12061206

12071207
# === Optional descriptor-level attention residuals ===
12081208
self.final_block_attn_res = None
@@ -1542,9 +1542,31 @@ def _run_graph(
15421542
graph = apply_pair_exclusion(graph, atype_flat, self.emask)
15431543
if n_out_nodes is None:
15441544
n_out_nodes = atype_flat.shape[0]
1545+
packed_wigner_graph = self.prepare_packed_wigner_graph(
1546+
graph, atype_flat.shape[0]
1547+
)
1548+
packed_wigner = packed_wigner_graph is not None
1549+
if packed_wigner_graph is not None:
1550+
graph = packed_wigner_graph
15451551
edge_index = graph.edge_index
15461552
edge_vec = graph.edge_vec
15471553
edge_mask = graph.edge_mask
1554+
# Graph-owned endpoint orderings remain aligned with the edge payload
1555+
# and are shared by every segmented consumer through the edge cache.
1556+
graph_csr_cache = None
1557+
if all(
1558+
value is not None
1559+
for value in (
1560+
graph.destination_order,
1561+
graph.destination_row_ptr,
1562+
graph.source_order,
1563+
graph.source_row_ptr,
1564+
)
1565+
):
1566+
graph_csr_cache = {
1567+
"dst": (graph.destination_order, graph.destination_row_ptr),
1568+
"src": (graph.source_order, graph.source_row_ptr),
1569+
}
15481570

15491571
xp = array_api_compat.array_namespace(edge_vec)
15501572
device = array_api_compat.device(edge_vec)
@@ -1592,14 +1614,18 @@ def _run_graph(
15921614
bridging_switch=self.bridging_switch,
15931615
edge_envelope=self.edge_envelope,
15941616
radial_basis=self.radial_basis,
1595-
fused_radial=None if training else self._cuda_radial_fn,
1596-
fused_wigner=None if training else self._cuda_wigner_fn,
1617+
fused_radial=None if training else self.cuda_infer_l_1_radial,
1618+
fused_wigner=None if training else self.cuda_infer_l_1_wigner,
15971619
# Random local-Z roll is a training-only augmentation; the model
15981620
# is roll-equivariant, so inference fixes gamma.
15991621
random_gamma=self.random_gamma and training,
16001622
wigner_calc=self.wigner_calc,
1601-
build_wigner=self._build_full_wigner(),
1623+
build_wigner=self._build_full_wigner() or packed_wigner,
16021624
node_partial_exchange=node_partial_exchange,
1625+
packed_wigner=packed_wigner,
1626+
destinations_sorted=graph.destination_sorted,
1627+
packed_wigner_fn=self.build_packed_wigner,
1628+
csr_cache=graph_csr_cache,
16031629
)
16041630

16051631
ebed_dim_0 = self.node_init_dim # (node_init_lmax+1)^2
@@ -1719,6 +1745,9 @@ def _run_graph(
17191745
edge_cache = edge_cache_to_dtype(
17201746
edge_cache, get_xp_precision(xp, self.precision)
17211747
)
1748+
edge_cache.cute_infer_so2_metadata = self.prepare_cute_infer_so2_metadata(
1749+
edge_cache, n_nodes
1750+
)
17221751
x = self._forward_blocks(
17231752
x, edge_cache, rad_feat_per_block, comm_dict=comm_dict
17241753
)
@@ -1897,10 +1926,30 @@ def _apply_readout(self, x: Array, n_rows: int) -> Array:
18971926
)
18981927
for layer in self.readout_pre_layers:
18991928
x_ro = x_ro + layer(x_ro)
1929+
if not self.readout_pre_layers and self.so3_readout != "none":
1930+
accelerated = self.run_cute_infer_readout(x_ro)
1931+
if accelerated is not None:
1932+
return xp.reshape(accelerated, (n_rows, 1, 1, self.channels))
19001933
if self.so3_readout == "none":
19011934
return (x_ro + self.output_ffn(x_ro))[:, 0:1, :, :]
19021935
return x_ro[:, 0:1, :, :] + self.output_ffn.call_scalar(x_ro)
19031936

1937+
def run_cute_infer_readout(self, ffn_in: Array) -> Array | None:
1938+
"""Run the CuTe readout when its exact inference contract matches.
1939+
1940+
Parameters
1941+
----------
1942+
ffn_in : Array
1943+
Equivariant readout input with shape ``(N, D, 1, C)``.
1944+
1945+
Returns
1946+
-------
1947+
Array or None
1948+
Residual-inclusive scalar output with shape ``(N, C)``, or ``None``
1949+
when the backend has no eligible implementation.
1950+
"""
1951+
return None
1952+
19041953
def _edge_quaternion(self, edge_cache: EdgeCache) -> Array:
19051954
"""
19061955
Return the cached global->local edge quaternion, rebuilding if absent.
@@ -1931,8 +1980,73 @@ def _build_full_wigner(self) -> bool:
19311980
if not self._need_full_wigner:
19321981
return False
19331982
if self._in_training_mode():
1934-
return not self._packed_wigner_train
1935-
return not self._wigner_free_conv
1983+
return not self.cuda_train_covers_all_blocks
1984+
return not self.cuda_infer_l_2_covers_all_blocks
1985+
1986+
def prepare_packed_wigner_graph(
1987+
self,
1988+
graph: NeighborGraph,
1989+
n_nodes: int,
1990+
) -> NeighborGraph | None:
1991+
"""Prepare an edge graph for backend packed-Wigner storage.
1992+
1993+
Parameters
1994+
----------
1995+
graph : NeighborGraph
1996+
Edge graph supplied to the descriptor.
1997+
n_nodes : int
1998+
Number of nodes addressed by the graph.
1999+
2000+
Returns
2001+
-------
2002+
NeighborGraph or None
2003+
A graph satisfying the packed layout contract, or ``None`` when
2004+
the backend does not select that representation.
2005+
"""
2006+
return None
2007+
2008+
def build_packed_wigner(
2009+
self,
2010+
edge_quat: Array,
2011+
wigner_calc: Any,
2012+
) -> Array | None:
2013+
"""Build backend-specific packed Wigner storage when available.
2014+
2015+
Parameters
2016+
----------
2017+
edge_quat : Array
2018+
Global-to-local edge quaternions with shape ``(E, 4)``.
2019+
wigner_calc : Any
2020+
Wigner calculator carrying the degree and basis convention.
2021+
2022+
Returns
2023+
-------
2024+
Array or None
2025+
Packed per-edge Wigner storage, or ``None`` to retain dense storage.
2026+
"""
2027+
return None
2028+
2029+
def prepare_cute_infer_so2_metadata(
2030+
self,
2031+
edge_cache: EdgeCache,
2032+
n_nodes: int,
2033+
) -> tuple[Array, Array, Array] | None:
2034+
"""Build the edge metadata consumed by the CuTe SO2 implementation.
2035+
2036+
Parameters
2037+
----------
2038+
edge_cache : EdgeCache
2039+
Per-forward cache carrying the destination-major edge payload.
2040+
n_nodes : int
2041+
Number of nodes addressed by the edge payload.
2042+
2043+
Returns
2044+
-------
2045+
tuple[Array, Array, Array] or None
2046+
Destination row pointers, source order, and source row pointers, or
2047+
``None`` when the backend does not select CuTe SO2.
2048+
"""
2049+
return None
19362050

19372051
def _shared_wigner_runs(
19382052
self,
@@ -1981,6 +2095,8 @@ def _build_gie_zonal_coupling(
19812095
the blocks are skipped (all-Cartesian model) the full coupling is
19822096
reconstructed from the edge quaternion via the m=0-only path.
19832097
"""
2098+
if edge_cache.D_packed is not None:
2099+
return self.build_cute_infer_zonal_coupling(edge_cache)
19842100
if edge_cache.Dt_full is None:
19852101
calc = self.gie_zonal_wigner_calc or self.wigner_calc
19862102
shared = self._shared_wigner_runs(edge_cache, calc.lmax)
@@ -2008,6 +2124,28 @@ def _build_gie_zonal_coupling(
20082124
)
20092125
return xp.concat([mp_coupling, extra_coupling], axis=1)
20102126

2127+
def build_cute_infer_zonal_coupling(self, edge_cache: EdgeCache) -> Array:
2128+
"""Extract the GIE zonal coupling from packed Wigner storage.
2129+
2130+
Parameters
2131+
----------
2132+
edge_cache : EdgeCache
2133+
Per-forward cache carrying backend packed Wigner storage.
2134+
2135+
Returns
2136+
-------
2137+
Array
2138+
Zonal coupling with shape ``(E, D_node - 1)``.
2139+
2140+
Raises
2141+
------
2142+
NotImplementedError
2143+
If a backend supplies packed Wigner storage without this extractor.
2144+
"""
2145+
raise NotImplementedError(
2146+
"packed Wigner storage requires a backend zonal-coupling implementation"
2147+
)
2148+
20112149
def _apply_charge_spin_embedding(
20122150
self,
20132151
type_ebed: Array,

0 commit comments

Comments
 (0)