@@ -766,11 +766,21 @@ def distributed_barrier(mesh: device_mesh | None = None, device_dptr=None, space
766766 return None
767767
768768
769+ def _unwrap_remote_shard_id (shard_id : Any ):
770+ shard_id = tl ._unwrap_if_constexpr (shard_id )
771+ # Tuple literals in JIT functions are represented as tl.tuple even when
772+ # every coordinate is compile-time constant. Convert them back to a Python
773+ # tuple so the shared compile-time coordinate path can process them.
774+ if isinstance (shard_id , tl .tuple ):
775+ shard_id = tuple (shard_id )
776+ return shard_id
777+
778+
769779def _normalize_remote_shard_id (
770780 shard_id : Any ,
771781 scope : device_mesh | None ,
772782) -> int :
773- shard_id = tl . _unwrap_if_constexpr (shard_id )
783+ shard_id = _unwrap_remote_shard_id (shard_id )
774784 scope = tl ._unwrap_if_constexpr (scope )
775785
776786 if isinstance (shard_id , int ):
@@ -907,11 +917,6 @@ def _check_device_remote_pointer(tensor: tl.tensor, shard_id: int | tuple[int, .
907917 ...
908918
909919
910- def _check_node_remote_pointer (tensor : tl .tensor , shard_id : int | tuple [int , ...] | list [int ],
911- scope : device_mesh | None ) -> None :
912- ...
913-
914-
915920def _remote_pointer (
916921 tensor : tl .tensor ,
917922 shard_id ,
@@ -922,14 +927,13 @@ def _remote_pointer(
922927 _semantic = None ,
923928) -> tl .tensor :
924929
925- if not isinstance (tensor , tl .tensor ) and space not in ( "device" , "node" ) :
930+ if not isinstance (tensor , tl .tensor ) and space != "device" :
926931 raise TypeError (f"tensor must be tl.tensor, got { type (tensor ).__name__ } " )
927932
928933 space = tl ._unwrap_if_constexpr (space )
929934 res = {
930935 "cluster" : _check_cluster_remote_pointer ,
931936 "device" : _check_device_remote_pointer ,
932- "node" : _check_node_remote_pointer ,
933937 }[space ](tensor , shard_id , scope )
934938 if isinstance (res , tl .tensor ):
935939 return res
@@ -948,6 +952,120 @@ def _remote_pointer(
948952
949953 return _create_remote_pointers_tensor (tensor , shard_id_tensor , _semantic , dtype = dtype , space = space , offset = offset )
950954
955+ # dstoffset / srcoffset / nelems -> scalar i64 tl.tensor
956+ # dstoffset and srcoffset must be >= 0.
957+ # nelems must be > 0.
958+ def _normalize_node_i64 (value , label : str , * , must_be_positive : bool , _semantic ) -> tl .tensor :
959+ value = tl ._unwrap_if_constexpr (value )
960+ if isinstance (value , int ):
961+ if must_be_positive and value <= 0 :
962+ raise ValueError (f"node space { label } must be > 0, got { value } " )
963+ if not must_be_positive and value < 0 :
964+ raise ValueError (f"node space { label } must be >= 0, got { value } " )
965+
966+ value_tensor = value if isinstance (value , tl .tensor ) else _semantic .to_tensor (value )
967+ if not value_tensor .dtype .is_int ():
968+ raise TypeError (f"node space { label } must be an integer scalar, got { value_tensor .dtype } " )
969+ if value_tensor .shape != ():
970+ raise ValueError (f"node space { label } must be scalar, got shape { value_tensor .shape } " )
971+ if value_tensor .dtype != tl .int64 :
972+ value_tensor = tl .cast (value_tensor , tl .int64 , _semantic = _semantic )
973+ return value_tensor
974+
975+
976+ def _normalize_node_elem_bytes (dtype ) -> int :
977+ dtype = tl ._unwrap_if_constexpr (dtype )
978+ if not isinstance (dtype , tl .dtype ):
979+ raise TypeError (f"node space dtype must be a scalar Triton dtype, got { type (dtype ).__name__ } " )
980+ elem_bytes = dtype .itemsize
981+ if elem_bytes <= 0 :
982+ raise ValueError (f"node space dtype must be byte-addressable, got { dtype } " )
983+ return elem_bytes
984+
985+
986+ def _normalize_node_peer (shard_id , scope , _semantic ) -> tl .tensor :
987+ shard_id = _unwrap_remote_shard_id (shard_id )
988+ scope = tl ._unwrap_if_constexpr (scope )
989+ if scope is not None and not isinstance (scope , device_mesh ):
990+ raise TypeError (f"node space scope must be device_mesh or None, got { type (scope ).__name__ } " )
991+
992+ if isinstance (shard_id , (int , tuple , list )):
993+ is_coordinate = isinstance (shard_id , (tuple , list ))
994+ peer = _normalize_compile_time_remote_shard_id (shard_id , scope )
995+ if is_coordinate :
996+ # Coordinates are relative to the selected mesh. Resolve through
997+ # physical_ids so coordinates on a sliced submesh still produce
998+ # the corresponding world rank rather than a submesh-local rank.
999+ peer = scope .physical_ids [peer ]
1000+ if peer > 0x7FFFFFFF :
1001+ raise ValueError (f"node space world rank { peer } exceeds int32 range" )
1002+ shard_id = _semantic .to_tensor (peer )
1003+ elif not isinstance (shard_id , tl .tensor ):
1004+ shard_id = _semantic .to_tensor (shard_id )
1005+ return _normalize_runtime_remote_shard_id_tensor (shard_id )
1006+
1007+
1008+ def _normalize_put_coop_kind (coopkind ) -> int :
1009+ coopkind = tl ._unwrap_if_constexpr (coopkind )
1010+ if isinstance (coopkind , GroupKind ):
1011+ coopkind = coopkind .value
1012+ if not isinstance (coopkind , str ):
1013+ raise TypeError (
1014+ "node space coopkind must be GroupKind.THREAD/WARP/BLOCK or the corresponding string" )
1015+ mapping = {"thread" : 0 , "warp" : 1 , "block" : 2 }
1016+ normalized = coopkind .lower ()
1017+ if normalized not in mapping :
1018+ raise ValueError ("node space coopkind must be THREAD, WARP, or BLOCK" )
1019+ return mapping [normalized ]
1020+
1021+
1022+ def _parse_node_context (builder , value , label : str , index : int ):
1023+ from triton .runtime import DistributedRtContext
1024+ value = tl ._unwrap_if_constexpr (value )
1025+ if not isinstance (value , DistributedRtContext ):
1026+ raise TypeError (f"node space { label } must be DistributedRtContext, got { type (value ).__name__ } " )
1027+ return _parse_src_arg (builder , value , index )
1028+
1029+
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' )
1034+ if nelems is None :
1035+ raise TypeError ('tle.remote(..., space="node") requires nelems' )
1036+ if coopkind is None :
1037+ raise TypeError ('tle.remote(..., space="node") requires coopkind' )
1038+ if dtype is None :
1039+ 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' )
1042+
1043+ 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" )
1046+
1047+ peer = _normalize_node_peer (shard_id , scope , _semantic )
1048+ elem_bytes = _normalize_node_elem_bytes (dtype )
1049+
1050+ dstoffset = _normalize_node_i64 (dstoffset , "dstoffset" , must_be_positive = False , _semantic = _semantic )
1051+ if srcoffset is None :
1052+ srcoffset = dstoffset
1053+ 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 )
1056+ coop_kind = _normalize_put_coop_kind (coopkind )
1057+
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+
9511069
9521070@tl .builtin
9531071def remote (
@@ -957,6 +1075,11 @@ def remote(
9571075 space : str = "cluster" ,
9581076 dtype : tl .dtype = None ,
9591077 offset : int | tl .tensor | None = None ,
1078+ src = None ,
1079+ dstoffset : int | tl .tensor | None = None ,
1080+ srcoffset : int | tl .tensor | None = None ,
1081+ nelems : int | tl .tensor | None = None ,
1082+ coopkind : GroupKind | str | None = None ,
9601083 _semantic = None ,
9611084):
9621085 """
@@ -969,25 +1092,46 @@ def remote(
9691092 pointer directly.
9701093
9711094 `shard_id` is the target block id inside the current thread block cluster.
972- When `scope` is provided, launch cluster dimensions are inferred from that
973- mesh and this mode requires `num_ctas=1` (one program maps to one block).
1095+ For cluster/device pointer paths, when `scope` is provided, launch cluster
1096+ dimensions are inferred from that mesh and this mode requires `num_ctas=1`
1097+ (one program maps to one block).
1098+
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.
1105+ `shard_id` may be a scalar i32 world rank. With `scope=device_mesh`, a
1106+ compile-time tuple/list coordinate is also accepted and resolved through
1107+ 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`,
1109+ `srcoffset`, and `nelems` are scalar element counts normalized to i64;
1110+ lowering multiplies all three by `dtype.itemsize` before calling FlagCX.
1111+ This first version emits a network put without flush, completion
1112+ notification, or a remote-visibility guarantee.
9741113
9751114 `offset` is an optional scalar element offset relative to the target
9761115 shard's memory base address. It is only supported for `space="device"`
9771116 and is internally converted to a byte offset before being passed to
9781117 `flagcxGetIntraPointerC`. It may be a Python `int` (compile-time constant)
9791118 or a scalar `tl.tensor` (runtime value, shape == ()).
9801119 """
981- shard_id = tl ._unwrap_if_constexpr (shard_id )
1120+ space = tl ._unwrap_if_constexpr (space )
1121+ shard_id = _unwrap_remote_shard_id (shard_id )
9821122 scope = tl ._unwrap_if_constexpr (scope )
1123+ if space == "node" :
1124+ return _node_put (tensor , shard_id , src , scope , dtype , offset ,
1125+ dstoffset , srcoffset , nelems , coopkind ,
1126+ _semantic )
9831127 if scope is not None and not isinstance (scope , device_mesh ):
9841128 raise TypeError (f"scope must be device_mesh or None, got { type (scope ).__name__ } " )
9851129 if scope is not None :
9861130 _apply_mesh_cluster_launch (scope , _semantic )
9871131
9881132 # Direct pointer path: support local_ptr scalar/tensor values and return
9891133 # remote pointer with preserved shape.
990- if isinstance (tensor , tl .tensor ) or ( space in ( "device" , "node" )) :
1134+ if isinstance (tensor , tl .tensor ) or space == "device" :
9911135 return _remote_pointer (tensor , shard_id , scope = scope , space = space , _semantic = _semantic , dtype = dtype ,
9921136 offset = offset )
9931137
0 commit comments