@@ -952,8 +952,8 @@ def _remote_pointer(
952952
953953 return _create_remote_pointers_tensor (tensor , shard_id_tensor , _semantic , dtype = dtype , space = space , offset = offset )
954954
955- # dstoffset / srcoffset / nelems -> scalar i64 tl.tensor
956- # dstoffset and srcoffset must be >= 0.
955+ # offset / srcoffset / nelems -> scalar i64 tl.tensor
956+ # offset and srcoffset must be >= 0.
957957# nelems must be > 0.
958958def _normalize_node_i64 (value , label : str , * , must_be_positive : bool , _semantic ) -> tl .tensor :
959959 value = tl ._unwrap_if_constexpr (value )
@@ -1019,6 +1019,27 @@ def _normalize_put_coop_kind(coopkind) -> int:
10191019 return mapping [normalized ]
10201020
10211021
1022+ def _normalize_node_netidx (netidx , _semantic ) -> tl .tensor :
1023+ netidx = tl ._unwrap_if_constexpr (netidx )
1024+ if isinstance (netidx , int ):
1025+ if netidx < 0 or netidx > 0x7FFFFFFF :
1026+ raise ValueError (
1027+ f"node space netidx must be in int32 range [0, 2147483647], got { netidx } " )
1028+ netidx = _semantic .to_tensor (netidx )
1029+ elif not isinstance (netidx , tl .tensor ):
1030+ netidx = _semantic .to_tensor (netidx )
1031+
1032+ if not netidx .dtype .is_int ():
1033+ raise TypeError (
1034+ f"node space netidx must be an integer scalar, got { netidx .dtype } " )
1035+ if netidx .shape != ():
1036+ raise ValueError (
1037+ f"node space netidx must be scalar, got shape { netidx .shape } " )
1038+ if netidx .dtype != tl .int32 :
1039+ netidx = tl .cast (netidx , tl .int32 , _semantic = _semantic )
1040+ return netidx
1041+
1042+
10221043def _parse_node_context (builder , value , label : str , index : int ):
10231044 from triton .runtime import DistributedRtContext
10241045 value = tl ._unwrap_if_constexpr (value )
@@ -1027,45 +1048,195 @@ def _parse_node_context(builder, value, label: str, index: int):
10271048 return _parse_src_arg (builder , value , index )
10281049
10291050
1030- def _node_put (dst , shard_id , src , scope , dtype , offset , dstoffset , srcoffset ,
1031- nelems , coopkind , _semantic ) -> None :
1032- if dstoffset is None :
1033- raise TypeError ('tle.remote(..., space="node") requires dstoffset' )
1051+ class _node_remote_destination_type (tl .base_type ):
1052+
1053+ def __init__ (self , field_types , dtype : tl .dtype , elem_bytes : int , coop_kind : int ):
1054+ # dst_mem, src_mem, comm, peer, dst_offset, src_offset, nelems, net_idx.
1055+ self .field_types = tuple (field_types )
1056+ self .dtype = dtype
1057+ self .elem_bytes = elem_bytes
1058+ self .coop_kind = coop_kind
1059+
1060+ def _unflatten_ir (self , handles , cursor ):
1061+ fields = []
1062+ for field_type in self .field_types :
1063+ field , cursor = field_type ._unflatten_ir (handles , cursor )
1064+ fields .append (field )
1065+ return _node_remote_destination (
1066+ * fields ,
1067+ dtype = self .dtype ,
1068+ elem_bytes = self .elem_bytes ,
1069+ coop_kind = self .coop_kind ,
1070+ ), cursor
1071+
1072+ def _flatten_ir_types (self , builder , out ) -> None :
1073+ for field_type in self .field_types :
1074+ field_type ._flatten_ir_types (builder , out )
1075+
1076+ def mangle (self ) -> str :
1077+ fields = "_" .join (field_type .mangle () for field_type in self .field_types )
1078+ return f"node_remote_dst_{ self .dtype .mangle ()} _e{ self .elem_bytes } _c{ self .coop_kind } _{ fields } "
1079+
1080+ def __eq__ (self , other ) -> bool :
1081+ return (type (self ) is type (other ) and self .field_types == other .field_types and self .dtype == other .dtype
1082+ and self .elem_bytes == other .elem_bytes and self .coop_kind == other .coop_kind )
1083+
1084+ def __str__ (self ) -> str :
1085+ return f"node_remote_destination<{ self .dtype } , coop_kind={ self .coop_kind } >"
1086+
1087+ @property
1088+ def scalar (self ):
1089+ raise ValueError ('tle.remote(..., space="node") destinations only support tl.store' )
1090+
1091+
1092+ class _node_remote_destination (tl .base_value ):
1093+
1094+ def __init__ (self , dst_mem : tl .tensor , src_mem : tl .tensor , comm : tl .tensor ,
1095+ peer : tl .tensor , dst_offset : tl .tensor , src_offset : tl .tensor ,
1096+ nelems : tl .tensor , net_idx : tl .tensor , * , dtype : tl .dtype ,
1097+ elem_bytes : int , coop_kind : int ):
1098+ super ().__init__ ()
1099+ self .dst_mem = dst_mem
1100+ self .src_mem = src_mem
1101+ self .comm = comm
1102+ self .peer = peer
1103+ self .dst_offset = dst_offset
1104+ self .src_offset = src_offset
1105+ self .nelems = nelems
1106+ self .net_idx = net_idx
1107+ self .dtype = dtype
1108+ self .elem_bytes = elem_bytes
1109+ self .coop_kind = coop_kind
1110+
1111+ @property
1112+ def type (self ):
1113+ fields = (self .dst_mem , self .src_mem , self .comm , self .peer ,
1114+ self .dst_offset , self .src_offset , self .nelems , self .net_idx )
1115+ return _node_remote_destination_type (
1116+ tuple (field .type for field in fields ),
1117+ self .dtype ,
1118+ self .elem_bytes ,
1119+ self .coop_kind ,
1120+ )
1121+
1122+ def _flatten_ir (self , handles ) -> None :
1123+ for field in (self .dst_mem , self .src_mem , self .comm , self .peer ,
1124+ self .dst_offset , self .src_offset , self .nelems ,
1125+ self .net_idx ):
1126+ field ._flatten_ir (handles )
1127+
1128+ def _unsupported_pointer_operation (self ):
1129+ raise ValueError ('tle.remote(..., space="node") destinations only support tl.store' )
1130+
1131+ def __add__ (self , other ):
1132+ self ._unsupported_pointer_operation ()
1133+
1134+ def __radd__ (self , other ):
1135+ self ._unsupported_pointer_operation ()
1136+
1137+ def __sub__ (self , other ):
1138+ self ._unsupported_pointer_operation ()
1139+
1140+ def __rsub__ (self , other ):
1141+ self ._unsupported_pointer_operation ()
1142+
1143+ def __getitem__ (self , index ):
1144+ self ._unsupported_pointer_operation ()
1145+
1146+ def __triton_load__ (self , mask , other , boundary_check , padding_option , cache_modifier , eviction_policy ,
1147+ volatile , flagtree_hints , _semantic = None ):
1148+ self ._unsupported_pointer_operation ()
1149+
1150+ def __triton_store__ (self , value , mask , boundary_check , cache_modifier , eviction_policy , _semantic = None ):
1151+ if value is not tl ._STORE_VALUE_UNSET :
1152+ raise TypeError (
1153+ "tl.store to a node remote destination does not accept a value; "
1154+ "pass the source context to tle.remote(..., src=...) and call tl.store(remote_dst)" )
1155+ if mask is not None :
1156+ raise ValueError ("tl.store to a node remote destination does not support mask" )
1157+ boundary_check = tl ._unwrap_if_constexpr (boundary_check )
1158+ if isinstance (boundary_check , tl .tuple ):
1159+ boundary_check = tuple (boundary_check )
1160+ if boundary_check :
1161+ raise ValueError ("tl.store to a node remote destination does not support boundary_check" )
1162+ if cache_modifier :
1163+ raise ValueError ("tl.store to a node remote destination does not support cache_modifier" )
1164+ if eviction_policy :
1165+ raise ValueError ("tl.store to a node remote destination does not support eviction_policy" )
1166+
1167+ builder = _semantic .builder
1168+ if not hasattr (builder , "create_remote_pointers" ):
1169+ raise RuntimeError (
1170+ "node put requires TLE remote_pointers support in the active Triton build" )
1171+ builder .create_remote_pointers (
1172+ None ,
1173+ None ,
1174+ self .peer .handle ,
1175+ "node" ,
1176+ self .dst_offset .handle ,
1177+ self .dst_mem .handle ,
1178+ self .src_mem .handle ,
1179+ self .comm .handle ,
1180+ self .src_offset .handle ,
1181+ self .nelems .handle ,
1182+ self .net_idx .handle ,
1183+ self .elem_bytes ,
1184+ self .coop_kind ,
1185+ )
1186+ return _semantic .tensor (None , tl .void )
1187+
1188+
1189+ def _create_node_remote_destination (dst , shard_id , scope , dtype , offset , src ,
1190+ srcoffset , nelems , coopkind , netidx ,
1191+ _semantic ) -> _node_remote_destination :
1192+ if offset is None :
1193+ raise TypeError ('tle.remote(..., space="node") requires offset' )
10341194 if nelems is None :
10351195 raise TypeError ('tle.remote(..., space="node") requires nelems' )
10361196 if coopkind is None :
10371197 raise TypeError ('tle.remote(..., space="node") requires coopkind' )
10381198 if dtype is None :
10391199 raise TypeError ('tle.remote(..., space="node") requires dtype' )
1040- if offset is not None :
1041- raise ValueError ('tle.remote(..., space="node") does not accept offset; use dstoffset and srcoffset' )
10421200
10431201 builder = _semantic .builder
1044- if not hasattr (builder , "create_node_put" ):
1045- raise RuntimeError ("node put requires TLE node_put support in the active Triton build" )
1202+ if not hasattr (builder , "create_remote_pointers" ):
1203+ raise RuntimeError (
1204+ "node put requires TLE remote_pointers support in the active Triton build" )
10461205
10471206 peer = _normalize_node_peer (shard_id , scope , _semantic )
1207+ dtype = tl ._unwrap_if_constexpr (dtype )
10481208 elem_bytes = _normalize_node_elem_bytes (dtype )
10491209
1050- dstoffset = _normalize_node_i64 (dstoffset , "dstoffset" , must_be_positive = False , _semantic = _semantic )
1210+ offset = _normalize_node_i64 (
1211+ offset , "offset" , must_be_positive = False , _semantic = _semantic )
10511212 if srcoffset is None :
1052- srcoffset = dstoffset
1213+ srcoffset = offset
10531214 else :
1054- srcoffset = _normalize_node_i64 (srcoffset , "srcoffset" , must_be_positive = False , _semantic = _semantic )
1055- nelems = _normalize_node_i64 (nelems , "nelems" , must_be_positive = True , _semantic = _semantic )
1215+ srcoffset = _normalize_node_i64 (
1216+ srcoffset , "srcoffset" , must_be_positive = False , _semantic = _semantic )
1217+ nelems = _normalize_node_i64 (
1218+ nelems , "nelems" , must_be_positive = True , _semantic = _semantic )
1219+ net_idx = _normalize_node_netidx (netidx , _semantic )
10561220 coop_kind = _normalize_put_coop_kind (coopkind )
10571221
1058- dst_mem_handle = _parse_node_context (builder , dst , "dst" , 0 )
1059- dst_comm_handle = _parse_node_context (builder , dst , "dst" , 1 )
1060- src_mem_handle = (dst_mem_handle if src is None else
1061- _parse_node_context (builder , src , "src" , 0 ))
1062-
1063- builder .create_node_put (dst_mem_handle , src_mem_handle , dst_comm_handle ,
1064- peer .handle , dstoffset .handle ,
1065- srcoffset .handle , nelems .handle , elem_bytes ,
1066- coop_kind )
1067- return None
1068-
1222+ if src is None :
1223+ src = dst
1224+ dst_mem = tl .tensor (_parse_node_context (builder , dst , "dst" , 0 ), tl .int64 )
1225+ src_mem = tl .tensor (_parse_node_context (builder , src , "src" , 0 ), tl .int64 )
1226+ dst_comm = tl .tensor (_parse_node_context (builder , dst , "dst" , 1 ), tl .int64 )
1227+ return _node_remote_destination (
1228+ dst_mem ,
1229+ src_mem ,
1230+ dst_comm ,
1231+ peer ,
1232+ offset ,
1233+ srcoffset ,
1234+ nelems ,
1235+ net_idx ,
1236+ dtype = dtype ,
1237+ elem_bytes = elem_bytes ,
1238+ coop_kind = coop_kind ,
1239+ )
10691240
10701241@tl .builtin
10711242def remote (
@@ -1076,10 +1247,10 @@ def remote(
10761247 dtype : tl .dtype = None ,
10771248 offset : int | tl .tensor | None = None ,
10781249 src = None ,
1079- dstoffset : int | tl .tensor | None = None ,
10801250 srcoffset : int | tl .tensor | None = None ,
10811251 nelems : int | tl .tensor | None = None ,
10821252 coopkind : GroupKind | str | None = None ,
1253+ netidx : int | tl .tensor = 0 ,
10831254 _semantic = None ,
10841255):
10851256 """
@@ -1090,40 +1261,45 @@ def remote(
10901261 should then use `tle.gpu.local_ptr(...)` to materialize remote pointers.
10911262 - tl.tensor shared-memory pointer (scalar or tensor): returns remote
10921263 pointer directly.
1264+ - DistributedRtContext with `space="node"`: returns a store-only remote
1265+ destination consumed by `tl.store`.
10931266
10941267 `shard_id` is the target block id inside the current thread block cluster.
10951268 For cluster/device pointer paths, when `scope` is provided, launch cluster
10961269 dimensions are inferred from that mesh and this mode requires `num_ctas=1`
10971270 (one program maps to one block).
10981271
1099- For `space="node"`, `tensor` is the destination `DistributedRtContext`.
1100- The optional `src` is another `DistributedRtContext`; it defaults to
1101- `tensor`, providing same registered-buffer transfer by default.
1102- `dtype`, `dstoffset`, `nelems`, and `coopkind` must be explicit, while
1103- `srcoffset` defaults to `dstoffset`. The cooperative kind accepts only
1104- `GroupKind.THREAD`, `GroupKind.WARP`, `GroupKind.BLOCK`, or their strings.
1272+ For `space="node"`, `tensor` is the destination `DistributedRtContext`
1273+ and `src` is the source registered-memory context. `src` defaults to
1274+ `tensor`. This function returns a store-only destination triggered with
1275+ `tl.store(remote_dst)`; node destinations do not accept a store value.
1276+ `dtype`, `offset`, `nelems`, and `coopkind` must be explicit, while
1277+ `srcoffset` defaults to `offset` and `netidx` defaults to zero. The
1278+ cooperative kind accepts only `GroupKind.THREAD`, `GroupKind.WARP`,
1279+ `GroupKind.BLOCK`, or their strings.
11051280 `shard_id` may be a scalar i32 world rank. With `scope=device_mesh`, a
11061281 compile-time tuple/list coordinate is also accepted and resolved through
11071282 the mesh's physical ids to a world rank. Node scope is used only for peer
1108- addressing and does not alter the CUDA cluster launch. `dstoffset `,
1283+ addressing and does not alter the CUDA cluster launch. `offset `,
11091284 `srcoffset`, and `nelems` are scalar element counts normalized to i64;
11101285 lowering multiplies all three by `dtype.itemsize` before calling FlagCX.
11111286 This first version emits a network put without flush, completion
11121287 notification, or a remote-visibility guarantee.
11131288
1114- `offset` is an optional scalar element offset relative to the target
1115- shard's memory base address. It is only supported for `space="device"`
1116- and is internally converted to a byte offset before being passed to
1117- `flagcxGetIntraPointerC`. It may be a Python `int` (compile-time constant)
1118- or a scalar `tl.tensor` (runtime value, shape == ()).
1289+ `offset` is a scalar element offset relative to the target shard's memory
1290+ base address. It is required for `space="node"` and optional for
1291+ `space="device"`. The device path converts it to a byte offset before
1292+ passing it to `flagcxGetIntraPointerC`. It may be a Python `int`
1293+ (compile-time constant) or a scalar `tl.tensor` (runtime value,
1294+ shape == ()).
11191295 """
11201296 space = tl ._unwrap_if_constexpr (space )
11211297 shard_id = _unwrap_remote_shard_id (shard_id )
11221298 scope = tl ._unwrap_if_constexpr (scope )
11231299 if space == "node" :
1124- return _node_put ( tensor , shard_id , src , scope , dtype , offset ,
1125- dstoffset , srcoffset , nelems , coopkind ,
1126- _semantic )
1300+ return _create_node_remote_destination (
1301+ tensor , shard_id , scope , dtype , offset , src , srcoffset , nelems ,
1302+ coopkind , netidx , _semantic )
11271303 if scope is not None and not isinstance (scope , device_mesh ):
11281304 raise TypeError (f"scope must be device_mesh or None, got { type (scope ).__name__ } " )
11291305 if scope is not None :
0 commit comments