@@ -54,6 +54,7 @@ class OperandRef:
5454 jnp .dtype ,
5555 jnp .dtype ,
5656 int ,
57+ bool ,
5758 ],
5859 gmm_v2 .TileSizes ,
5960]
@@ -89,6 +90,7 @@ def calculate_tgmm_tiling(
8990 out_dtype : jnp .dtype ,
9091 acc_dtype : jnp .dtype ,
9192 target_zero_ref_bytes : int ,
93+ has_partial_sum : bool = False ,
9294) -> gmm_v2 .TileSizes :
9395 """Calculate optimal tile sizes for TGMM kernel."""
9496 # In tgmm, we calculate lhs.T @ dout which doesn't require quantization.
@@ -121,8 +123,10 @@ def within_vmem_limit(tile_m, tile_k, tile_n):
121123 # XLU's transpose. in order to reduce redundant XLU computation, instead
122124 # of performing XLU's transpose every time lhs is pushed into XLU, it
123125 # caches the transposed value into VMEM. this increases VMEM requirement.
126+ ps_bytes = tile_k * tile_n * num_buffers * out_bytes if has_partial_sum else 0
124127 budget = (
125128 tile_k * tile_n * (acc_bytes + num_buffers * out_bytes )
129+ + ps_bytes
126130 + (num_buffers + 1 ) * (tile_m * tile_k * lhs_bytes )
127131 + num_buffers * (tile_m * tile_n * rhs_bytes )
128132 # Reserve VMEM for zero_ref. Use the upper bound target_zero_ref_bytes
@@ -179,6 +183,7 @@ def make_tgmm_configs(
179183 lhs : jax .Array , # [m, k]
180184 rhs : jax .Array , # [m, n]
181185 rhs_scale : jax .Array , # [1, 1, n] (per-N scale)
186+ partial_sum : jax .Array | None ,
182187 group_sizes : jax .Array ,
183188 num_actual_groups : int ,
184189 * ,
@@ -256,14 +261,15 @@ def make_tgmm_configs(
256261 out_dtype ,
257262 acc_dtype ,
258263 target_zero_ref_bytes ,
264+ partial_sum is not None ,
259265 )
260266
261267 return gmm_v2 .GmmConfigs (
262268 dims = dims ,
263269 tiles = tiles ,
264270 lhs_cfgs = lhs_cfgs ,
265271 rhs_cfgs = rhs_cfgs ,
266- has_partial_sum = False , # This should always be False until partial sum support is added in bwd pass.
272+ has_partial_sum = ( partial_sum is not None ),
267273 out_dtype = jnp .dtype (out_dtype ),
268274 acc_dtype = jnp .dtype (acc_dtype ),
269275 # GMM's 'zero_init' zeros unvisited m-rows via DMA, which doesn't apply to
@@ -280,6 +286,7 @@ def tgmm_inner_kernel(
280286 tiled_rhs_ref : OperandRef ,
281287 # .value: [tile_m // size_lhs_sublane, size_lhs_sublane, tile_n]
282288 # .scale: [1, 1, tile_n] or None
289+ tiled_ps_ref : jax .Array | None ,
283290 tiled_out_ref : jax .Array ,
284291 acc_ref : jax .Array ,
285292 metadata_ref : gmm_v2 .MetadataRef ,
@@ -342,6 +349,8 @@ def _matmul(is_new_group: bool, is_group_changing: bool):
342349 if cfgs .rhs_cfgs .has_scale :
343350 scale_slice = tiled_rhs_scale_ref [0 ] # pyrefly: ignore[unsupported-operation]
344351 acc *= scale_slice
352+ if cfgs .has_partial_sum :
353+ acc += tiled_ps_ref [...].astype (acc .dtype )
345354 tiled_out_ref [...] = acc .astype (tiled_out_ref .dtype )
346355 else :
347356 acc_ref [...] = acc
@@ -430,7 +439,7 @@ def out_index_map(self, n_id: jax.Array, k_id: jax.Array, gm_id: jax.Array):
430439
431440def generate_tgmm_block_specs (
432441 metadata_ref : gmm_v2 .MetadataRef , cfgs : gmm_v2 .GmmConfigs
433- ) -> Tuple [Tuple [pl .BlockSpec , OperandRef ], pl .BlockSpec ]:
442+ ) -> Tuple [Tuple [pl .BlockSpec , OperandRef , pl . BlockSpec | None ], pl .BlockSpec ]:
434443 """Generates block specs for the given lhs, rhs, and out refs."""
435444 index_map = TgmmIndexMaps (metadata_ref , cfgs )
436445 # NB: in tgmm, LHS is reshaped from (M, K) to (-1, size_lhs_sublane, K) so
@@ -457,8 +466,14 @@ def generate_tgmm_block_specs(
457466 (None , cfgs .tiles .tile_k , cfgs .tiles .tile_n ),
458467 index_map .out_index_map ,
459468 )
460-
461- return (lhs_block_spec , rhs_spec ), out_block_spec
469+ ps_block_spec = None
470+ if cfgs .has_partial_sum :
471+ ps_block_spec = pl .BlockSpec (
472+ (None , cfgs .tiles .tile_k , cfgs .tiles .tile_n ),
473+ index_map .out_index_map ,
474+ )
475+ in_specs = (lhs_block_spec , rhs_spec , ps_block_spec )
476+ return in_specs , out_block_spec
462477
463478
464479def zero_out_start (
@@ -526,6 +541,7 @@ def tgmm_kernel_main(
526541 group_offset_ref , # int32[1]
527542 lhs_ref , # [m, k]
528543 rhs_ref , # OperandRef: .value [m, n], .scale [1, 1, n] or None
544+ partial_sum_ref , # [num_actual_groups, k, n] or None
529545 out_ref , # [num_actual_groups, k, n]
530546 # Scratch memory
531547 acc_ref : jax .Array , # [tile_k, tile_n]
@@ -578,9 +594,12 @@ def tgmm_kernel_main(
578594 rhs_value = rhs_ref .value
579595 rhs_in = rhs_value .reshape (- 1 , cfgs .dims .size_lhs_sublane , rhs_value .shape [- 1 ])
580596 rhs_operand = OperandRef (value = rhs_in , scale = rhs_ref .scale )
597+ ps_in = None
598+ if cfgs .has_partial_sum :
599+ ps_in = partial_sum_ref
581600 scratches = [acc_ref , metadata_ref ]
582601
583- pipeline_fn (lhs_in , rhs_operand , out_ref , scratches = scratches )
602+ pipeline_fn (lhs_in , rhs_operand , ps_in , out_ref , scratches = scratches )
584603 zero_out_end (
585604 num_groups_to_zero ,
586605 out_ref ,
@@ -632,6 +651,7 @@ def tgmm_v2(
632651 group_sizes : jax .Array ,
633652 num_actual_groups : int ,
634653 rhs_scale : jax .Array | None = None , # [1, 1, size_n] (per-N scale)
654+ partial_sum : jax .Array | None = None ,
635655 group_offset : jax .Array | None = None ,
636656 * ,
637657 tile_info : gmm_v2 .TileSizes | TileTgmmFn = calculate_tgmm_tiling ,
@@ -683,6 +703,7 @@ def tgmm_v2(
683703 lhs ,
684704 rhs ,
685705 rhs_scale , # pyrefly: ignore[bad-argument-type]
706+ partial_sum ,
686707 group_sizes ,
687708 num_actual_groups ,
688709 tile_info = tile_info ,
@@ -731,14 +752,18 @@ def tgmm_v2(
731752 rhs_scale = jnp .pad (rhs_scale , ((0 , 0 ), (0 , 0 ), (0 , pad_n )))
732753 rhs = OperandRef (value = rhs , scale = rhs_scale ) # pyrefly: ignore[bad-assignment]
733754 hbm_spec = pl .BlockSpec (memory_space = pltpu .HBM )
755+ partial_sum_spec = None
756+ if partial_sum is not None :
757+ partial_sum_spec = hbm_spec
734758 in_specs = [
735759 hbm_spec , # lhs
736760 # the tree.map build a
737761 # OperandRef(value=hbm_spec, scale=None if scale is None else hbm_spec.
738762 jax .tree .map (lambda _ : hbm_spec , rhs ), # rhs
763+ partial_sum_spec ,
739764 ]
740765
741- return pl .pallas_call (
766+ raw_out = pl .pallas_call (
742767 functools .partial (tgmm_kernel_main , cfgs = cfgs ),
743768 out_shape = out_init ,
744769 grid_spec = pltpu .PrefetchScalarGridSpec (
@@ -756,4 +781,10 @@ def tgmm_v2(
756781 # the metadata here is for profiling, debugging, and cost modeling.
757782 # It does not affect the kernel's computation.
758783 metadata = gmm_v2 .get_metadata (cfgs ),
759- )(group_sizes , group_offset , lhs , rhs )[:, : dims .size_k , : dims .size_n ]
784+ )(group_sizes , group_offset , lhs , rhs , partial_sum )[:, : dims .size_k , : dims .size_n ]
785+
786+ if partial_sum is not None :
787+ local_group_sizes = lax .dynamic_slice (group_sizes , (group_offset [0 ],), (num_actual_groups ,))
788+ empty_mask = (local_group_sizes == 0 ).reshape (num_actual_groups , 1 , 1 )
789+ return jnp .where (empty_mask , partial_sum , raw_out )
790+ return raw_out
0 commit comments