@@ -1173,14 +1173,23 @@ def __init__(
11731173 # ``DP_CUTE_INFER``. Each flag is read once at construction so it becomes a
11741174 # compile-time constant in the traced (``make_fx``) graph, and each only
11751175 # takes effect during inference. ``DP_TRITON_INFER`` replaces the dense
1176- # ``bmm`` rotation with fused Triton kernels; ``DP_CUTE_INFER`` selects a
1177- # fused CuTe value-path operator and is independent of the Triton flag. The
1178- # CuTe value-path entry is bound at the end of construction (once every
1179- # submodule exists) and stays ``None`` when the backend is unavailable or
1180- # the block layout is unsupported.
1176+ # ``bmm`` rotation with fused Triton kernels; ``DP_CUTE_INFER`` selects
1177+ # the experimental CuTe value-path operator instead. Both flags claim
1178+ # the same ``so2_message`` value path, so enabling them together has no
1179+ # coherent meaning and is rejected at construction. The fused
1180+ # value-path entries are bound at the end of construction (once every
1181+ # submodule exists) and stay ``None`` when the backend is unavailable
1182+ # or the block layout is unsupported.
11811183 self .use_triton_infer = use_triton_infer ()
11821184 self .use_cute_infer = use_cute_infer ()
1185+ if self .use_triton_infer and self .use_cute_infer :
1186+ raise ValueError (
1187+ "DP_TRITON_INFER and DP_CUTE_INFER are mutually exclusive: "
1188+ "both select the fused SO(2) value-path backend. Enable "
1189+ "exactly one of them."
1190+ )
11831191 self ._cute_value_path = None
1192+ self ._triton_value_path = None
11841193
11851194 # === Step 1. Split deterministic seeds at the module top-level ===
11861195 seed_so2_stack = child_seed (seed , 0 )
@@ -1570,7 +1579,23 @@ def __init__(
15701579 self ._flash_atten_fn = flash_atten_aggregate
15711580 self ._build_row_ptr_fn = build_row_ptr
15721581
1573- # === Step 13. Optional fused CuTe SO(2) value-path operator ===
1582+ # === Step 13. Optional fused Triton SO(2) value-path operators ===
1583+ # Fuses rotate-to-local, the radial degree mixing, the gated mixing
1584+ # stack, and the focus competition of ``so2_message`` into the
1585+ # ``sezm_triton::so2_rotate_mix`` / ``so2_mixing_stack`` operators.
1586+ # The factory validates the block layout (``mmax == 1``, gated stack
1587+ # with an identity final layer, supported focus widths) and returns
1588+ # ``None`` otherwise, leaving the reference path in charge.
1589+ if self .use_triton_infer :
1590+ from .triton .so2_value_path import (
1591+ make_triton_value_path ,
1592+ )
1593+
1594+ self ._triton_value_path = make_triton_value_path (self )
1595+
1596+ # === Step 14. Optional fused CuTe SO(2) value-path operator ===
1597+ # Experimental alternative backend; mutually exclusive with the Triton
1598+ # flag (enforced above).
15741599 if self .use_cute_infer :
15751600 from .cute import (
15761601 make_cute_value_path ,
@@ -1922,7 +1947,15 @@ def so2_message(
19221947 src , dst = edge_cache .src , edge_cache .dst
19231948 n_edge = src .numel ()
19241949
1925- if self ._cute_value_path is not None and not self .training :
1950+ if self ._triton_value_path is not None and not self .training :
1951+ # === Steps 1-5 (fused Triton operators). ``so2_rotate_mix`` folds
1952+ # the rotation and the radial degree mixing into one edge-parallel
1953+ # kernel writing the focus-major layout; ``so2_mixing_stack`` runs
1954+ # the whole gated stack with the competition weight fused into its
1955+ # final store, keeping the inter-layer activations off the traced
1956+ # graph. ===
1957+ x_local , rad_feat = self ._triton_value_path (x , edge_cache , radial_feat )
1958+ elif self ._cute_value_path is not None and not self .training :
19261959 # === Steps 1-5 (fused CuTe operator). The operator folds
19271960 # rotate_to_local, radial degree mixing, the multi-layer gated SO(2)
19281961 # stack, and the focus competition into the bucketed kernels; the
0 commit comments