Skip to content

Commit fe7082b

Browse files
mawad-amdclaude
andauthored
Fix Gluon tracing: runtime guard, 2D reduction, test cleanup
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent eec6de4 commit fe7082b

2 files changed

Lines changed: 75 additions & 15 deletions

File tree

iris/experimental/iris_gluon.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,27 @@ def record_event_start(
151151
Args:
152152
event_id: Event type ID (constexpr)
153153
target_rank: Target rank for the operation
154-
address: Memory address(es) - can be 1D or 2D block of pointers.
154+
address: Memory address(es) - 1D or 2D block of pointers.
155155
pid_m: Program ID in M dimension
156156
pid_n: Program ID in N dimension
157-
mask: Optional mask tensor indicating valid elements.
157+
mask: Optional mask tensor indicating valid elements (1D or 2D).
158158
"""
159159
if not self.enabled:
160160
return tl.cast(0, tl.int32)
161161

162+
# Guard against runtime-disabled tracing: when the kernel is compiled
163+
# with tracing=True but the host context has tracing disabled, the
164+
# counter pointers are null and max_events is 0. Skip all work.
165+
if self.max_events <= 0:
166+
return tl.cast(0, tl.int32)
167+
162168
event_idx = tl.atomic_add(self.counter, 1)
163169
op_index = tl.atomic_add(self.op_index_counter, 1)
164170

165171
# Calculate payload_size from mask and datatype
166172
if mask is not None:
167173
mask_i32 = tl.cast(mask, tl.int32)
168-
num_elements = gl.sum(mask_i32, axis=0)
174+
num_elements = gl.sum(mask_i32)
169175
elem_type = address.dtype.element_ty
170176
bitwidth = elem_type.primitive_bitwidth
171177
elem_size_bytes = bitwidth // 8
@@ -184,7 +190,7 @@ def record_event_start(
184190
tl.store(self.buf_cu_id + event_idx, device_utils.get_cu_id())
185191
tl.store(self.buf_timestamp + event_idx, device_utils.read_realtime())
186192
addr_i64 = tl.cast(address, tl.int64)
187-
tl.store(self.buf_address + event_idx, gl.min(addr_i64, axis=0))
193+
tl.store(self.buf_address + event_idx, gl.min(addr_i64))
188194
tl.store(self.buf_duration_cycles + event_idx, tl.cast(0, tl.int64))
189195
tl.store(self.buf_op_index + event_idx, op_index)
190196
tl.store(self.buf_payload_size + event_idx, tl.cast(payload_size, tl.int32))
@@ -263,9 +269,16 @@ def initialize(context_tensor, tracing: gl.constexpr = False):
263269
heap_bases = context_tensor + 2 # Offset pointer to start at heap bases
264270

265271
if tracing:
266-
# Extract tracing info: starts after heap_bases, then skip trace_enabled flag
272+
# Extract tracing info: starts after heap_bases, then skip trace_enabled flag.
267273
# Layout: [cur_rank, num_ranks, heap_base_0..N-1, trace_enabled, max_events,
268274
# trace_counter_ptr, op_index_counter_ptr, buf_event_id, ...(13 buffers)]
275+
#
276+
# When tracing is disabled at the host, the context tensor is padded with
277+
# zeros in the same positions (max_events=0, null pointers). On device,
278+
# the tracing helpers (e.g., record_event_start) first early-return when
279+
# max_events <= 0 and then guard all writes with a bounds check
280+
# (event_idx < max_events), so decoding potentially null pointers here is
281+
# safe as long as those invariants are preserved.
269282
trace_info_base = 2 + num_ranks + 1 # skip cur_rank, num_ranks, heap_bases, trace_enabled
270283
max_events = tl.cast(gl.load(context_tensor + trace_info_base + 0), tl.int32)
271284
trace_counter_ptr = gl.load(context_tensor + trace_info_base + 1)
@@ -1001,7 +1014,13 @@ def _build_device_context(self):
10011014
self.tracing.op_index_counter.data_ptr(),
10021015
] + trace_buffer_ptrs
10031016
else:
1004-
context_data += [0] # trace_enabled = 0 (false)
1017+
# trace_enabled = 0, then pad with zeros so a kernel compiled with
1018+
# tracing=True can safely decode the same layout without reading
1019+
# out of bounds. The zeros produce max_events=0 and null pointers,
1020+
# so the bounds check (event_idx < max_events) in record_event_start
1021+
# prevents any actual writes.
1022+
# Padding: 1 (trace_enabled) + 1 (max_events) + 2 (counters) + 13 (buffers) = 17
1023+
context_data += [0] * 17
10051024

10061025
self._device_context = torch.tensor(context_data, dtype=torch.int64, device=self.device)
10071026

tests/unittests/test_device_context_gluon.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def test_device_context_gluon_tracing_1d_address():
6767

6868
# Verify event data fields for the first recorded event
6969
bufs = shmem.tracing.trace_buffers
70-
assert bufs["event_id"][0].item() == 3 # TraceEvent().put == 3
70+
assert bufs["event_id"][0].item() == int(TraceEvent().put)
7171
assert bufs["cur_rank"][0].item() == source_rank
7272
assert bufs["target_rank"][0].item() == (source_rank + 1) % num_ranks
7373
assert bufs["timestamp"][0].item() > 0
@@ -104,26 +104,67 @@ def test_device_context_gluon_initialize():
104104
gc.collect()
105105

106106

107+
def test_device_context_gluon_tracing_compiled_but_disabled():
108+
"""Test kernel compiled with tracing=True against a context tensor with tracing disabled.
109+
110+
The context tensor is zero-padded so the kernel can safely decode the tracing
111+
layout. max_events=0 ensures record_event_start never writes to the null buffers.
112+
"""
113+
shmem = iris_gl.iris(1 << 20)
114+
# Do NOT enable tracing on host — context tensor has trace_enabled=0
115+
context_tensor = shmem.get_device_context()
116+
source_rank = shmem.get_rank()
117+
num_ranks = shmem.get_num_ranks()
118+
119+
BLOCK_SIZE = 64
120+
dummy_buffer = shmem.zeros((BLOCK_SIZE,), dtype=torch.int64)
121+
122+
shmem.barrier()
123+
124+
# Launch kernel compiled with tracing=True against non-tracing context
125+
device_context_tracing_1d_address_kernel[(1,)](
126+
iris_gl.IrisDeviceCtx,
127+
context_tensor,
128+
dummy_buffer,
129+
source_rank,
130+
num_ranks,
131+
BLOCK_SIZE,
132+
num_warps=1,
133+
)
134+
shmem.barrier()
135+
136+
# Verify the padded layout still reports tracing disabled and that the
137+
# dummy buffer remains untouched (no writes to null pointers).
138+
trace_enabled_idx = 2 + num_ranks
139+
assert context_tensor[trace_enabled_idx].item() == 0
140+
assert torch.all(dummy_buffer == 0)
141+
142+
shmem.barrier()
143+
del shmem
144+
import gc
145+
146+
gc.collect()
147+
148+
107149
def test_device_context_gluon_tracing_enable():
108150
"""Test that shmem.tracing.enable() rebuilds context tensor with tracing fields."""
109151
shmem = iris_gl.iris(1 << 20)
110152
num_ranks = shmem.get_num_ranks()
111153

112-
# Without tracing: [cur_rank, num_ranks, heap_base_0..N-1, trace_enabled=0]
154+
# Without tracing: tensor is padded to same size as tracing-enabled layout
155+
# (zeros for max_events, counter ptrs, buffer ptrs) so kernels compiled with
156+
# tracing=True can safely decode the tensor without reading out of bounds.
113157
ctx_no_trace = shmem.get_device_context()
114-
size_no_trace = ctx_no_trace.shape[0]
158+
trace_enabled_idx = 2 + num_ranks
159+
assert ctx_no_trace[trace_enabled_idx].item() == 0
115160

116161
# Enable tracing and rebuild
117162
shmem.tracing.enable(max_events=1000)
118163
ctx_with_trace = shmem.get_device_context()
119-
size_with_trace = ctx_with_trace.shape[0]
120164

121-
# With tracing the context tensor is larger:
122-
# [cur_rank, num_ranks, heap_base_0..N-1, trace_enabled=1, max_events,
123-
# trace_counter_ptr, op_index_counter_ptr, 13 buffer ptrs]
124-
assert size_with_trace > size_no_trace
165+
# Both tensors should be the same size (no-trace is zero-padded)
166+
assert ctx_with_trace.shape[0] == ctx_no_trace.shape[0]
125167
# trace_enabled flag should be 1
126-
trace_enabled_idx = 2 + num_ranks
127168
assert ctx_with_trace[trace_enabled_idx].item() == 1
128169

129170
shmem.barrier()

0 commit comments

Comments
 (0)