Skip to content

Commit 2344b4d

Browse files
committed
fix(pt-expt): harden Neo CuTe integration
1 parent 85c4efb commit 2344b4d

20 files changed

Lines changed: 212 additions & 107 deletions

deepmd/pt/model/descriptor/sezm.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,8 @@ def _prepare_cute_k1_sorted_metadata(
18021802
torch.Tensor | None,
18031803
]:
18041804
"""Build one per-forward CSR tensor bundle for eligible K1 blocks."""
1805+
if torch.jit.is_scripting():
1806+
return None, None, None
18051807
if (
18061808
self.training
18071809
or not edge_cache.destinations_sorted

deepmd/pt_expt/kernels/cute/sezm/k1.py

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
from functools import (
1616
lru_cache,
1717
)
18+
from itertools import (
19+
count,
20+
)
1821
from types import (
1922
SimpleNamespace,
2023
)
@@ -481,7 +484,7 @@ class _RunnerState:
481484

482485

483486
_REGISTRY: dict[int, _RegistryEntry] = {}
484-
_NEXT_HANDLE = 1
487+
_HANDLE_COUNTER = count(1)
485488
_REGISTRY_LOCK = threading.Lock()
486489
_PACKED_RUNNER_CACHE: dict[tuple[str, int | None, int], _RunnerState] = {}
487490
_PACKED_RUNNER_CACHE_LOCK = threading.Lock()
@@ -523,7 +526,6 @@ def _compile_output_gate_backward(
523526

524527
def register_cute_k1_block(block: Any, config: Any) -> int:
525528
"""Register a DeePMD block/config pair and return a stable integer handle."""
526-
global _NEXT_HANDLE
527529

528530
def remove_collected_entry(block_ref: weakref.ReferenceType[Any]) -> None:
529531
with _REGISTRY_LOCK:
@@ -532,8 +534,7 @@ def remove_collected_entry(block_ref: weakref.ReferenceType[Any]) -> None:
532534
_REGISTRY.pop(handle, None)
533535

534536
with _REGISTRY_LOCK:
535-
handle = _NEXT_HANDLE
536-
_NEXT_HANDLE += 1
537+
handle = next(_HANDLE_COUNTER)
537538
_REGISTRY[handle] = _RegistryEntry(
538539
block=block,
539540
config=config,
@@ -800,8 +801,7 @@ def _equivariant_rmsnorm_backward(norm: Any, x: Tensor, grad_out: Tensor) -> Ten
800801
return torch.cat([grad_x0_in, grad_xt_in], dim=1).to(dtype=in_dtype)
801802

802803

803-
def _so3_linear_backward_input(linear: Any, x: Tensor, grad_out: Tensor) -> Tensor:
804-
del x
804+
def _so3_linear_backward_input(linear: Any, grad_out: Tensor) -> Tensor:
805805
weight = linear.weight.view(
806806
linear.lmax + 1,
807807
linear.in_channels,
@@ -994,14 +994,9 @@ def _so3_grid_cross_glu_flat_backward(
994994
coeff = torch.einsum("dkg,ngfc->ndfkc", from_grid, left_grid * right_grid)
995995
coeff_flat = coeff.reshape(n_batch, coeff_dim, n_focus, expanded)
996996

997-
scalar_out = _swiglu_forward(scalar_pair)
998997
scalar_logits = _focus_linear_forward(net.scalar_gate, scalar_pair)
999998
scalar_gate = torch.sigmoid(scalar_logits)
1000999
coeff_view = coeff_flat.reshape(n_batch, coeff_dim, n_focus, n_frames, channels)
1001-
scalar_path = coeff_view * scalar_gate[:, None, :, None, :]
1002-
scalar_path = scalar_path.clone()
1003-
scalar_path[:, 0, :, net.frame_zero_index, :].add_(scalar_out)
1004-
scalar_path_flat = scalar_path.reshape(n_batch, coeff_dim, n_focus, expanded)
10051000

10061001
grad = grad_out_flat.reshape(n_batch, coeff_dim, n_focus, channels).to(
10071002
dtype=net.dtype
@@ -1046,7 +1041,6 @@ def _so3_grid_cross_glu_flat_backward(
10461041
grad_context = _frame_expand_backward_input(net.frame_expand, grad_right)
10471042
grad_query[:, 0, :, :].add_(grad_scalar_pair[:, :, :channels])
10481043
grad_context[:, 0, :, :].add_(grad_scalar_pair[:, :, channels:])
1049-
del scalar_path_flat
10501044
return (
10511045
grad_query.reshape_as(query_flat).to(dtype=q_dtype),
10521046
grad_context.reshape_as(context_flat).to(dtype=c_dtype),
@@ -1065,7 +1059,6 @@ def _final_manual_backward(runner: Any, grad_out: Tensor) -> tuple[Tensor, Tenso
10651059
phase = runner.phase_c_out.detach()
10661060
x_wide = runner.x_wide.detach()
10671061
out_gate_flat = runner.out_gate_flat
1068-
post_in = runner.post_mix_input.unsqueeze(2)
10691062
post_norm_in = runner.post_norm_input
10701063

10711064
grad_post_norm_in = _equivariant_rmsnorm_backward(
@@ -1075,7 +1068,6 @@ def _final_manual_backward(runner: Any, grad_out: Tensor) -> tuple[Tensor, Tenso
10751068
)
10761069
grad_post_mix = _so3_linear_backward_input(
10771070
so2.post_focus_mix,
1078-
post_in,
10791071
grad_post_norm_in.squeeze(2).unsqueeze(2),
10801072
).squeeze(2)
10811073

@@ -1145,7 +1137,6 @@ def _qk_manual_backward(
11451137
) -> Tensor:
11461138
so2 = runner.so2
11471139
n_node = runner.node_count
1148-
n_edge = runner.edge_count
11491140
x_wide = runner.x_wide.detach()
11501141
x_l0 = x_wide[:, 0, :].reshape(n_node, 2, 32)
11511142
q_node = runner.q_node
@@ -1285,20 +1276,16 @@ def _stack_backward_manual(runner: Any, grad_stack_out: Tensor) -> Tensor:
12851276
def _x_wide_manual_backward(runner: Any, grad_x_wide_total: Tensor) -> Tensor:
12861277
block = runner.block
12871278
so2 = runner.so2
1288-
n_node = runner.node_count
12891279
x_so2 = runner.x if runner.use_full_node else runner.x[:, : block.mp_ebed_dim, :, :]
1290-
x_pre = block.pre_so2_norm(x_so2)
1291-
x_pre_flat = x_pre.reshape(n_node, x_so2.shape[1], block.channels).unsqueeze(2)
1280+
if not _is_identity(block.pre_so2_norm):
1281+
raise NotImplementedError(
1282+
"manual x-wide backward currently expects Identity pre norm"
1283+
)
12921284
grad_x_pre_flat = _so3_linear_backward_input(
12931285
so2.pre_focus_mix,
1294-
x_pre_flat,
12951286
grad_x_wide_total.unsqueeze(2),
12961287
)
12971288
grad_x_pre = grad_x_pre_flat.squeeze(2).reshape_as(x_so2)
1298-
if type(block.pre_so2_norm).__name__ != "Identity":
1299-
raise NotImplementedError(
1300-
"manual x-wide backward currently expects Identity pre norm"
1301-
)
13021289
if runner.use_full_node:
13031290
grad_x = grad_x_pre
13041291
else:

deepmd/pt_expt/kernels/cute/sezm/k1_kernels/cute_neo_phase_a_radial_forward.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,10 @@ def run_neo_phase_a_radial_forward_packed_direct(
346346
)
347347
if tuple(radial_degree_mixer.weight.shape) != (4 * 64, 25):
348348
raise NotImplementedError("collapsed radial mixer expects lmax=3,mmax=1,C=64")
349-
if tuple(radial_degree_mixer.channel_basis.shape) != (64,):
350-
raise NotImplementedError("collapsed radial mixer expects 64 channel weights")
349+
if tuple(radial_degree_mixer.channel_basis.shape) != (1, 64):
350+
raise NotImplementedError(
351+
"collapsed radial mixer expects a rank-1, 64-channel basis"
352+
)
351353

352354
combined_weight = _combined_radial_weight(
353355
radial_hidden_proj,

deepmd/pt_expt/kernels/cute/sezm/k1_kernels/cute_neo_phase_c_onepass.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,19 @@ def run_neo_phase_c_onepass_output_gate(
512512
raise ValueError("dst_ptr must be on the input CUDA device")
513513
if dst_ptr.dtype not in (torch.int32, torch.int64):
514514
raise TypeError(f"dst_ptr must be int32 or int64, got {dst_ptr.dtype}")
515+
torch._assert_async(
516+
dst_ptr[0] == 0,
517+
"Phase-C forward requires dst_ptr[0] == 0",
518+
)
519+
torch._assert_async(
520+
dst_ptr[-1] == edge_count,
521+
"Phase-C forward requires dst_ptr[-1] == edge_count",
522+
)
523+
if dst_ptr.numel() > 1:
524+
torch._assert_async(
525+
torch.all(dst_ptr[1:] >= dst_ptr[:-1]),
526+
"Phase-C forward requires nondecreasing dst_ptr",
527+
)
515528

516529
if out is None:
517530
out = torch.empty(

deepmd/pt_expt/kernels/cute/sezm/k1_kernels/cute_neo_radial_phase_a_backward_node.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,27 +120,25 @@ def _warp_owned_grad_compact(
120120
shared_row_pitch: cutlass.Constexpr[int],
121121
):
122122
"""Reduce one compact-kernel gradient inside a four-lane subgroup."""
123-
in_coeff = cutlass.Int32(0)
124-
out_coeff = cutlass.Int32(0)
123+
value = cutlass.Float32(0.0)
125124
if compact_idx < 16:
126125
in_coeff = compact_idx // 4
127126
out_coeff = compact_idx - in_coeff * 4
128-
else:
129-
pair = compact_idx - 16
130-
in_coeff = pair // 3
131-
out_coeff = pair - in_coeff * 3
132-
133-
value = cutlass.Float32(0.0)
134-
for channel_step in cutlass.range_constexpr(CHANNELS_PER_SUBGROUP_LANE):
135-
hidden_channel = subgroup_lane + channel_step * WARP_REDUCTION_GROUP
136-
basis = channel_basis[hidden_channel].to(cutlass.Float32)
137-
if compact_idx < 16:
127+
for channel_step in cutlass.range_constexpr(CHANNELS_PER_SUBGROUP_LANE):
128+
hidden_channel = subgroup_lane + channel_step * WARP_REDUCTION_GROUP
129+
basis = channel_basis[hidden_channel].to(cutlass.Float32)
138130
value += (
139131
focus_grad[out_coeff * shared_row_pitch + hidden_channel]
140132
* local_values[in_coeff * shared_row_pitch + hidden_channel]
141133
* basis
142134
)
143-
else:
135+
else:
136+
pair = compact_idx - 16
137+
in_coeff = pair // 3
138+
out_coeff = pair - in_coeff * 3
139+
for channel_step in cutlass.range_constexpr(CHANNELS_PER_SUBGROUP_LANE):
140+
hidden_channel = subgroup_lane + channel_step * WARP_REDUCTION_GROUP
141+
basis = channel_basis[hidden_channel].to(cutlass.Float32)
144142
value += basis * (
145143
focus_grad[(4 + out_coeff) * shared_row_pitch + hidden_channel]
146144
* local_values[(4 + in_coeff) * shared_row_pitch + hidden_channel]

deepmd/pt_expt/kernels/cute/sezm/k1_message_grid_packed.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,26 @@
77
)
88

99
from typing import (
10-
TYPE_CHECKING,
1110
Any,
11+
Protocol,
1212
)
1313

1414
import torch
1515
from torch import (
1616
Tensor,
1717
)
1818

19-
if TYPE_CHECKING:
20-
from .message_grid_readout_sm90 import (
21-
Sm90MessageGridState,
22-
)
23-
24-
2519
COEFF_DIM = 16
2620
N_FOCUS = 2
2721
N_FRAMES = 3
22+
23+
24+
class _Sm90MessageGridState(Protocol):
25+
"""State subset consumed by the packed SM90 forward path."""
26+
27+
schedule: Tensor
28+
29+
2830
CHANNELS = 32
2931
HIDDEN_CHANNELS = N_FOCUS * CHANNELS
3032

@@ -205,7 +207,7 @@ def run_packed_message_grid_forward(
205207
context_flat: Tensor,
206208
*,
207209
return_product: bool = False,
208-
sm90_state: Sm90MessageGridState | None = None,
210+
sm90_state: _Sm90MessageGridState | None = None,
209211
) -> Tensor | tuple[Tensor, Tensor]:
210212
"""Run only the message-grid module and return its canonical flat output."""
211213
query, context = _validate_contract(net, query_flat, context_flat)

deepmd/pt_expt/kernels/cute/sezm/k1_radial_phase_a_node.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ def build_source_csr(
6363
) -> NeoSourceCSR:
6464
"""Build indirect source CSR without changing the physical edge order.
6565
66-
``validate_sources=True`` synchronizes when ``src`` is CUDA. Callers should
67-
build this once with the edge cache and retain both tensors.
66+
Source bounds are always checked before constructing the CSR. Setting
67+
``validate_sources=True`` reports an eager ``ValueError`` and therefore
68+
synchronizes when ``src`` is CUDA; the default uses an asynchronous device
69+
assertion. Callers should build this once with the edge cache and retain
70+
both tensors.
6871
"""
6972
import torch
7073

@@ -78,11 +81,16 @@ def build_source_csr(
7881
raise ValueError("source CSR int32 indexing requires E <= 2**31 - 1")
7982

8083
src = src.contiguous()
81-
if validate_sources and src.numel() != 0:
84+
if src.numel() != 0:
8285
valid = torch.all((src >= 0) & (src < node_count))
83-
if not bool(valid):
84-
raise ValueError(
85-
"source-CSR backward requires source indices in [0, node_count)"
86+
message = "source-CSR backward requires source indices in [0, node_count)"
87+
if validate_sources:
88+
if not bool(valid):
89+
raise ValueError(message)
90+
else:
91+
torch._assert_async(
92+
valid,
93+
message,
8694
)
8795

8896
source_order_i64 = torch.argsort(src, stable=True)
@@ -169,6 +177,10 @@ def _project_batched_radial_adjoint(
169177
"""Project the 27-column adjoint packed in consumed edge scratch."""
170178
import torch
171179

180+
if torch.backends.cuda.matmul.allow_tf32:
181+
raise RuntimeError("strict FP32 requires allow_tf32=False")
182+
if torch.get_float32_matmul_precision() != "highest":
183+
raise RuntimeError("strict FP32 requires float32 matmul precision 'highest'")
172184
edge_count = consumed_workspace.shape[0]
173185
device = consumed_workspace.device
174186
tensors = (
@@ -330,6 +342,24 @@ def run_neo_radial_phase_a_backward_node_tiled(
330342
)
331343
if node_count == 0 and edge_count != 0:
332344
raise ValueError("a non-empty edge list requires at least one source node")
345+
torch._assert_async(
346+
source_ptr[0] == 0,
347+
"source-CSR backward requires source_ptr[0] == 0",
348+
)
349+
torch._assert_async(
350+
source_ptr[-1] == edge_count,
351+
"source-CSR backward requires source_ptr[-1] == edge_count",
352+
)
353+
if source_ptr.numel() > 1:
354+
torch._assert_async(
355+
torch.all(source_ptr[1:] >= source_ptr[:-1]),
356+
"source-CSR backward requires nondecreasing source_ptr",
357+
)
358+
if source_order.numel() != 0:
359+
torch._assert_async(
360+
torch.all((source_order >= 0) & (source_order < edge_count)),
361+
"source-CSR backward requires source_order entries in [0, E)",
362+
)
333363
if validate_csr:
334364
_validate_csr_values(source_order, source_ptr, edge_count)
335365

deepmd/pt_expt/kernels/cute/sezm/message_grid_gaunt_sm90.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
CHANNELS = 64
4343
THREADS = 256
4444
GROUPS = 4
45-
THREADS_PER_GROUP = CHANNELS
4645
VALUES_PER_NODE = COEFF_DIM * CHANNELS
4746
EXPECTED_COMPACT_PATHS = 1968
4847
EXPECTED_ORDERED_PATHS = 3833

deepmd/pt_expt/kernels/cute/sezm/output_grid_kernels/cute_readout_l0.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import cutlass
1717
import cutlass.cute as cute
18+
import cutlass.pipeline as pipeline
1819
from cuda.bindings.driver import (
1920
CUstream,
2021
)
@@ -32,7 +33,6 @@
3233
TILE_K,
3334
TILE_M,
3435
TILE_N,
35-
TiledOutputGridProductBackward,
3636
)
3737

3838
# CuTe JIT functions use DSL-inferred argument and return types.
@@ -116,11 +116,16 @@ def kernel(
116116
out[node, channel] = value.to(out.element_type)
117117

118118

119-
class TiledReadoutL0GramBackward(TiledOutputGridProductBackward):
119+
class TiledReadoutL0GramBackward:
120120
"""Apply the frozen 48x48 Gram matrix to one 64-channel tile."""
121121

122122
def __init__(self) -> None:
123-
super().__init__(HIDDEN_CHANNELS)
123+
self.cta_tiler = (TILE_M, TILE_N, TILE_K)
124+
self.channel_tiles = HIDDEN_CHANNELS // TILE_N
125+
self.cta_sync_barrier = pipeline.NamedBarrier(
126+
barrier_id=1,
127+
num_threads=THREADS,
128+
)
124129

125130
@cute.jit
126131
def __call__(

deepmd/pt_expt/kernels/cute/sezm/sm90_k1/final_phase_c.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
from ..compile_cache import (
3838
device_aware_lru_cache,
3939
)
40+
from ..k1_wigner_layout import (
41+
PACKED_VALUE_COUNT,
42+
)
4043

4144
if TYPE_CHECKING:
4245
from collections.abc import (
@@ -53,7 +56,7 @@
5356
DEGREE_COUNT = 16
5457
M0_WIDTH = 128
5558
M1_WIDTH = 96
56-
PACKED_WIGNER_VALUES = 46
59+
PACKED_WIGNER_VALUES = PACKED_VALUE_COUNT
5760
M0_THREADS = M0_WIDTH
5861
M1_THREADS = M1_WIDTH * 2
5962
THREADS = M0_THREADS + M1_THREADS

0 commit comments

Comments
 (0)