Skip to content

CFT builds add about 2.6 µs to every thread-scope nvshmem_quiet() under TMA, even with logical endpoints off #138

Open

Description

@0z5a

With NVSHMEM_CFT_HANDLES_SUPPORT=ON, shmem_put_tma_bw is noticeably slower under NVSHMEM_TMA_POLICY=ENABLE than the same perftest built without CFT. The difference is a constant cost per iteration, about 2.6 µs, not a cost per put. Logical endpoints are not enabled in these runs (NVSHMEM_ENABLE_LOGICAL_ENDPOINT unset, POSIX FD heap handles), so no handle operation can be pending.

Measurements

B300, 2 PEs on one node over NVSwitch, devel 2dca770, library and perftests built for sm_103 with CUDA 13.3. The two builds differ only in -DNVSHMEM_CFT_HANDLES_SUPPORT=ON. Command: shmem_put_tma_bw -n 200 -w 20 -c 1 -t 128 -b 4096 -e 8388608, median of 5 runs, NVSHMEM_PERFTEST_VERIFY=1 (all 30 runs passed). GB/s:

size CFT off, DISABLE CFT off, ENABLE CFT on, DISABLE CFT on, ENABLE
4 KiB 1.50 1.13 1.48 0.66
32 KiB 10.62 9.08 10.39 5.29
256 KiB 36.72 29.03 36.11 23.58
1 MiB 49.64 35.52 48.60 32.97
8 MiB 55.18 38.86 54.11 38.30

FORCE matches ENABLE in both builds, and DISABLE is unaffected by CFT. Converted to time per iteration, CFT adds 2.58 µs at 4 KiB and 3.16 µs at 8 MiB, i.e. roughly the same amount whether an iteration issues 1 or 128 puts. On two RTX PRO 6000 (sm_120, PCIe) the same comparison showed CFT ENABLE about 2.4× slower at 4–32 KiB (single run, no verification).

Nsight Compute, one launch of bw_smem_tma at 4 KiB (1 CTA × 128 threads, 200 iterations), rank 0, one metric per run:

metric CFT off CFT on difference per iteration
gpu__time_duration.sum 896 µs 1408 µs 2.56 µs
smsp__inst_executed.sum 167,436 581,594 ~518 instructions per warp
smsp__sass_inst_executed_op_ld.sum (generic) 11,212 39,232 ~35 per warp
smsp__sass_inst_executed_op_global_ld.sum 4,804 7,216 ~3 per warp

Where it comes from

The perftest calls nvshmem_quiet() from all 128 threads once per iteration. In a CFT build, the thread-scope quiet first runs the handle quiet (nvshmemi_common_device.cuh:1191-1194), which for NVSHMEMI_THREADGROUP_THREAD is nvshmemi_handle_quiet_all() (:752-756 → :693-706). Every calling thread looks up the TMA shared-memory registration (dependent volatile global loads) and then reads pending_handle_active of all 32 handle-barrier slots through a generic pointer. That matches the ~32 extra generic loads and ~3 extra global loads per warp per iteration above. The only gate is a valid TMA registration, which is why DISABLE is unaffected. Handle operations themselves are only issued when nvshmemi_is_le_implemented() holds, which requires a valid logical-endpoint id (logical_endpoint_device.cuh:193-206; callers at nvshmemi_common_device.cuh:1369, :1410, :1430). With logical endpoints off, no slot can have a pending handle, but the scan still runs.

Questions

  • Could the scan be skipped when no handle operation can be pending, for example by checking the logical-endpoint state in the device state, or a per-registration flag that the handle put sets?
  • Since thread-scope quiet must cover every warp, would scanning once per warp (one lane, then a shuffle) be acceptable instead of once per thread?

Separately, the perftest fills shared memory from offset 0 after nvshmemx_give_smem(), so it overwrites the region that nvshmemx_give_smem() reserves for mbarriers and handle-barrier slots (NVSHMEMI_SMEM_DATA_REGION_OFFSET). It happens to leave pending_handle_active at 0 because the stored int values are thread ids below 65536. Should the perftest use the data region only?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions