1212from .config import Config
1313from .utils import extract_group_info
1414
15+ # Conditional import for Gluon
16+ try :
17+ from triton .experimental import gluon
18+ from triton .experimental .gluon import language as gl
19+ from iris .experimental .iris_gluon import IrisDeviceCtx
20+
21+ GLUON_AVAILABLE = True
22+ except ImportError :
23+ GLUON_AVAILABLE = False
24+
1525
1626@triton .jit ()
1727def persistent_all_gather (
@@ -279,6 +289,158 @@ def persistent_all_gather_partitioned(
279289 )
280290
281291
292+ # Gluon implementation: flat-2D tiling approach
293+ #
294+ # Uses a single 1D arange over BLOCK_SIZE_M * BLOCK_SIZE_N elements with
295+ # div/mod to compute 2D row/col indices. This gives one load + world_size
296+ # stores per tile (matching Triton's 2D load/store structure) while staying
297+ # within gluon's 1D BlockedLayout framework.
298+ #
299+ # Key optimizations:
300+ # - Flat-2D tiling: eliminates the inner BLOCK_SIZE_M row loop
301+ # - Hoisted pointer translation: local_base loaded once outside tile loop
302+ # - Traffic shaping: staggered write order avoids memory controller contention
303+ if GLUON_AVAILABLE :
304+
305+ @gluon .jit
306+ def persistent_all_gather_gluon (
307+ IrisDeviceCtx : gl .constexpr ,
308+ context_tensor ,
309+ input_ptr ,
310+ output_ptr ,
311+ M ,
312+ N ,
313+ stride_in_m ,
314+ stride_in_n ,
315+ stride_out_m ,
316+ stride_out_n ,
317+ group_rank : gl .constexpr ,
318+ iris_rank : gl .constexpr ,
319+ world_size : gl .constexpr ,
320+ rank_start : gl .constexpr ,
321+ rank_stride : gl .constexpr ,
322+ BLOCK_SIZE_M : gl .constexpr ,
323+ BLOCK_SIZE_N : gl .constexpr ,
324+ GROUP_SIZE_M : gl .constexpr ,
325+ COMM_SMS : gl .constexpr ,
326+ THREADS_PER_WARP : gl .constexpr ,
327+ WARPS_PER_CTA : gl .constexpr ,
328+ ):
329+ """
330+ Persistent all-gather kernel using Gluon with flat-2D tiling.
331+
332+ Uses a flat 1D index space of BLOCK_SIZE_M * BLOCK_SIZE_N elements,
333+ computing 2D row/col via integer div/mod. This produces one vectorized
334+ load and world_size vectorized stores per tile, matching Triton's 2D
335+ load/store instruction structure while staying within gluon's 1D
336+ BlockedLayout framework.
337+
338+ Memory layout (BlockedLayout):
339+ A 1D BlockedLayout distributes TOTAL_ELEMS = BLOCK_SIZE_M * BLOCK_SIZE_N
340+ elements across the thread hierarchy:
341+ ELEMS_PER_THREAD = TOTAL_ELEMS // (THREADS_PER_WARP * WARPS_PER_CTA)
342+
343+ Each thread handles ELEMS_PER_THREAD contiguous elements in the
344+ flattened row-major order. Row/col are recovered via:
345+ row = flat_idx // BLOCK_SIZE_N
346+ col = flat_idx % BLOCK_SIZE_N
347+
348+ Constraints:
349+ - BLOCK_SIZE_M * BLOCK_SIZE_N must be a multiple of
350+ (THREADS_PER_WARP * WARPS_PER_CTA).
351+ - Optimal tile: 2048-4096 total elements (8-16 per thread).
352+ Larger tiles cause register spilling and performance collapse.
353+ - Recommended: BLOCK_SIZE_M=8, BLOCK_SIZE_N=256 (2048 elems, 8/thread).
354+
355+ Args:
356+ IrisDeviceCtx: Gluon device context class for remote memory operations.
357+ context_tensor: Opaque tensor holding IrisDeviceCtx state.
358+ input_ptr: Pointer to local input tensor of shape (M, N).
359+ output_ptr: Pointer to output tensor of shape (world_size * M, N).
360+ M: Number of rows in the input tensor (per rank).
361+ N: Number of columns.
362+ stride_in_m, stride_in_n: Row and column strides for input tensor.
363+ stride_out_m, stride_out_n: Row and column strides for output tensor.
364+ group_rank: This rank's index within the ProcessGroup (0..world_size-1).
365+ iris_rank: This rank's global index in the iris context (for RMA addressing).
366+ world_size: Total number of ranks in the group.
367+ rank_start: First iris rank in the group (for RMA target computation).
368+ rank_stride: Stride between consecutive iris ranks in the group.
369+ BLOCK_SIZE_M: Number of rows per tile.
370+ BLOCK_SIZE_N: Number of columns per tile.
371+ GROUP_SIZE_M: Swizzle group size for M-dimension tiling.
372+ COMM_SMS: Number of CUs used for persistent scheduling.
373+ THREADS_PER_WARP: Threads per warp/wavefront (64 for AMD, 32 for NVIDIA).
374+ WARPS_PER_CTA: Number of warps per workgroup. Must match num_warps.
375+ """
376+ ctx = IrisDeviceCtx .initialize (context_tensor , tracing = False )
377+
378+ pid = gl .program_id (0 )
379+
380+ num_pid_m = gl .cdiv (M , BLOCK_SIZE_M )
381+ num_pid_n = gl .cdiv (N , BLOCK_SIZE_N )
382+ total_tiles = num_pid_m * num_pid_n
383+
384+ # Flat 1D layout covering BLOCK_SIZE_M * BLOCK_SIZE_N elements
385+ TOTAL_ELEMS : gl .constexpr = BLOCK_SIZE_M * BLOCK_SIZE_N
386+ ELEMS_PER_THREAD : gl .constexpr = TOTAL_ELEMS // (THREADS_PER_WARP * WARPS_PER_CTA )
387+ flat_layout : gl .constexpr = gl .BlockedLayout ([ELEMS_PER_THREAD ], [THREADS_PER_WARP ], [WARPS_PER_CTA ], [0 ])
388+
389+ # Hoist local heap base outside the tile loop: eliminates redundant
390+ # gl.load(heap_bases) calls in the inner store loop.
391+ local_base = gl .load (ctx .heap_bases + iris_rank )
392+
393+ for tile_id in range (pid , total_tiles , COMM_SMS ):
394+ # Swizzled tile index computation for better L2 locality
395+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
396+ group_id = tile_id // num_pid_in_group
397+ first_pid_m = group_id * GROUP_SIZE_M
398+ group_size_m = min (num_pid_m - first_pid_m , GROUP_SIZE_M )
399+ pid_m = first_pid_m + ((tile_id % num_pid_in_group ) % group_size_m )
400+ pid_n = (tile_id % num_pid_in_group ) // group_size_m
401+
402+ # Flat index -> 2D row/col within tile
403+ flat_idx = gl .arange (0 , TOTAL_ELEMS , layout = flat_layout )
404+ row_local = flat_idx // BLOCK_SIZE_N
405+ col_local = flat_idx % BLOCK_SIZE_N
406+
407+ # Global row/col
408+ row = pid_m * BLOCK_SIZE_M + row_local
409+ col = pid_n * BLOCK_SIZE_N + col_local
410+
411+ mask = (row < M ) & (col < N )
412+
413+ # Single flat load of the entire tile
414+ input_offsets = row * stride_in_m + col * stride_in_n
415+ input_addr = input_ptr + input_offsets
416+ data = gl .load (input_addr , mask = mask , other = 0.0 )
417+
418+ # Output: this rank's data goes to output[group_rank * M + row, col]
419+ output_row = group_rank * M + row
420+ output_offsets = output_row * stride_out_m + col * stride_out_n
421+
422+ # Traffic-shaped stores to all ranks: stagger write order per rank
423+ # so each rank writes to a different target at any given moment,
424+ # avoiding memory controller contention on the receiver side.
425+ for rank_idx in range (world_size ):
426+ dest_idx = (group_rank + rank_idx ) % world_size
427+ target_iris_rank = rank_start + dest_idx * rank_stride
428+ output_ptrs = output_ptr + output_offsets
429+
430+ if dest_idx == group_rank :
431+ gl .store (output_ptrs , data , mask = mask , cache_modifier = ".wt" )
432+ else :
433+ # Hoisted translation: compute ptr_delta from pre-loaded
434+ # local_base rather than calling ctx.store() which would
435+ # do 2x gl.load(heap_bases) per call.
436+ target_base = gl .load (ctx .heap_bases + target_iris_rank )
437+ ptr_delta = target_base - local_base
438+ output_ptrs_int = tl .cast (output_ptrs , gl .uint64 )
439+ remote_ptrs_int = output_ptrs_int + ptr_delta
440+ remote_ptrs = tl .cast (remote_ptrs_int , output_ptrs .dtype )
441+ gl .store (remote_ptrs , data , mask = mask )
442+
443+
282444def all_gather (
283445 output_tensor ,
284446 input_tensor ,
@@ -314,26 +476,11 @@ def all_gather(
314476 if config is None :
315477 config = Config (block_size_m = 32 , block_size_n = 64 )
316478
317- # Check for unsupported options
318- if config .use_gluon :
319- raise ValueError (
320- "all_gather does not support use_gluon=True. "
321- "Gluon implementation is not available for all_gather. "
322- "Use default config (use_gluon=False)."
323- )
324-
325479 # Extract group information
326480 # rank_in_group: position within the ProcessGroup (0, 1, 2, ...) - passed as group_rank to kernel
327481 # rank_global: global rank in iris context - passed as iris_rank to kernel for RMA operations
328482 rank_in_group , rank_global , world_size , rank_start , rank_stride = extract_group_info (group , shmem )
329483
330- # Validate COMM_SMS divisibility for partitioned variant
331- if config .all_gather_variant == "partitioned" and config .comm_sms % world_size != 0 :
332- raise ValueError (
333- f"For all_gather_variant='partitioned', COMM_SMS ({ config .comm_sms } ) must be divisible by world_size ({ world_size } ). "
334- f"Please adjust config.comm_sms to be a multiple of { world_size } ."
335- )
336-
337484 M , N = input_tensor .shape [:2 ]
338485 expected_output_shape = (world_size * M , N )
339486
@@ -346,41 +493,121 @@ def all_gather(
346493 stride_in_m , stride_in_n = input_tensor .stride (0 ), input_tensor .stride (1 )
347494 stride_out_m , stride_out_n = output_tensor .stride (0 ), output_tensor .stride (1 )
348495
349- heap_bases = shmem .get_heap_bases ()
496+ # Choose between Triton and Gluon implementation
497+ if config .use_gluon and GLUON_AVAILABLE :
498+ # Check if shmem is Iris Gluon (has get_device_context method)
499+ if not hasattr (shmem , "get_device_context" ):
500+ raise ValueError ("use_gluon=True requires Iris Gluon context. Use iris.experimental.iris_gluon.iris()" )
350501
351- # Dispatch to the appropriate kernel based on variant
352- if config .all_gather_variant == "persistent" :
353- kernel_fn = persistent_all_gather
354- elif config .all_gather_variant == "partitioned" :
355- kernel_fn = persistent_all_gather_partitioned
502+ # Gluon only supports the persistent variant
503+ if config .all_gather_variant != "persistent" :
504+ raise ValueError (
505+ f"Gluon all_gather only supports all_gather_variant='persistent', got '{ config .all_gather_variant } '."
506+ )
507+
508+ # Apply optimal defaults for gluon flat-2D kernel when user hasn't
509+ # overridden block sizes from the Config defaults (32x64).
510+ block_size_m = config .block_size_m
511+ block_size_n = config .block_size_n
512+ if block_size_m == 32 and block_size_n == 64 :
513+ # User didn't override — use optimal flat-2D tile: 8x256
514+ block_size_m = 8
515+ block_size_n = 256
516+
517+ # Validate flat-2D layout constraints.
518+ # TOTAL_ELEMS = BLOCK_SIZE_M * BLOCK_SIZE_N must be a multiple of
519+ # THREADS_PER_WARP * WARPS_PER_CTA so each thread gets a whole
520+ # number of elements.
521+ total_elems = block_size_m * block_size_n
522+ threads_per_cta = config .threads_per_warp * config .num_warps
523+ if total_elems < threads_per_cta :
524+ raise ValueError (
525+ f"Gluon all-gather requires block_size_m * block_size_n >= "
526+ f"threads_per_warp * num_warps ({ threads_per_cta } ), "
527+ f"got { block_size_m } * { block_size_n } = { total_elems } ."
528+ )
529+ if total_elems % threads_per_cta != 0 :
530+ raise ValueError (
531+ f"Gluon all-gather requires block_size_m * block_size_n to be a "
532+ f"multiple of threads_per_warp * num_warps ({ threads_per_cta } ), "
533+ f"got { block_size_m } * { block_size_n } = { total_elems } . "
534+ f"Recommended: block_size_m=8, block_size_n=256."
535+ )
536+
537+ context_tensor = shmem .get_device_context ()
538+
539+ persistent_all_gather_gluon [(config .comm_sms ,)](
540+ IrisDeviceCtx ,
541+ context_tensor ,
542+ input_tensor ,
543+ output_tensor ,
544+ M ,
545+ N ,
546+ stride_in_m ,
547+ stride_in_n ,
548+ stride_out_m ,
549+ stride_out_n ,
550+ rank_in_group ,
551+ rank_global ,
552+ world_size ,
553+ rank_start ,
554+ rank_stride ,
555+ block_size_m ,
556+ block_size_n ,
557+ config .swizzle_size ,
558+ config .comm_sms ,
559+ config .threads_per_warp ,
560+ config .num_warps ,
561+ num_stages = config .num_stages ,
562+ num_warps = config .num_warps ,
563+ waves_per_eu = config .waves_per_eu ,
564+ )
356565 else :
357- raise ValueError (f"Unknown all_gather_variant: { config .all_gather_variant } " )
566+ if config .use_gluon and not GLUON_AVAILABLE :
567+ raise ValueError ("Gluon is not available. Install Triton with Gluon support or set use_gluon=False" )
568+
569+ # Validate COMM_SMS divisibility for partitioned variant
570+ if config .all_gather_variant == "partitioned" and config .comm_sms % world_size != 0 :
571+ raise ValueError (
572+ f"For all_gather_variant='partitioned', COMM_SMS ({ config .comm_sms } ) must be divisible by world_size ({ world_size } ). "
573+ f"Please adjust config.comm_sms to be a multiple of { world_size } ."
574+ )
358575
359- kernel_fn [(config .comm_sms ,)](
360- input_tensor ,
361- output_tensor ,
362- M ,
363- N ,
364- stride_in_m ,
365- stride_in_n ,
366- stride_out_m ,
367- stride_out_n ,
368- heap_bases ,
369- rank_in_group ,
370- rank_global ,
371- world_size ,
372- rank_start ,
373- rank_stride ,
374- config .block_size_m ,
375- config .block_size_n ,
376- config .swizzle_size ,
377- config .comm_sms ,
378- config .num_xcds ,
379- config .chunk_size ,
380- num_stages = config .num_stages ,
381- num_warps = config .num_warps ,
382- waves_per_eu = config .waves_per_eu ,
383- )
576+ heap_bases = shmem .get_heap_bases ()
577+
578+ # Dispatch to the appropriate kernel based on variant
579+ if config .all_gather_variant == "persistent" :
580+ kernel_fn = persistent_all_gather
581+ elif config .all_gather_variant == "partitioned" :
582+ kernel_fn = persistent_all_gather_partitioned
583+ else :
584+ raise ValueError (f"Unknown all_gather_variant: { config .all_gather_variant } " )
585+
586+ kernel_fn [(config .comm_sms ,)](
587+ input_tensor ,
588+ output_tensor ,
589+ M ,
590+ N ,
591+ stride_in_m ,
592+ stride_in_n ,
593+ stride_out_m ,
594+ stride_out_n ,
595+ heap_bases ,
596+ rank_in_group ,
597+ rank_global ,
598+ world_size ,
599+ rank_start ,
600+ rank_stride ,
601+ config .block_size_m ,
602+ config .block_size_n ,
603+ config .swizzle_size ,
604+ config .comm_sms ,
605+ config .num_xcds ,
606+ config .chunk_size ,
607+ num_stages = config .num_stages ,
608+ num_warps = config .num_warps ,
609+ waves_per_eu = config .waves_per_eu ,
610+ )
384611
385612 if not async_op :
386613 shmem .barrier ()
0 commit comments