Files
project_6_89d52222/muh/include/muh/tuning/tuning_reduce.cuh
muh-bot 2795d2b7f2 [muh] tuning_reduce: 修正 SMEM 模型错误 (基于 agent_reduce.cuh 源码分析)
关键修正:
  旧注释: 'tile = tpb * ipt * accum_size ≤ 48KB SMEM'
  实际: reduce 不使用 BlockLoad 的 SMEM staging buffer!
    数据直接从 global memory 加载到寄存器 (striped/vectorized)
    SMEM 仅用于 BlockReduce (warp shuffle scratch, << 1KB)
    真正的约束是寄存器压力: items[ITEMS_PER_THREAD] 全在寄存器里
    items=24 float32 → ~24 regs → 可以接受 (64K regs/SM)

from agent_reduce.cuh:
  - ConsumeFullTile: 直接 striped load 或 VectorT 加载到 items[] 数组
  - 没有 BlockLoad::TempStorage (不像 scan 有 union {load, store, scan})
  - ATTEMPT_VECTORIZATION 条件: vec_size>1, items%vec==0, sizeof≤8
  - 数值不变 (items=24 对 float32 是合理的, 只是理由从错误的 SMEM 改为正确的寄存器压力)

from kernel_reduce.cuh:
  - atomic path (not_guaranteed): atomicAdd 聚合, 16 SMs 上 contention 极低
  - LOAD_LDG 保留 (等 bench 数据, topk 显示 LOAD_DEFAULT 可能更优)
2026-08-05 03:20:38 +00:00

298 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// muh/include/muh/tuning/tuning_reduce.cuh — BI-V100 reduce tuning
//
// Mirrors: cccl_upstream/cub/cub/device/dispatch/tuning/tuning_reduce.cuh
// vllm impact: Attention score reduction in paged_attention (every decode step)
// Competition weight: Output TPS × 16.796 (83% — highest priority)
//
// HARDWARE PROFILE (confirmed via ixsmi on Phanthy Cloud):
// SM count: 16 (NOT 50 from spec sheet)
// SMEM: 48KB (49152 bytes) per block
// L2 cache: 6MB
// HBM BW: 900 GB/s
// BW/SM: 900/16 = 56 GB/s
// Warp size: 32
//
// CRITICAL ARCHITECTURE INSIGHT (from agent_reduce.cuh source):
// Reduce does NOT use BlockLoad — data is loaded directly to registers
// via striped access (ConsumeFullTile) or vectorized loads (VectorT).
// The SMEM is ONLY used by BlockReduce (warp shuffles + small scratch).
// BlockReduce<WARP_REDUCTIONS> SMEM ≈ threads * sizeof(AccumT) / 32 (one value per warp).
//
// Previous muh tuning assumed "tile = tpb * ipt * accum_size ≤ 48KB SMEM"
// — THIS WAS WRONG. That formula applies to scan (which uses BlockLoad staging).
// For reduce, the real constraint is REGISTER PRESSURE:
// Each thread holds AccumT items[ITEMS_PER_THREAD] in registers.
// items=24 for float32 → 24 registers → acceptable (BI-V100 has 64K registers/SM)
// items=24 also means fewer CTAs needed → good for 16 SMs
//
// Vectorized load condition (agent_reduce.cuh line ~180):
// ATTEMPT_VECTORIZATION requires: vec_size > 1, items % vec == 0,
// is_pointer<InputT>, is_trivially_relocatable<InputT>, sizeof(InputT) <= 8
//
// CCCL kernel_reduce.cuh two execution paths:
// 1. multi-tile (DeviceReduceKernel): each block reduces a tile partition
// - StableReductionOrder=false: atomicAdd aggregation (lowest latency)
// - StableReductionOrder=true: write to d_out[blockIdx.x], then single-tile pass
// 2. single-tile (DeviceReduceSingleTileKernel): one block for all data
//
// BI-V100: 16 SMs → max 32 CTAs → atomic contention is negligible
// → not_guaranteed determinism (atomic path) is optimal for decode
//
// PENDING REAL BENCHMARK: LOAD_LDG vs LOAD_DEFAULT
// topk bench showed LOAD_DEFAULT (ld=0) beat LOAD_LDG (ld=1).
// reduce may be similar — BI-V100's L1/L2 behavior differs from NVIDIA.
// Keep LOAD_LDG for now (CCCL default), switch if bench says otherwise.
#pragma once
#include "muh/hardware.cuh"
#include "muh/tuning/common.cuh"
namespace muh::tuning::reduce {
struct ReducePassPolicy {
int threads_per_block;
int items_per_thread;
int vec_size;
BlockReduceAlgorithm reduce_algorithm;
CacheLoadModifier load_modifier;
};
struct ReducePolicy {
ReducePassPolicy multi_tile;
ReducePassPolicy single_tile;
};
enum class determinism_t {
run_to_run,
gpu_to_gpu,
not_guaranteed,
};
// ============================================================
// BI-V100 tuning values for plus<> operator
//
// Tile sizing rationale (corrected):
// NOT SMEM-limited — reduce loads to registers, not SMEM staging.
// Constraint is register pressure + occupancy:
// items=24, float32 → 24 regs for data + overhead → ~40 regs/thread
// SM has 64K regs → 64K/40 = 1600 threads max → 3 CTAs @ 512 threads ✓
// Also want large tiles (few CTAs) because 16 SMs can't launch many anyway.
// CCCL SM100 uses items=16 for float32 (with 148 SMs, more CTAs = more parallelism)
// BI-V100 uses items=24 (fewer CTAs, each processes more data)
// ============================================================
// --- plus<> operator, two-phase (WARP_REDUCTIONS) ---
struct bi100_plus_accum1_o4 {
// accum_size=1, reg pressure: 32 regs × 1B = trivial
static constexpr int items = 32;
static constexpr int threads = 512;
static constexpr int vec = 4; // 4×1B = 4-byte vectorized load
};
struct bi100_plus_accum2_o4 {
// accum_size=2 (float16/bfloat16 — Qwen3.6 KV cache hot path)
// reg pressure: 24 regs × 2B accum overhead → moderate
static constexpr int items = 24;
static constexpr int threads = 512;
static constexpr int vec = 2; // 2×2B = 4-byte vectorized load
};
struct bi100_plus_float32_o4 {
// accum_size=4 (float32 — paged_attention score reduction)
// reg pressure: 24 × 4B = 96B per thread → ~24 regs just for data
// CCCL SM100: items=16, threads=512, vec=2
// BI-V100: items=24 (fewer CTAs = each does more, compensating for 16 SMs)
static constexpr int items = 24;
static constexpr int threads = 512;
static constexpr int vec = 2; // items%vec=0 ✓ → vectorized load enabled
};
struct bi100_plus_float32_o8 {
static constexpr int items = 24;
static constexpr int threads = 512;
static constexpr int vec = 1; // 8-byte offset → unaligned risk, disable vectorize
};
struct bi100_plus_float64_o4 {
// accum_size=8, reg pressure: 16 × 2 regs (double) = 32 regs → fine
// CCCL SM100: items=16, threads=640 → 640 NOT multiple of 32 on all warps
// BI-V100: threads=384 (12 warps, clean), items=16
static constexpr int items = 16;
static constexpr int threads = 384;
static constexpr int vec = 2; // 2×8B = 16-byte vectorized load
};
struct bi100_plus_float64_o8 {
static constexpr int items = 16;
static constexpr int threads = 384;
static constexpr int vec = 1;
};
struct bi100_plus_int64_o4 {
static constexpr int items = 16;
static constexpr int threads = 384;
static constexpr int vec = 2;
};
struct bi100_plus_int64_o8 {
static constexpr int items = 16;
static constexpr int threads = 384;
static constexpr int vec = 1;
};
struct bi100_plus_accum16_o4 {
// accum_size=16 (int128/complex<double>)
// reg pressure: 16 × 4 regs = 64 regs → high, keep items=16
// threads=192 (6 warps) to allow 2 CTAs per SM for occupancy
static constexpr int items = 16;
static constexpr int threads = 192;
static constexpr int vec = 1; // sizeof(InputT)=16 > 8 → ATTEMPT_VECTORIZATION=false
};
// --- Deterministic tunings: BLOCK_REDUCE_RAKING, vec=1 ---
// RAKING needs more SMEM than WARP_REDUCTIONS (scratch array per warp-step)
// but still far less than scan's BlockLoad staging
struct bi100_det_float32 {
// Deterministic: RAKING with large tile
// items=32, threads=384 → 32 regs for data → acceptable
static constexpr int items = 32;
static constexpr int threads = 384;
};
struct bi100_det_float64 {
static constexpr int items = 16;
static constexpr int threads = 384;
};
struct bi100_det_int32 {
static constexpr int items = 32;
static constexpr int threads = 384;
};
struct bi100_det_int16 {
// int16/float16/bfloat16: items=64 → 64 regs × 2B/accum → moderate
static constexpr int items = 64;
static constexpr int threads = 384;
};
// --- Default fallback ---
struct bi100_default {
static constexpr int items = 24;
static constexpr int threads = 256;
static constexpr int vec = 4;
};
// ============================================================
// policy_selector
// ============================================================
struct policy_selector {
type_t accum_t;
op_kind_t operation_t;
int offset_size;
int accum_size;
determinism_t determinism = determinism_t::run_to_run;
constexpr ReducePolicy get_deterministic(const hardware_capability& hw) const {
if (hw.at_least(hardware_capability::vendor_t::iluvatar, 100)) {
if (accum_size <= 2) {
auto [i, t] = scale_mem_bound(bi100_det_int16::threads,
bi100_det_int16::items, accum_size);
ReducePassPolicy rp{t, i, 1, BLOCK_REDUCE_RAKING, LOAD_DEFAULT};
return {rp, rp};
}
if (accum_t == type_t::float32 || accum_size == 4) {
auto [i, t] = scale_mem_bound(bi100_det_float32::threads,
bi100_det_float32::items, accum_size);
ReducePassPolicy rp{t, i, 1, BLOCK_REDUCE_RAKING, LOAD_DEFAULT};
return {rp, rp};
}
if (accum_t == type_t::float64 || accum_size == 8) {
auto [i, t] = scale_mem_bound(bi100_det_float64::threads,
bi100_det_float64::items, accum_size);
ReducePassPolicy rp{t, i, 1, BLOCK_REDUCE_RAKING, LOAD_DEFAULT};
return {rp, rp};
}
}
auto [i, t] = scale_mem_bound(256, 16, accum_size);
ReducePassPolicy rp{t, i, 1, BLOCK_REDUCE_RAKING, LOAD_DEFAULT};
return {rp, rp};
}
constexpr ReducePolicy get_two_phase(const hardware_capability& hw) const {
if (hw.at_least(hardware_capability::vendor_t::iluvatar, 100)) {
if (operation_t == op_kind_t::plus || operation_t == op_kind_t::min
|| operation_t == op_kind_t::max) {
if (accum_size == 1) {
auto [i, t] = scale_mem_bound(bi100_plus_accum1_o4::threads,
bi100_plus_accum1_o4::items, accum_size);
ReducePassPolicy rp{t, i, bi100_plus_accum1_o4::vec,
BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
if (accum_size == 2) {
auto [i, t] = scale_mem_bound(bi100_plus_accum2_o4::threads,
bi100_plus_accum2_o4::items, accum_size);
ReducePassPolicy rp{t, i, bi100_plus_accum2_o4::vec,
BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
if (accum_size == 4) {
int vec = (offset_size <= 4) ? bi100_plus_float32_o4::vec
: bi100_plus_float32_o8::vec;
auto [i, t] = scale_mem_bound(bi100_plus_float32_o4::threads,
bi100_plus_float32_o4::items, accum_size);
ReducePassPolicy rp{t, i, vec, BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
if (accum_size == 8) {
if (accum_t == type_t::float64) {
int vec = (offset_size <= 4) ? bi100_plus_float64_o4::vec
: bi100_plus_float64_o8::vec;
auto [i, t] = scale_mem_bound(bi100_plus_float64_o4::threads,
bi100_plus_float64_o4::items, accum_size);
ReducePassPolicy rp{t, i, vec, BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
int vec = (offset_size <= 4) ? bi100_plus_int64_o4::vec
: bi100_plus_int64_o8::vec;
auto [i, t] = scale_mem_bound(bi100_plus_int64_o4::threads,
bi100_plus_int64_o4::items, accum_size);
ReducePassPolicy rp{t, i, vec, BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
if (accum_size == 16) {
auto [i, t] = scale_mem_bound(bi100_plus_accum16_o4::threads,
bi100_plus_accum16_o4::items, accum_size);
ReducePassPolicy rp{t, i, bi100_plus_accum16_o4::vec,
BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
}
}
auto [i, t] = scale_mem_bound(bi100_default::threads,
bi100_default::items, accum_size);
ReducePassPolicy rp{t, i, bi100_default::vec,
BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
constexpr ReducePolicy operator()(const hardware_capability& hw) const {
if (determinism == determinism_t::gpu_to_gpu)
return get_deterministic(hw);
auto policy = get_two_phase(hw);
if (determinism == determinism_t::not_guaranteed) {
policy.multi_tile.reduce_algorithm =
BLOCK_REDUCE_WARP_REDUCTIONS_NONDETERMINISTIC;
}
return policy;
}
};
} // namespace muh::tuning::reduce