.. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at .. http://www.apache.org/licenses/LICENSE-2.0 .. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. reduction → shared ================== The ``shared`` variant lowers a reduction (``sum`` / ``max`` / ``min``) when **both source and destination are shared** memory. At CTA / warpgroup / warp scope it partitions the threads into groups — one group per output position — has each thread gather a chunk of the reduction axis, then folds the group with an adaptive ``__shfl_xor`` tree. Source: ``python/tvm/backend/cuda/operator/tile_primitive/reduction/shared.py``. What it accepts --------------- .. code-block:: python @register_dispatch(op_name, "cuda", variant="shared", priority=10, when=[ predicate("storage_scope", _match_reduction_storage_scope, expected_scope=["shared*"]), predicate("shared_valid", validate_reduction_shared), ]) .. list-table:: :header-rows: 1 :widths: 22 78 * - Property - Requirement * - target / priority - ``cuda``; priority ``10`` * - operand scope - src **and** dst in ``shared*``, equal dtype * - exec scope - ``cta`` / ``warpgroup`` / ``warp`` (shuffle tree) or ``thread`` (sequential) * - thread binding - ``threadIdx.x`` present and **1-D** (no ``threadIdx.y`` / ``z``) * - shape - ``dst`` size equals the source's spatial extent (product of the non-reduced dims) Demonstration program ---------------------- A 32-thread CTA reduces each row of a ``4×8`` ``float32`` shared tile (reduce axis ``-1``) to a ``4``-vector (from ``test_reduction.py``): .. code-block:: python @T.prim_func def test_reduction(A_ptr: T.handle, B_ptr: T.handle): A = T.match_buffer(A_ptr, (4, 8), "float32", layout=TileLayout(S[(4, 8)])) B = T.match_buffer(B_ptr, (4,), "float32", layout=TileLayout(S[(4,)])) T.device_entry(); T.cta_id([1]); T.thread_id([32]) A_smem = T.alloc_buffer((4, 8), "float32", scope="shared", layout=TileLayout(S[(4, 8)])) B_smem = T.alloc_buffer((4,), "float32", scope="shared", layout=TileLayout(S[(4,)])) Tx.cta.copy(A_smem, A); T.cuda.cta_sync() Tx.cta.sum(B_smem, A_smem, axes=(-1,), accum=False) # reduction shared dispatch T.cuda.cta_sync() Tx.cta.copy(B, B_smem) Algorithm --------- **1. Choose the group size.** ``group_size = min(next_power_of_2(reduction_len), 32, thread_cnt)`` — here ``reduction_len = 8`` ⇒ ``group_size = 8``. Each group of 8 lanes cooperatively reduces one row; the CTA processes the 4 rows in parallel. **2. Gather + shuffle tree.** Each lane loads its slice of the reduction axis into a register, then ``log2(group_size)`` ``shfl_xor`` steps (masks ``1, 2, 4``) fold the group; lane 0 of each group writes the result, followed by a barrier: .. code-block:: python mask = T.tvm_warp_activemask() for i in range(n_shuffles): # n_shuffles = log2(group_size) thread_data[0] = op(thread_data[0], T.tvm_warp_shuffle_xor(mask, thread_data[0], 1 << i, group_size, 32)) (``warp`` uses ``warp_sync``; ``warpgroup`` ``warpgroup_sync(8)``; ``cta`` ``cta_sync``. Thread scope is instead the sequential loop of :doc:`local`.) Generated TIRx IR ----------------- .. code-block:: python thread_data[0] = thread_data[0] + T.tvm_warp_shuffle_xor(T.tvm_warp_activemask(), thread_data[0], 1, 8, 32) thread_data[0] = thread_data[0] + T.tvm_warp_shuffle_xor(T.tvm_warp_activemask(), thread_data[0], 2, 8, 32) thread_data[0] = thread_data[0] + T.tvm_warp_shuffle_xor(T.tvm_warp_activemask(), thread_data[0], 4, 8, 32) Generated CUDA -------------- .. code-block:: c++ thread_data_ptr[0] = thread_data_ptr[0] + __shfl_xor_sync(__activemask(), thread_data_ptr[0], 1, 8); thread_data_ptr[0] = thread_data_ptr[0] + __shfl_xor_sync(__activemask(), thread_data_ptr[0], 2, 8); thread_data_ptr[0] = thread_data_ptr[0] + __shfl_xor_sync(__activemask(), thread_data_ptr[0], 4, 8); (Verified on ``sm_100a`` — each ``B[r] == sum(A[r, :])``. The shuffle width ``8`` is the group size, not the full warp.) How inputs change the algorithm ------------------------------- .. list-table:: :header-rows: 1 :widths: 28 72 * - input - effect * - op - ``sum`` → ``+`` shuffle tree; ``max`` / ``min`` → the corresponding combine * - reduction length / thread count - set ``group_size = min(next_pow2(reduction_len), 32, thread_cnt)`` and hence the number of shuffle steps * - exec scope - ``cta`` / ``warpgroup`` / ``warp`` → shuffle tree (different sync); ``thread`` → sequential loop * - accum - ``True`` combines the reduced value with the old dst