Cuda TMA (Tensor Memory Accelerator)

Cuda 从 Hopper 架构引入了 Tensor Memory Accelerator,简称 TMA。它是一个专门负责在shared memory和 global memory 之间搬运数据的专用协处理器。通过专用协处理器可以将数据拷贝从 warp 同步调度中分解出来,实现异步数据拷贝,并最终通过重叠计算和内存访问,提高GPU的内存带宽利用率。

基础介绍

TMA 可以执行四类操作:

  1. global memory 到 shared memory 的 load;

  2. shared memory 到 global memory 的 store;

  3. shared 到 global 的 reduce,也就是边写回边做逐元素归约;

  4. multicast,把同一份数据广播到多个 block 所在的 SM。

cp.async 本质上还是“由 warp 驱动的细粒度异步拷贝指令”,而 TMA 本质上是“由描述符驱动的、独立硬件引擎执行的块级/张量级搬运”。

传统 cp.async 里,每个线程还是要自己参与这件事。通常是一个 warp 的 32 个 lane,各自给出 global address 和 shared memory address,每个 lane 搬一小段数据,典型是 4/8/16B。于是整个 warp 合起来完成一块数据的搬运。虽然它是异步的,但它仍然是“线程指令流的一部分”:warp 要发出这些 cp.async 指令,硬件按 lane 收集地址、形成内存事务,再把结果写进 shared memory。

TMA 则完全换了思路。程序先在 host 端构造一个 CUtensorMap 描述符,把张量的基地址、维度、stride、tile 大小、swizzle 等信息编码好。到 kernel 里,往往只需要一个 lane 发起一次 TMA 指令,并告诉硬件“我要第几个 tile”。之后真正的地址生成、多维步长展开、tile 内各元素的定位、实际内存请求的组织,都是 TMA 引擎自己完成的。也就是说,线程不再逐小块参与搬运过程,而是把“搬哪一块”这件事提交给硬件协处理器。

TMA cp.async 相比,它的几个关键特点是:

  • 搬运粒度从小块连续字节提升到 tile 粒度

  • 可以处理多维张量布局,而不是手写复杂地址计算。

  • 一般只需要 单个 lane 发起,后面的搬运由 TMA 引擎独立完成。

  • 这样可以减少指令发射压力,也能减少寄存器压力。

这意味着:

  • 线程不用再为每个元素做大量 index arithmetic。

  • 数据搬运和有用计算更容易重叠。

  • 后面的多 warp pipeline 才有成立的基础。

Tensor descriptor:CUtensorMap

TMA 不能凭空知道一块数据在 global memory 里长什么样,所以必须先在 host 端创建一个 tensor descriptor,再传给 kernel。Tensor descriptor 即CUtensorMap通常包含:

  • tensorDataType:元素类型。这个 lab 里重点是 bf16

  • tensorRank:张量维度。

  • globalDim:整个 tensor 每一维多大。

  • globalStrides:global memory 中跨维移动时要跳过多少 字节

  • boxDim:一次 TMA 操作搬多大的 tile。

  • elementStrides:写入 shared memory 时,各维元素的步长,单位是 元素,不是字节。

TMA 完成异步数据搬运

因为 TMA 是异步的,所以 issue 一次拷贝之后,线程并不能立刻假设数据已经到达 shared memory。这个时候就必须有一种同步机制来判断“数据什么时候真正可用”。

对于 global-to-shared 的 TMA load,Hopper 使用的是 split barrier,也叫 mbarrier。这和我们平时熟悉的 __syncthreads() 很不一样。

__syncthreads() 是“到达和等待”绑定在一起的 join barrier。线程一旦到这里,就一边声明自己到了,一边阻塞等待所有人都到齐。

而 split barrier 把“arrival”和“wait”拆开了。线程可以先到达 barrier,之后去做别的事,过一会儿再来等待它完成。更重要的是,arrival 数量可以在运行时配置,所以你可以只同步某几个 lane,而不是整个 block。

在 TMA 场景下,mbarrier 不仅可以统计有多少线程到达,还能统计有多少字节的异步数据已经到达。换句话说,barrier 释放的条件是双重的:参与同步的 lane 已经满足 arrival 条件,而且 TMA 搬运的字节也真的完成了。这样 consumer 才不会过早读取尚未到达的 shared memory 数据。

TMA 的异步性必须配合 barrier 的状态管理,否则就会读到错误数据。

单 block、单 lane、单 tile 的 TMA load

一个 block,一个 lane,一次 TMA,把一块 tile 从 global memory 搬到 shared memory,再用普通 CUDA store 写回 global memory。

TMA load 用的是 mbarrier,因为 barrier 和目标 shared memory 在同一侧,硬件容易在数据到达 shared memory 时顺便更新 barrier 状态。但 TMA store 是 shared 到 global,写完成的信息如果还要再反向通知 shared memory 中的 barrier,就会涉及一次不自然的“往返确认”。因此硬件没有沿用 mbarrier 方案,而是采用 commit group 机制来追踪 store 是否完成。

示例代码:

__device__ static __forceinline__ void async_proxy_fence() {
  asm volatile("fence.proxy.async.shared::cta;\n" ::: "memory");
}

__device__ static __forceinline__ void init_barrier(uint64_t *bar,
                                                    int arrival_count) {
  uint32_t bar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile("mbarrier.init.shared::cta.b64 [%0], %1;\n" ::"r"(bar_ptr),
               "r"(arrival_count)
               : "memory");
}

__device__ static __forceinline__ void arrive(uint64_t *bar, uint32_t count) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile("mbarrier.arrive.release.cta.shared::cta.b64 _, [%0],  %1;\n"
               :
               : "r"(mbar_ptr), "r"(count)
               : "memory");
}

__device__ static __forceinline__ int try_wait(uint64_t *bar, int phaseParity) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  int result;
  asm volatile("{\n"
               ".reg .pred P1;\n"
               "mbarrier.try_wait.parity.shared::cta.b64 P1, [%1], %2;\n"
               "selp.u32 %0,1,0,P1;"
               "}\n"
               : "=r"(result)
               : "r"(mbar_ptr), "r"(phaseParity));
  return result;
}

__device__ static __forceinline__ int test_wait(uint64_t *bar,
                                                int phaseParity) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  int result;
  asm volatile("{\n"
               ".reg .pred P1;\n"
               "mbarrier.test_wait.parity.shared::cta.b64 P1, [%1], %2;\n"
               "selp.u32 %0,1,0,P1;"
               "}\n"
               : "=r"(result)
               : "r"(mbar_ptr), "r"(phaseParity));
  return result;
}

__device__ static __forceinline__ void wait(uint64_t *bar, int phaseParity) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile("{\n"
               ".reg .pred                P1;\n"
               "LAB_WAIT:\n"
               "mbarrier.try_wait.parity.shared::cta.b64 P1, [%0], %1;\n"
               "@P1                       bra.uni DONE;\n"
               "bra.uni                   LAB_WAIT;\n"
               "DONE:\n"
               "}\n" ::"r"(mbar_ptr),
               "r"(phaseParity));
}

__device__ static __forceinline__ void expect_bytes(uint64_t *bar,
                                                    uint32_t bytes) {
  uint32_t bar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm("mbarrier.expect_tx.relaxed.cta.shared::cta.b64 [%0], %1;\n"
      :
      : "r"(bar_ptr), "r"(bytes)
      : "memory");
}

__device__ static __forceinline__ void expect_bytes_and_arrive(uint64_t *bar,
                                                               uint32_t bytes) {
  uint32_t bar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm("mbarrier.arrive.expect_tx.release.cta.shared.b64 _, [%0], %1;\n "
      :
      : "r"(bar_ptr), "r"(bytes)
      : "memory");
}

__device__ static __forceinline__ void tma_commit_group() {
  asm volatile("cp.async.bulk.commit_group;");
}

template <int N>
__device__ static __forceinline__ void tma_wait_until_pending() {
  asm volatile("cp.async.bulk.wait_group %0;" : : "n"(N) : "memory");
}

__device__ static __forceinline__ void cp_async_bulk_tensor_1d_global_to_shared(
    void *smem_dest, const CUtensorMap *tensor_map, int c0, uint64_t *bar) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile(
      "cp.async.bulk.tensor.1d.shared::cluster.global.tile.mbarrier::complete_"
      "tx::bytes "
      "[%0], [%1, {%2}], [%3];\n"
      :
      : "r"(static_cast<uint32_t>(__cvta_generic_to_shared(smem_dest))),
        "l"(tensor_map), "r"(c0), "r"(mbar_ptr)
      : "memory");
}

/**
 * @brief Asynchronously copy a 2D tensor tile from global to shared memory.
 *
 * @param smem_dest Destination address in shared memory.
 * @param tensor_map Tensor map descriptor for the source tensor.
 * @param c0 Coordinate in the first dimension.
 * @param c1 Coordinate in the second dimension.
 * @param bar Pointer to mbarrier for completion tracking.
 * @return This function does not return a value.
 */
__device__ static __forceinline__ void
cp_async_bulk_tensor_2d_global_to_shared(void *smem_dest,
                                         const CUtensorMap *tensor_map, int c0,
                                         int c1, uint64_t *bar) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile(
      "cp.async.bulk.tensor.2d.shared::cluster.global.tile.mbarrier::complete_"
      "tx::bytes "
      "[%0], [%1, {%2, %3}], [%4];\n"
      :
      : "r"(static_cast<uint32_t>(__cvta_generic_to_shared(smem_dest))),
        "l"(tensor_map), "r"(c0), "r"(c1), "r"(mbar_ptr)
      : "memory");
}

/**
 * @brief Asynchronously copy a 3D tensor tile from global to shared memory.
 *
 * @param smem_dest Destination address in shared memory.
 * @param tensor_map Tensor map descriptor for the source tensor.
 * @param c0 Coordinate in the first dimension.
 * @param c1 Coordinate in the second dimension.
 * @param c2 Coordinate in the third dimension.
 * @param bar Pointer to mbarrier for completion tracking.
 * @return This function does not return a value.
 */
__device__ static __forceinline__ void
cp_async_bulk_tensor_3d_global_to_shared(void *smem_dest,
                                         const CUtensorMap *tensor_map, int c0,
                                         int c1, int c2, uint64_t *bar) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile(
      "cp.async.bulk.tensor.3d.shared::cluster.global.tile.mbarrier::complete_"
      "tx::bytes "
      "[%0], [%1, {%2, %3, %4}], [%5];\n"
      :
      : "r"(static_cast<uint32_t>(__cvta_generic_to_shared(smem_dest))),
        "l"(tensor_map), "r"(c0), "r"(c1), "r"(c2), "r"(mbar_ptr)
      : "memory");
}

__device__ static __forceinline__ void cp_async_bulk_tensor_4d_global_to_shared(
    void *smem_dest, const CUtensorMap *tensor_map, int c0, int c1, int c2,
    int c3, uint64_t *bar) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile(
      "cp.async.bulk.tensor.4d.shared::cluster.global.tile.mbarrier::complete_"
      "tx::bytes "
      "[%0], [%1, {%2, %3, %4, %5}], [%6];\n"
      :
      : "r"(static_cast<uint32_t>(__cvta_generic_to_shared(smem_dest))),
        "l"(tensor_map), "r"(c0), "r"(c1), "r"(c2), "r"(c3), "r"(mbar_ptr)
      : "memory");
}

__device__ static __forceinline__ void cp_async_bulk_tensor_5d_global_to_shared(
    void *smem_dest, const CUtensorMap *tensor_map, int c0, int c1, int c2,
    int c3, int c4, uint64_t *bar) {
  uint32_t mbar_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(bar));
  asm volatile(
      "cp.async.bulk.tensor.5d.shared::cluster.global.tile.mbarrier::complete_"
      "tx::bytes "
      "[%0], [%1, {%2, %3, %4, %5, %6}], [%7];\n"
      :
      : "r"(static_cast<uint32_t>(__cvta_generic_to_shared(smem_dest))),
        "l"(tensor_map), "r"(c0), "r"(c1), "r"(c2), "r"(c3), "r"(c4),
        "r"(mbar_ptr)
      : "memory");
}

__device__ static __forceinline__ void
cp_async_bulk_tensor_1d_shared_to_global(const CUtensorMap *tensor_map, int c0,
                                         const void *src) {
  asm volatile("cp.async.bulk.tensor.1d.global.shared::cta.tile.bulk_group "
               "[%0, {%1}], [%2];\n"
               :
               : "l"(tensor_map), "r"(c0),
                 "r"(static_cast<uint32_t>(__cvta_generic_to_shared(src)))
               : "memory");
}

__device__ static __forceinline__ void
cp_async_bulk_tensor_2d_shared_to_global(const CUtensorMap *tensor_map, int c0,
                                         int c1, const void *src) {
  asm volatile("cp.async.bulk.tensor.2d.global.shared::cta.tile.bulk_group "
               "[%0, {%1, %2}], [%3];\n"
               :
               : "l"(tensor_map), "r"(c0), "r"(c1),
                 "r"(static_cast<uint32_t>(__cvta_generic_to_shared(src)))
               : "memory");
}

__device__ static __forceinline__ void
cp_async_bulk_tensor_3d_shared_to_global(const CUtensorMap *tensor_map, int c0,
                                         int c1, int c2, const void *src) {
  asm volatile("cp.async.bulk.tensor.3d.global.shared::cta.tile.bulk_group "
               "[%0, {%1, %2, %3}], [%4];\n"
               :
               : "l"(tensor_map), "r"(c0), "r"(c1), "r"(c2),
                 "r"(static_cast<uint32_t>(__cvta_generic_to_shared(src)))
               : "memory");
}

__device__ static __forceinline__ void
cp_async_bulk_tensor_4d_shared_to_global(const CUtensorMap *tensor_map, int c0,
                                         int c1, int c2, int c3,
                                         const void *src) {
  asm volatile("cp.async.bulk.tensor.4d.global.shared::cta.tile.bulk_group "
               "[%0, {%1, %2, %3, %4}], [%5];\n"
               :
               : "l"(tensor_map), "r"(c0), "r"(c1), "r"(c2), "r"(c3),
                 "r"(static_cast<uint32_t>(__cvta_generic_to_shared(src)))
               : "memory");
}

__device__ static __forceinline__ void
cp_async_bulk_tensor_5d_shared_to_global(const CUtensorMap *tensor_map, int c0,
                                         int c1, int c2, int c3, int c4,
                                         const void *src) {
  asm volatile("cp.async.bulk.tensor.5d.global.shared::cta.tile.bulk_group "
               "[%0, {%1, %2, %3, %4, %5}], [%6];\n"
               :
               : "l"(tensor_map), "r"(c0), "r"(c1), "r"(c2), "r"(c3), "r"(c4),
                 "r"(static_cast<uint32_t>(__cvta_generic_to_shared(src)))
               : "memory");
}


template <int TILE_M, int TILE_N>
__global__ void single_tma_load(__grid_constant__ const CUtensorMap src_map, bf16 *dest) {
    __shared__ alignas(128) bf16 share_mem[TILE_M * TILE_N];
    __shared__ alignas(8) uint64_t barrier;

    constexpr int tileSize = TILE_M * TILE_N * sizeof(bf16);
    init_barrier(&barrier, 1);
    async_proxy_fence();

    expect_bytes(&barrier, tileSize);

    cp_async_bulk_tensor_2d_global_to_shared(share_mem, &src_map, 0, 0, &barrier);
    arrive(&barrier, 1);
    wait(&barrier, 0);

    for (int i = 0; i < TILE_M; ++i) {
        int base = i * TILE_N;
        for (int j = 0; j < TILE_N; ++j) {
            int idx = base + j;
            dest[idx] = share_mem[idx];
        }
    }
}

template <int TILE_M, int TILE_N> void launch_single_tma_load(bf16 *src, bf16 *dest) {
    alignas(128) CUtensorMap tensor_map;
    constexpr cuuint32_t RANK = 2;

    const cuuint64_t global_dim[RANK] = {
        static_cast<cuuint64_t>(TILE_N),
        static_cast<cuuint64_t>(TILE_M)};

    const cuuint64_t global_stride[RANK - 1] = {
        static_cast<cuuint64_t>(TILE_N * sizeof(bf16))};

    const cuuint32_t box_dim[RANK] = {
        static_cast<cuuint32_t>(TILE_N),
        static_cast<cuuint32_t>(TILE_M),
    };

    const cuuint32_t element_stride[RANK] = {1, 1};

    CUDA_CHECK(cuInit(0));
    CUDA_CHECK(cuTensorMapEncodeTiled(
        &tensor_map,
        CU_TENSOR_MAP_DATA_TYPE_BFLOAT16,
        RANK,
        src,
        global_dim,
        global_stride,
        box_dim,
        element_stride,
        CU_TENSOR_MAP_INTERLEAVE_NONE,
        CU_TENSOR_MAP_SWIZZLE_NONE,
        CU_TENSOR_MAP_L2_PROMOTION_NONE,
        CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE));

    single_tma_load<TILE_M, TILE_N><<<1, 1>>>(tensor_map, dest);
}

TMA store

TMA Store:即“shared memory 写回 global memory”。看起来只是方向跟TMS load反了,但同步机制其实完全不同。

TMA load 用的是 mbarrier,因为 barrier 和目标 shared memory 在同一侧,硬件容易在数据到达 shared memory 时顺便更新 barrier 状态。但 TMA store 是 shared 到 global,写完成的信息如果还要再反向通知 shared memory 中的 barrier,就会涉及一次不自然的“往返确认”。因此硬件没有沿用 mbarrier 方案,而是采用 commit group 机制来追踪 store 是否完成。

示例代码:

typedef __nv_bfloat16 bf16;

template <int TILE_M, int TILE_N>
__global__ void single_tma_store(__grid_constant__ const CUtensorMap src_map,
                                 __grid_constant__ const CUtensorMap dest_map) {
    __shared__ alignas(128) bf16 share_mem[TILE_M * TILE_N];
    __shared__ alignas(8) uint64_t barrier;

    constexpr int tile_size = TILE_M * TILE_N * sizeof(bf16);
    init_barrier(&barrier, 1);
    async_proxy_fence();

    expect_bytes(&barrier, tile_size);
    cp_async_bulk_tensor_2d_global_to_shared(share_mem, &src_map, 0, 0,
                                             &barrier);
    arrive(&barrier, 1);
    wait(&barrier, 0);

    cp_async_bulk_tensor_2d_shared_to_global(&dest_map, 0, 0, share_mem);
    tma_commit_group();
    tma_wait_until_pending<0>();
}

template <int TILE_M, int TILE_N>
void launch_single_tma_store(bf16 *src, bf16 *dest) {
    alignas(128) CUtensorMap src_map;
    alignas(128) CUtensorMap dest_map;
    constexpr cuuint32_t RANK = 2;

    const cuuint64_t global_dim[RANK] = {
        static_cast<cuuint64_t>(TILE_N),
        static_cast<cuuint64_t>(TILE_M)};

    const cuuint64_t global_stride[RANK - 1] = {
        static_cast<cuuint64_t>(TILE_N * sizeof(bf16))};

    const cuuint32_t box_dim[RANK] = {
        static_cast<cuuint32_t>(TILE_N),
        static_cast<cuuint32_t>(TILE_M),
    };

    const cuuint32_t element_stride[RANK] = {1, 1};

    CUDA_CHECK(cuInit(0));
    CUDA_CHECK(cuTensorMapEncodeTiled(
        &src_map,
        CU_TENSOR_MAP_DATA_TYPE_BFLOAT16,
        RANK,
        src,
        global_dim,
        global_stride,
        box_dim,
        element_stride,
        CU_TENSOR_MAP_INTERLEAVE_NONE,
        CU_TENSOR_MAP_SWIZZLE_NONE,
        CU_TENSOR_MAP_L2_PROMOTION_NONE,
        CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE));
    CUDA_CHECK(cuTensorMapEncodeTiled(
        &dest_map,
        CU_TENSOR_MAP_DATA_TYPE_BFLOAT16,
        RANK,
        dest,
        global_dim,
        global_stride,
        box_dim,
        element_stride,
        CU_TENSOR_MAP_INTERLEAVE_NONE,
        CU_TENSOR_MAP_SWIZZLE_NONE,
        CU_TENSOR_MAP_L2_PROMOTION_NONE,
        CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE));

    single_tma_store<TILE_M, TILE_N><<<1, 1>>>(src_map, dest_map);
}

TMA reduce,把“搬运”和“归约”合并

TMA 还有更强的能力:边搬运边做 reduce。也就是把 shared memory 中的数据写回 global memory 的同时,与 global memory 里原有数据做逐元素归约,例如 add、min 之类。

add 版本的 TMA reduce 接口示例代码如下:

__device__ static __forceinline__ void
cp_async_reduce_add_bulk_tensor_2d_shared_to_global(
    const CUtensorMap *tensor_map, int c0, int c1, const void *src) {
    asm volatile(
        "cp.reduce.async.bulk.tensor.2d.global.shared::cta.add.tile.bulk_group "
        "[%0, {%1, %2}], [%3];\n"
        :
        : "l"(tensor_map), "r"(c0), "r"(c1),
          "r"(static_cast<uint32_t>(__cvta_generic_to_shared(src)))
        : "memory");
}

template <int TILE_M, int TILE_N>
__global__ void
single_tma_reduce(__grid_constant__ const CUtensorMap src_map,
                  __grid_constant__ const CUtensorMap dest_map) {
    __shared__ alignas(128) bf16 share_mem[TILE_M * TILE_N];
    __shared__ alignas(8) uint64_t barrier;

    constexpr int tile_size = TILE_M * TILE_N * sizeof(bf16);
    init_barrier(&barrier, 1);
    async_proxy_fence();

    expect_bytes(&barrier, tile_size);
    cp_async_bulk_tensor_2d_global_to_shared(share_mem, &src_map, 0, 0,
                                             &barrier);
    arrive(&barrier, 1);
    wait(&barrier, 0);

    cp_async_reduce_add_bulk_tensor_2d_shared_to_global(&dest_map, 0, 0,
                                                        share_mem);
    tma_commit_group();
    tma_wait_until_pending<0>();
}

template <int TILE_M, int TILE_N>
void launch_single_tma_reduce(bf16 *src, bf16 *dest) {
    alignas(128) CUtensorMap src_map;
    alignas(128) CUtensorMap dest_map;
    constexpr cuuint32_t RANK = 2;

    const cuuint64_t global_dim[RANK] = {
        static_cast<cuuint64_t>(TILE_N),
        static_cast<cuuint64_t>(TILE_M)};

    const cuuint64_t global_stride[RANK - 1] = {
        static_cast<cuuint64_t>(TILE_N * sizeof(bf16))};

    const cuuint32_t box_dim[RANK] = {
        static_cast<cuuint32_t>(TILE_N),
        static_cast<cuuint32_t>(TILE_M),
    };

    const cuuint32_t element_stride[RANK] = {1, 1};

    CUDA_CHECK(cuInit(0));
    CUDA_CHECK(cuTensorMapEncodeTiled(
        &src_map,
        CU_TENSOR_MAP_DATA_TYPE_BFLOAT16,
        RANK,
        src,
        global_dim,
        global_stride,
        box_dim,
        element_stride,
        CU_TENSOR_MAP_INTERLEAVE_NONE,
        CU_TENSOR_MAP_SWIZZLE_NONE,
        CU_TENSOR_MAP_L2_PROMOTION_NONE,
        CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE));
    CUDA_CHECK(cuTensorMapEncodeTiled(
        &dest_map,
        CU_TENSOR_MAP_DATA_TYPE_BFLOAT16,
        RANK,
        dest,
        global_dim,
        global_stride,
        box_dim,
        element_stride,
        CU_TENSOR_MAP_INTERLEAVE_NONE,
        CU_TENSOR_MAP_SWIZZLE_NONE,
        CU_TENSOR_MAP_L2_PROMOTION_NONE,
        CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE));

    single_tma_reduce<TILE_M, TILE_N><<<1, 1>>>(src_map, dest_map);
}

TMA 中使用 swizzling pattern

我们知道 shared memory 的 bank conflict 会严重影响性能。如果一个 warp 中多个 lane 访问的地址落在同一个 bank,就会发生串行化。过去通常靠 padding、转置或手工重排来规避。但在 Hopper 上,TMA 可以在写入 shared memory 时直接按某种 swizzle pattern 排列数据,从而降低或避免 bank conflict。

具体来说:TMA 把一个 tile 从 global memory 搬到 shared memory 时,不按线性地址写入 shared memory,而是按 tensor map descriptor 里的 CUtensorMapSwizzle 做重排,目的是让后续 warp 对 shared memory 的访问分散到不同 bank。NVIDIA 的文档把 TMA 描述为 Hopper 引入的 global/shared bulk copy 机制;driver API 里 cuTensorMapEncodeTiled / cuTensorMapEncodeIm2col* 都显式带 CUtensorMapSwizzle swizzle 参数。