Skip to content

Commit be7bb9d

Browse files
committed
[TLE]Change the way tle.remote is used for communication between nodes
1 parent efc3293 commit be7bb9d

12 files changed

Lines changed: 481 additions & 213 deletions

File tree

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

Lines changed: 217 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
958958
def _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+
10221043
def _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
10711242
def 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:

python/triton/language/core.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
T = TypeVar('T')
4242

4343
TRITON_BUILTIN = "__triton_builtin__"
44+
_STORE_VALUE_UNSET = object()
4445

4546
PropagateNan = ir.PROPAGATE_NAN
4647

@@ -2177,6 +2178,11 @@ def load(pointer, mask=None, other=None, boundary_check=(), padding_option="", c
21772178
:param flagtree_hints: flagtree hints
21782179
:type flagtree_hints: str, optional
21792180
"""
2181+
custom_load = getattr(pointer, "__triton_load__", None)
2182+
if custom_load is not None:
2183+
return custom_load(mask, other, boundary_check, padding_option, cache_modifier, eviction_policy, volatile,
2184+
flagtree_hints, _semantic=_semantic)
2185+
21802186
# `mask` and `other` can be constexpr
21812187
mask = _unwrap_if_constexpr(mask)
21822188
other = _unwrap_if_constexpr(other)
@@ -2209,7 +2215,7 @@ def store_tensor_descriptor(desc: tensor_descriptor_base, offsets: Sequence[cons
22092215

22102216
@_tensor_member_fn
22112217
@builtin
2212-
def store(pointer, value, mask=None, boundary_check=(), cache_modifier="", eviction_policy="", _semantic=None):
2218+
def store(pointer, value=_STORE_VALUE_UNSET, mask=None, boundary_check=(), cache_modifier="", eviction_policy="", _semantic=None):
22132219
"""
22142220
Store a tensor of data into memory locations defined by `pointer`.
22152221
@@ -2233,6 +2239,9 @@ def store(pointer, value, mask=None, boundary_check=(), cache_modifier="", evict
22332239
22342240
`value` is implicitly broadcast to `pointer.shape` and typecast to `pointer.dtype.element_ty`.
22352241
2242+
Experimental store-only destinations may omit `value`; ordinary pointers
2243+
still require it.
2244+
22362245
:param pointer: The memory location where the elements of `value` are stored
22372246
:type pointer: `triton.PointerType`, or block of `dtype=triton.PointerType`
22382247
:param value: The tensor of elements to be stored
@@ -2248,13 +2257,23 @@ def store(pointer, value, mask=None, boundary_check=(), cache_modifier="", evict
22482257
:param eviction_policy: changes eviction policy in NVIDIA PTX
22492258
:type eviction_policy: str, optional, should be one of {"", "evict_first", "evict_last"}
22502259
"""
2260+
mask = _unwrap_if_constexpr(mask)
2261+
cache_modifier = _unwrap_if_constexpr(cache_modifier)
2262+
eviction_policy = _unwrap_if_constexpr(eviction_policy)
2263+
2264+
# Experimental pointer-like destinations can intercept stores before
2265+
# `value` is validated or converted to a tensor.
2266+
custom_store = getattr(pointer, "__triton_store__", None)
2267+
if custom_store is not None:
2268+
return custom_store(value, mask, boundary_check, cache_modifier, eviction_policy, _semantic=_semantic)
2269+
2270+
if value is _STORE_VALUE_UNSET:
2271+
raise TypeError("tl.store() missing required argument 'value' for an ordinary pointer")
2272+
22512273
# `value` can be constexpr
22522274
value = _semantic.to_tensor(value)
2253-
mask = _unwrap_if_constexpr(mask)
22542275
if mask is not None:
22552276
mask = _semantic.to_tensor(mask)
2256-
cache_modifier = _unwrap_if_constexpr(cache_modifier)
2257-
eviction_policy = _unwrap_if_constexpr(eviction_policy)
22582277
return _semantic.store(pointer, value, mask, boundary_check, cache_modifier, eviction_policy)
22592278

22602279

0 commit comments

Comments
 (0)