Skip to content

Commit efc3293

Browse files
committed
[TLE]Add a node parameter to tle.remote
1 parent 1b28af1 commit efc3293

7 files changed

Lines changed: 340 additions & 25 deletions

File tree

python/triton/experimental/tle/language/distributed.py

Lines changed: 156 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
769779
def _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-
915920
def _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
9531071
def 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

third_party/nvidia/lib/TritonNVIDIAGPUToLLVM/TritonGPUToLLVM.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ struct ConvertTritonGPUToLLVM
178178
typeConverter, patterns, benefit);
179179
mlir::triton::tle::populateLocalPointersOpToLLVMPatterns(
180180
typeConverter, targetInfo, patterns, benefit);
181+
mlir::triton::tle::populateNodePutOpToLLVMPatterns(typeConverter,
182+
patterns, benefit);
181183
mlir::triton::tle::populateExtractTileOpToLLVMPatterns(
182184
typeConverter, patterns, targetInfo, benefit);
183185
mlir::triton::tle::populateInsertTileOpToLLVMPatterns(

third_party/tle/dialect/include/Conversion/TleToLLVM/LocalPointersOpToLLVM.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ void populateLocalPointersOpToLLVMPatterns(
3232
mlir::LLVMTypeConverter &typeConverter, const TargetInfoBase &targetInfo,
3333
RewritePatternSet &patterns, PatternBenefit benefit);
3434

35+
void populateNodePutOpToLLVMPatterns(
36+
mlir::LLVMTypeConverter &typeConverter, RewritePatternSet &patterns,
37+
PatternBenefit benefit);
38+
3539
void populateRemotePointersOpToLLVMPatterns(
3640
mlir::LLVMTypeConverter &typeConverter, const TargetInfoBase &targetInfo,
3741
RewritePatternSet &patterns, PatternBenefit benefit);

third_party/tle/dialect/include/IR/TleOps.td

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,24 @@ def Tle_RemotePointersOp : Tle_Op<"remote_pointers", [Pure, AttrSizedOperandSegm
367367
let results = (outs Tle_LocalPointerResultType:$result);
368368
let hasVerifier = 1;
369369
}
370+
371+
def Tle_NodePutOp
372+
: Tle_Op<"node_put", [MemoryEffects<[MemRead, MemWrite]>]> {
373+
374+
let arguments = (ins
375+
I64:$dst_mem,
376+
I64:$src_mem,
377+
I64:$comm,
378+
I32:$peer,
379+
I64:$dst_offset,
380+
I64:$src_offset,
381+
I64:$nelems,
382+
I64Attr:$elem_bytes,
383+
I32Attr:$put_coop_kind
384+
);
385+
let hasVerifier = 1;
386+
}
387+
370388
def Tle_GetNumPesOp : Tle_Op<"get_num_pes"> {
371389
let summary = "Get nume pes";
372390
let arguments = (ins

0 commit comments

Comments
 (0)