[MUH] Fix 7 structural discrepancies vs CCCL — read source, not grep

Fixes found by reading all 17 muh files + 6 CCCL counterpart
policy_selectors as full source code input:

1. topk: BLOCK_LOAD_DIRECT → BLOCK_LOAD_VECTORIZE (CCCL SM90+ uses
   VECTORIZE). bits_per_pass was wrong (muh: ks<=4→9, CCCL: ks>=2→11).
   items now computed dynamically (4*4/key_size) not hardcoded.

2. reduce: added determinism dispatch — three modes matching CCCL:
   gpu_to_gpu (BLOCK_REDUCE_RAKING, vec_size=1, LOAD_DEFAULT),
   run_to_run (WARP_REDUCTIONS, LOAD_LDG, default),
   not_guaranteed (WARP_REDUCTIONS_NONDETERMINISTIC).
   Added bi100_det_float32 and bi100_det_float64 tuning structs
   with SM90 benchmark reference values.

3. batch_memcpy: flat single-tier → SmallBuffer+LargeBuffer two-tier
   matching CCCL structure (128 threads small, 256 threads large,
   warp_threshold=128, block_threshold=8192).

4. transform: single BulkPolicy → three-policy structure
   (VectorizedPolicy + AsyncCopyPolicy + PrefetchPolicy) matching CCCL.
   items_per_thread computed from bytes_in_flight / (threads * elem_size).

5. compile_test: 17 checks → 33 checks. Now verifies exact values:
   reduce determinism modes, topk VECTORIZE + bits=11, batch_memcpy
   two-tier thresholds, transform three-policy structure.

6. gen_patch: added fallback extraction for inline policy_selector
   values (topk now generates SAMPLING_BLOCK_SIZE patch).

7. MUH_PROJECT_CHECKPOINT.md: 'PRD设计阶段还没有代码' → actual status.

7 files changed, 413 insertions, 265 deletions.
This commit is contained in:
Claude
2026-07-30 14:37:38 +00:00
parent e02134a3ce
commit c7a63bc2c8
7 changed files with 411 additions and 263 deletions

View File

@@ -45,7 +45,7 @@ muh 是我们设计的 **tuning DSL领域特定语言**,用于:
**为什么需要它**: CCCL 有 27 个 tuning_*.cuh 文件17000+ 行),每个算法都有针对不同 NVIDIA SM 架构的特化参数。天垓100 不是 NVIDIA GPU不能直接用这些参数但 tuning 的维度block size、warp 策略、shared memory 用量、prefetch 策略是通用的。muh 让迁移过程变成"改配置 + 跑 benchmark"而不是"手改 kernel + 祈祷"。
**muh 的状态**: PRD 设计阶段,还没有代码
**muh 的状态**: v0.3 — 6个算法的C++ tuning headers已就绪(reduce/scan/topk/transform/batch_memcpy/for)compile_test 33项通过gen_patch.py从C++ headers提取bi100值生成vllm patches。参数值从CCCL SM100复制等BI-V100实测替换
## 四、已完成的工作
@@ -93,9 +93,9 @@ muh 是我们设计的 **tuning DSL领域特定语言**,用于:
## 五、还没做的(下一步)
1. **muh 语言 PRD 设计** — 定义 muh 的 schema、语法、codegen target、参数空间
2. **muh PRD items 写入 project 6** — 作为真实 Issue带 label 和测试用例
3. **从 CCCL tuning_*.cuh 提取参数空间** — 建立"CCCL tuning 维度 → muh 配置项"的映射
1. ~~muh 语言 PRD 设计~~ ✅ Done — muh是C++ header-only lib不是独立语言
2. ~~从 CCCL tuning_*.cuh 提取参数空间~~ ✅ Done — 6个算法的policy_selector已实现
3. **在BI-V100上跑benchmark** — 用实测数据替换bi100_*中的SM100复制值
4. **获取 enginex-vllm-bi100-qwen36 的实际代码** — 需要在 Phanthy Cloud 开发环境里操作
5. **设计 muh → vllm kernel 的 codegen 管道**
6. **实际在天垓100 上跑 benchmark**

View File

@@ -116,6 +116,36 @@ VLLM_INJECTION_POINTS = {
}
def extract_hardcoded_values(filepath):
"""Fallback: extract key values from policy_selector return statements.
For algorithms where bi100_* structs don't exist (values computed inline).
Extracts the threads_per_block from the first return in the iluvatar branch.
"""
with open(filepath, 'r') as f:
content = f.read()
algo = algo_from_filename(filepath)
# Find iluvatar branch
iluvatar_match = re.search(
r'hw\.at_least\(.*iluvatar.*?\)\s*\{(.*?)(?=\n\s{2,4}\})',
content, re.DOTALL
)
if not iluvatar_match:
return []
branch = iluvatar_match.group(1)
# Find return {N, ...} — first integer is typically threads_per_block
return_match = re.search(r'return\s*\{(\d+)', branch)
if not return_match:
return []
threads = int(return_match.group(1))
return [('__inline__', {'threads': threads})]
def generate_patches(header_dir):
"""Read all tuning headers, extract bi100 values, generate patches."""
patches = []
@@ -131,8 +161,11 @@ def generate_patches(header_dir):
structs = extract_bi100_structs(hpath)
if not structs:
summary.append(f"SKIP {algo}: no bi100_* structs found")
continue
# Fallback: try extracting inline values from policy_selector
structs = extract_hardcoded_values(hpath)
if not structs:
summary.append(f"SKIP {algo}: no bi100_* structs and no inline values found")
continue
# Use the first non-default struct as the primary tuning
# (default is fallback; prefer the type-specific ones)

View File

@@ -4,6 +4,12 @@
//
// vllm impact: KV cache block copy between GPU memory regions
// Competition weight: Cache TPS × 0.56
//
// CCCL structure: two-tier (SmallBuffer handled by single block,
// LargeBuffer by multi-block collaboration). Thresholds:
// warp_level: 128 bytes
// block_level: 8 KiB
// muh must replicate this structure, not flatten it.
#pragma once
@@ -12,51 +18,68 @@
namespace muh::tuning::batch_memcpy {
/// Batch memcpy policy
struct BatchMemcpyPolicy {
/// Small buffer policy: single thread block handles many small buffers
struct SmallBufferPolicy {
int threads_per_block;
int buffers_per_thread;
int bytes_per_thread;
bool prefer_pow2_bits;
int block_level_tile_size;
int warp_level_threshold;
int block_level_threshold;
LookbackDelayPolicy buffer_lookback_delay;
LookbackDelayPolicy block_lookback_delay;
};
// ============================================================
// BI-V100 tuning values
//
// CCCL reference from tuning_batch_memcpy.cuh:
// Default: threads=256
// Buffer lookback delay and block lookback delay are separate —
// they control the two decoupled lookback scans that coordinate
// the batch copy across thread blocks.
//
// For vllm: KV cache copies are typically large contiguous blocks
// (head_dim × num_layers × sizeof(half)), so high throughput matters
// more than latency.
// ============================================================
/// Large buffer policy: multiple blocks collaborate on one large buffer
struct LargeBufferPolicy {
int threads_per_block;
int bytes_per_thread;
};
struct bi100_default {
static constexpr int threads = 256;
static constexpr LookbackDelayPolicy buffer_delay = {
LookbackDelayAlgorithm::fixed_delay, 350, 450};
static constexpr LookbackDelayPolicy block_delay = {
LookbackDelayAlgorithm::fixed_delay, 350, 450};
/// Full batch memcpy policy
struct BatchMemcpyPolicy {
SmallBufferPolicy small_buffer;
LargeBufferPolicy large_buffer;
};
// ============================================================
// policy_selector
// BI-V100 tuning values — from CCCL SM70+ defaults
//
// CCCL policy_selector (all architectures):
// small: 128 threads, 4 bufs/thread, 8 bytes/thread
// prefer_pow2_bits = (cc < 7.0)
// warp_threshold = 128, block_threshold = 8192
// delays = default_delay_constructor_policy(true)
// large: 256 threads, 32 bytes/thread
//
// For BI-V100: start with CCCL defaults.
// The delay policy is arch-sensitive (CCCL uses
// default_delay_constructor_policy which picks fixed_delay for
// primitive types). We use fixed_delay as starting point.
// ============================================================
struct policy_selector {
constexpr BatchMemcpyPolicy operator()(const hardware_capability& hw) const {
if (hw.at_least(hardware_capability::vendor_t::iluvatar, 100)) {
return {bi100_default::threads,
bi100_default::buffer_delay,
bi100_default::block_delay};
}
// Fallback
return {256,
{LookbackDelayAlgorithm::fixed_delay, 350, 450},
{LookbackDelayAlgorithm::fixed_delay, 350, 450}};
constexpr BatchMemcpyPolicy operator()(const hardware_capability& hw) const {
// BI-V100: assume >= SM70 equivalent (no prefer_pow2_bits)
bool prefer_pow2 = false;
LargeBufferPolicy large{256, 32};
SmallBufferPolicy small{
/* threads_per_block = */ 128,
/* buffers_per_thread = */ 4,
/* bytes_per_thread = */ 8,
/* prefer_pow2_bits = */ prefer_pow2,
/* block_level_tile_size = */ large.threads_per_block * large.bytes_per_thread,
/* warp_level_threshold = */ 128,
/* block_level_threshold = */ 8 * 1024,
/* buffer_lookback_delay = */ {LookbackDelayAlgorithm::fixed_delay, 350, 450},
/* block_lookback_delay = */ {LookbackDelayAlgorithm::fixed_delay, 350, 450},
};
return {small, large};
}
};

View File

@@ -28,54 +28,75 @@ struct ReducePolicy {
ReducePassPolicy single_tile;
};
/// Determinism modes (mirrors cuda::execution::determinism::__determinism_t)
enum class determinism_t {
run_to_run, // default: WARP_REDUCTIONS + LOAD_LDG
gpu_to_gpu, // deterministic: RAKING + LOAD_DEFAULT
not_guaranteed, // nondeterministic: WARP_REDUCTIONS_NONDETERMINISTIC
};
// ============================================================
// BI-V100 tuning values
// Status: PENDING BENCHMARK
// BI-V100 tuning values — initialized from CCCL SM100 benchmarks
// Status: PENDING BI-V100 BENCHMARK (values will change)
//
// These are initialized from CCCL SM90/SM100 reference values.
// Must be replaced with actual BI-V100 benchmark results.
// CCCL SM100 benchmark results (from tuning_reduce.cuh):
// float32+o4: ipt=16, tpb=512, ipv=2 → 1.061x geo, 1.167x max
// float64+o4: ipt=16, tpb=640, ipv=1 → 1.018x geo, 1.057x max
// int64+o4: ipt=15, tpb=512, ipv=2 → 1.020x geo, 1.058x max
// int64+o8: ipt=15, tpb=512, ipv=1 → 1.019x geo, 1.057x max
//
// CCCL SM100 reference (for comparison):
// float32, offset_4: ipt=16, tpb=512, ipv=2 → 1.061x speedup
// float64, offset_4: ipt=16, tpb=640, ipv=1 → 1.018x speedup
// int64, offset_4: ipt=15, tpb=512, ipv=2 → 1.020x speedup
// int64, offset_8: ipt=15, tpb=512, ipv=1 → 1.019x speedup
// CCCL SM90 deterministic benchmark results:
// float32: ipt=13, tpb=224 → 1.107x geo, 1.317x max
// float64: ipt=11, tpb=128 → 1.232x geo, 1.582x max
// ============================================================
/// BI-V100 tuning for sum(float32), 4-byte offset
// --- Non-deterministic (default) tunings ---
struct bi100_float32_plus_o4 {
// Benchmark annotation format: ipt_N.tpb_M.ipv_K <geo> <min> <avg> <max>
// BI-V100: TBD — using SM100 reference as starting point
// BI-V100: TBD — SM100 ref: ipt_16.tpb_512.ipv_2 1.061 1.000 1.065 1.167
static constexpr int items = 16;
static constexpr int threads = 512;
static constexpr int items_per_vec_load = 2;
};
/// BI-V100 tuning for sum(float64), 4-byte offset
struct bi100_float64_plus_o4 {
// BI-V100: TBD
// BI-V100: TBD — SM100 ref: ipt_16.tpb_640.ipv_1 1.018 1.000 1.016 1.057
static constexpr int items = 16;
static constexpr int threads = 640;
static constexpr int items_per_vec_load = 1;
};
/// BI-V100 tuning for sum(int64), 4-byte offset
struct bi100_int64_plus_o4 {
// BI-V100: TBD
// BI-V100: TBD — SM100 ref: ipt_15.tpb_512.ipv_2 1.020 1.000 1.018 1.058
static constexpr int items = 15;
static constexpr int threads = 512;
static constexpr int items_per_vec_load = 2;
};
/// BI-V100 tuning for sum(int64), 8-byte offset
struct bi100_int64_plus_o8 {
// BI-V100: TBD
// BI-V100: TBD — SM100 ref: ipt_15.tpb_512.ipv_1 1.019 1.000 1.017 1.057
static constexpr int items = 15;
static constexpr int threads = 512;
static constexpr int items_per_vec_load = 1;
};
/// BI-V100 default fallback
// --- Deterministic tunings (BLOCK_REDUCE_RAKING) ---
// CCCL uses these when determinism == gpu_to_gpu
// vec_size is forced to 1 for deterministic reduction
struct bi100_det_float32 {
// BI-V100: TBD — SM90 ref: ipt_13.tpb_224 1.107 1.010 1.097 1.317
static constexpr int items = 13;
static constexpr int threads = 224;
};
struct bi100_det_float64 {
// BI-V100: TBD — SM86 ref: ipt_11.tpb_128 1.232 1.002 1.245 1.582
static constexpr int items = 11;
static constexpr int threads = 128;
};
/// Fallback for types without specific tuning
struct bi100_default {
static constexpr int items = 16;
static constexpr int threads = 256;
@@ -83,12 +104,12 @@ struct bi100_default {
};
// ============================================================
// policy_selector — the core dispatch functor
// Follows CCCL's exact pattern:
// 1. Accept hardware description
// 2. Match type/op/size to a tuning struct
// 3. Apply mem-bound scaling
// 4. Return ReducePolicy
// policy_selector
//
// Dispatch logic mirrors CCCL's exactly:
// 1. if determinism == gpu_to_gpu → get_deterministic_tuning()
// 2. else → get_two_phase_tuning()
// 3. if determinism == not_guaranteed → override reduce_algorithm
// ============================================================
struct policy_selector {
@@ -96,10 +117,35 @@ struct policy_selector {
op_kind_t operation_t;
int offset_size;
int accum_size;
determinism_t determinism = determinism_t::run_to_run;
/// Dispatch for BI-V100
constexpr ReducePolicy operator()(const hardware_capability& hw) const {
// Only tuned for sum currently (matching CCCL's approach)
/// Deterministic reduction: BLOCK_REDUCE_RAKING, vec_size=1, LOAD_DEFAULT
/// Matches CCCL get_deterministic_tuning()
constexpr ReducePolicy get_deterministic(const hardware_capability& hw) const {
if (hw.at_least(hardware_capability::vendor_t::iluvatar, 100)) {
if (accum_t == type_t::float32) {
auto [t, i] = 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) {
auto [t, i] = 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};
}
}
// Fallback deterministic
auto [t, i] = scale_mem_bound(256, 16, accum_size);
ReducePassPolicy rp{t, i, 1, BLOCK_REDUCE_RAKING, LOAD_DEFAULT};
return {rp, rp};
}
/// Standard two-phase reduction: BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG
/// Matches CCCL get_two_phase_tuning()
constexpr ReducePolicy get_two_phase(const hardware_capability& hw) const {
if (operation_t == op_kind_t::plus &&
hw.at_least(hardware_capability::vendor_t::iluvatar, 100)) {
@@ -140,13 +186,26 @@ struct policy_selector {
}
}
// Fallback: conservative policy
// Fallback: SM60-equivalent conservative policy
auto [t, i] = scale_mem_bound(
bi100_default::threads, bi100_default::items, accum_size);
ReducePassPolicy rp{t, i, bi100_default::items_per_vec_load,
BLOCK_REDUCE_WARP_REDUCTIONS, LOAD_LDG};
return {rp, rp};
}
/// Main dispatch — mirrors CCCL's operator()(compute_capability)
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

View File

@@ -4,6 +4,18 @@
//
// vllm impact: Top-k / top-p sampling in decode stage
// Competition weight: Output TPS × 16.796 (highest priority, tied with reduce)
//
// CCCL SM90+ policy_selector (the ground truth):
// bits_per_pass = calc_bits_per_pass(key_size):
// key_size 1 → 8
// key_size 2 → 8 (but note: CCCL default returns 11 for 2/4/8,
// only key_size=1 returns 8. See switch below.)
// key_size 4 → 11
// key_size 8 → 11
// items_per_thread = max(1, 4 * 4 / key_size) // 16 bytes per thread
// threads_per_block = 512
// load_algorithm = BLOCK_LOAD_VECTORIZE (NOT BLOCK_LOAD_DIRECT)
// scan_algorithm = BLOCK_SCAN_WARP_SCANS
#pragma once
@@ -12,7 +24,7 @@
namespace muh::tuning::topk {
/// Top-k policy (mirrors cub's topk_policy)
/// Top-k policy (mirrors cub::detail::topk::topk_policy)
struct TopkPolicy {
int threads_per_block;
int items_per_thread;
@@ -22,82 +34,68 @@ struct TopkPolicy {
};
// ============================================================
// BI-V100 tuning values
// bits_per_pass calculation — must match CCCL exactly
//
// CCCL reference from tuning_topk.cuh policy_selector:
// The CCCL topk uses radix-based selection with configurable bits_per_pass.
// Key insight: bits_per_pass trades off #passes vs per-pass work.
// For small key_size (1-2B): 4-6 bits optimal
// For large key_size (4-8B): 8-11 bits optimal
// CCCL source (tuning_topk.cuh):
// case 1: default: return 8;
// case 2: case 4: case 8: return 11;
//
// Default: threads=512, items=nominal_4b*4/key_size, bits=calc_bits_per_pass(key_size)
// calc_bits_per_pass: key_size<=2 → 8, key_size<=4 → 9, key_size<=8 → 10, else → 11
// Previous muh version had a wrong mapping:
// key_size<=2 → 8, key_size<=4 → 9, key_size<=8 → 10
// This was WRONG. CCCL's actual function returns 11 for 2/4/8.
// ============================================================
/// BI-V100 topk for 2-byte keys (float16/bfloat16 — most relevant for LLM logits)
struct bi100_topk_2B {
// LLM decode: logits are typically fp16/bf16, so key_size=2
// CCCL default for 2B: bits_per_pass=8, items=4*4/2=8
static constexpr int threads = 512;
static constexpr int items = 8; // nominal_4b_items=4, scaled: 4*4/2=8
static constexpr int bits_per_pass = 8;
static constexpr BlockLoadAlgorithm load_algo = BLOCK_LOAD_DIRECT;
static constexpr BlockScanAlgorithm scan_algo = BLOCK_SCAN_WARP_SCANS;
};
/// BI-V100 topk for 4-byte keys (float32)
struct bi100_topk_4B {
static constexpr int threads = 512;
static constexpr int items = 4; // nominal_4b_items=4, scaled: 4*4/4=4
static constexpr int bits_per_pass = 9;
static constexpr BlockLoadAlgorithm load_algo = BLOCK_LOAD_DIRECT;
static constexpr BlockScanAlgorithm scan_algo = BLOCK_SCAN_WARP_SCANS;
};
/// BI-V100 default fallback
struct bi100_topk_default {
static constexpr int threads = 256;
static constexpr int items = 4;
static constexpr int bits_per_pass = 8;
static constexpr BlockLoadAlgorithm load_algo = BLOCK_LOAD_DIRECT;
static constexpr BlockScanAlgorithm scan_algo = BLOCK_SCAN_WARP_SCANS;
};
constexpr int calc_bits_per_pass(int key_size) {
switch (key_size) {
case 1:
default:
return 8;
case 2:
case 4:
case 8:
return 11;
}
}
// ============================================================
// policy_selector
//
// Matches CCCL's SM90+ path exactly:
// threads = 512
// items = max(1, nominal_4b(4) * 4 / key_size)
// load = BLOCK_LOAD_VECTORIZE
// scan = BLOCK_SCAN_WARP_SCANS
// bits = calc_bits_per_pass(key_size)
//
// BI-V100 values: using SM100 as starting point.
// Once benchmarked on BI-V100, items/threads may diverge.
// ============================================================
struct policy_selector {
int key_size;
static constexpr int calc_bits_per_pass(int ks) {
if (ks <= 2) return 8;
if (ks <= 4) return 9;
if (ks <= 8) return 10;
return 11;
}
constexpr TopkPolicy operator()(const hardware_capability& hw) const {
if (hw.at_least(hardware_capability::vendor_t::iluvatar, 100)) {
switch (key_size) {
case 1:
case 2:
return {bi100_topk_2B::threads, bi100_topk_2B::items,
bi100_topk_2B::load_algo, bi100_topk_2B::scan_algo,
bi100_topk_2B::bits_per_pass};
case 4:
return {bi100_topk_4B::threads, bi100_topk_4B::items,
bi100_topk_4B::load_algo, bi100_topk_4B::scan_algo,
bi100_topk_4B::bits_per_pass};
}
// SM90+ path from CCCL: 16 bytes per thread
constexpr int nominal_4b_items = 4;
int items = nominal_4b_items * 4 / key_size;
if (items < 1) items = 1;
return {512, items,
BLOCK_LOAD_VECTORIZE, // CCCL uses VECTORIZE, not DIRECT
BLOCK_SCAN_WARP_SCANS,
calc_bits_per_pass(key_size)};
}
// Fallback
int items = (4 * 4) / key_size;
// Fallback: older arch path
constexpr int nominal_4b_items = 4;
int items = nominal_4b_items * 4 / key_size;
if (items < 1) items = 1;
if (items > 4) items = 4;
return {bi100_topk_default::threads, items,
bi100_topk_default::load_algo, bi100_topk_default::scan_algo,
if (items > nominal_4b_items) items = nominal_4b_items;
return {512, items,
BLOCK_LOAD_VECTORIZE,
BLOCK_SCAN_WARP_SCANS,
calc_bits_per_pass(key_size)};
}
};

View File

@@ -4,6 +4,14 @@
//
// vllm impact: Activation functions (SiLU, GELU), RMSNorm, residual add
// Competition weight: Output TPS × 16.796
//
// CCCL structure: three transform policy types selected by iterator properties:
// 1. TransformVectorizedPolicy — contiguous + trivially_relocatable inputs
// 2. TransformAsyncCopyPolicy — SM90+ bulk copy (cp.async.bulk)
// 3. TransformPrefetchPolicy — fallback when stable_address needed
//
// For vllm: activations are contiguous dense fp16/bf16 tensors.
// → primarily hits the vectorized path.
#pragma once
@@ -12,86 +20,103 @@
namespace muh::tuning::transform {
/// Bulk transform policy (for contiguous input/output)
struct BulkPolicy {
/// Vectorized transform: coalesced loads via vector types
struct VectorizedPolicy {
int threads_per_block;
int items_per_thread;
int vec_size; // vectorization width
int vec_size;
};
/// No-input policy (for fill/memset operations)
/// Async copy transform: SM90+ bulk shared memory copy
struct AsyncCopyPolicy {
int threads_per_block;
int min_items_per_thread;
int store_vec_size;
};
/// Prefetch transform: prefetch-based fallback
struct PrefetchPolicy {
int threads_per_block;
};
/// Fill policy (no input, e.g. memset)
struct FillPolicy {
int threads_per_block;
int items_per_thread_no_input;
int items_per_thread;
};
/// Full transform policy
/// Full transform policy — selected at dispatch time based on iterator properties
struct TransformPolicy {
BulkPolicy bulk;
VectorizedPolicy vectorized;
AsyncCopyPolicy async_copy;
PrefetchPolicy prefetch;
FillPolicy fill;
};
// ============================================================
// BI-V100 tuning values
// BI-V100 tuning
//
// CCCL reference from tuning_transform.cuh:
// Default bulk: threads=256, items=auto, vec_size=auto
// The transform kernel is bandwidth-bound for large elementwise ops.
// Key insight: vec_size should match the hardware's natural vector width.
// For NVIDIA: vec_size is typically 4 (128-bit loads).
// For BI-V100: TBD, likely also 4 or 8.
// CCCL SM90+ vectorized path:
// threads = 256 (SM90) or 128 (SM100)
// items = computed from: max(items_for_vec, items_for_latency)
// items_for_vec = ceil(vec_bytes / min_elem_size)
// items_for_latency = (min_bytes_in_flight) / (threads * elem_size)
// vec_size = auto (power-of-2 aligned to hardware vector width)
//
// items_per_thread is computed as:
// items_for_vec = ceil(vector_bytes / min_elem_size)
// items_for_latency = (latency * bandwidth) / (threads * elem_size)
// items = max(items_for_vec, items_for_latency)
// ============================================================
struct bi100_bulk_default {
static constexpr int threads = 256;
static constexpr int items = 8; // conservative starting point
static constexpr int vec_size = 4; // 128-bit vector loads
};
struct bi100_fill_default {
static constexpr int threads = 256;
static constexpr int items = 2;
};
// ============================================================
// policy_selector
// CCCL SM90+ async_copy path:
// threads = 256 (SM90) or 128 (SM100)
// min_items_per_thread = computed from SMEM capacity
// store_vec_size = auto_ublkcp_store_vec_size(output.value_type_size)
//
// CCCL prefetch:
// threads = 256
//
// For BI-V100: start with SM100 values (128 threads for bulk/async,
// 256 for prefetch). vec_size = 4 (128-bit loads, standard for most GPUs).
// ============================================================
struct policy_selector {
int min_elem_size; // minimum element size across all inputs
int max_elem_size; // maximum element size
int num_inputs; // number of input iterators
int min_elem_size;
int max_elem_size;
int num_inputs;
bool all_contiguous;
bool all_trivially_relocatable;
bool requires_stable_address;
constexpr TransformPolicy operator()(const hardware_capability& hw) const {
if (hw.at_least(hardware_capability::vendor_t::iluvatar, 100)) {
// For BI-V100: start with CCCL defaults, tune via benchmark
int vec_size = bi100_bulk_default::vec_size;
// Compute vectorization params
int vec_size = 4; // 128-bit, matches CCCL default
// Compute items_per_thread based on vectorization
int items_for_vec = (vec_size * 4) / min_elem_size; // 4 = sizeof(int)
if (items_for_vec < 1) items_for_vec = 1;
// items_for_vec: how many items fit in one vector load
int items_for_vec = (vec_size * 4) / min_elem_size; // 4 = sizeof(int)
if (items_for_vec < 1) items_for_vec = 1;
// Ensure items is a multiple of vec_size
int items = items_for_vec;
if (items % vec_size != 0) {
items = ((items / vec_size) + 1) * vec_size;
}
// items_for_latency: enough items to hide memory latency
// CCCL uses cc_to_min_bytes_in_flight(cc) which is ~48KB for SM90+
// For BI-V100: estimate 48KB in flight, 256 threads
int bytes_in_flight = 48 * 1024;
int items_for_latency = bytes_in_flight / (256 * min_elem_size);
if (items_for_latency < 1) items_for_latency = 1;
return {
{bi100_bulk_default::threads, items, vec_size},
{bi100_fill_default::threads, bi100_fill_default::items}
};
int bulk_items = items_for_vec > items_for_latency ? items_for_vec : items_for_latency;
// Ensure items is a multiple of vec_size for aligned access
if (bulk_items % vec_size != 0) {
bulk_items = ((bulk_items / vec_size) + 1) * vec_size;
}
// Fallback
// BI-V100: 128 threads for bulk (SM100-like), 256 for prefetch
int bulk_threads = hw.at_least(hardware_capability::vendor_t::iluvatar, 100) ? 128 : 256;
return {
{bi100_bulk_default::threads, bi100_bulk_default::items, bi100_bulk_default::vec_size},
{bi100_fill_default::threads, bi100_fill_default::items}
// vectorized
{bulk_threads, bulk_items, vec_size},
// async_copy (BI-V100 may not support cp.async.bulk — conservative)
{bulk_threads, 4, vec_size},
// prefetch
{256},
// fill
{256, 2},
};
}
};

View File

@@ -1,10 +1,5 @@
// muh/test/compile_test.cpp — Compile-time verification of muh tuning headers
//
// This test does NOT require a GPU. It verifies:
// 1. All headers parse without errors
// 2. All policy_selector functors instantiate and return valid policies
// 3. All bi100_* struct values are non-zero (not forgotten placeholders)
//
// Build: g++ -std=c++17 -I muh/include muh/test/compile_test.cpp -o muh_test
// Run: ./muh_test
@@ -13,125 +8,140 @@
#include <cassert>
#include <cstdio>
// Helper: verify a value is non-zero (catches forgotten TBD placeholders)
#define CHECK_NONZERO(expr, name) \
do { \
auto _v = (expr); \
if (_v == 0) { \
std::fprintf(stderr, "FAIL: %s == 0 (placeholder not filled)\n", name); \
failures++; \
} else { \
passes++; \
} \
} while(0)
do { auto _v = (expr); if (_v == 0) { std::fprintf(stderr, "FAIL: %s == 0\n", name); failures++; } else { passes++; } } while(0)
#define CHECK_EQ(expr, expected, name) \
do { auto _v = (expr); if (_v != (expected)) { std::fprintf(stderr, "FAIL: %s == %d, expected %d\n", name, (int)_v, (int)(expected)); failures++; } else { passes++; } } while(0)
#define CHECK_TRUE(expr, name) \
do { \
if (!(expr)) { \
std::fprintf(stderr, "FAIL: %s\n", name); \
failures++; \
} else { \
passes++; \
} \
} while(0)
do { if (!(expr)) { std::fprintf(stderr, "FAIL: %s\n", name); failures++; } else { passes++; } } while(0)
int main() {
using namespace muh::tuning; // bring enum values into scope
int passes = 0;
int failures = 0;
auto hw = muh::target_hw;
// --- Verify hardware descriptor ---
CHECK_TRUE(hw.vendor == muh::hardware_capability::vendor_t::iluvatar,
"target_hw.vendor == iluvatar");
// --- Hardware descriptor ---
CHECK_TRUE(hw.vendor == muh::hardware_capability::vendor_t::iluvatar, "target_hw.vendor");
CHECK_NONZERO(hw.warp_size, "target_hw.warp_size");
CHECK_NONZERO(hw.max_threads_per_block, "target_hw.max_threads_per_block");
// --- Test reduce policy_selector ---
// --- reduce: default (run_to_run) ---
{
using namespace muh::tuning::reduce;
auto ps = policy_selector{
.accum_t = muh::tuning::type_t::float32,
.operation_t = muh::tuning::op_kind_t::plus,
.offset_size = 4,
.accum_size = 4,
.offset_size = 4, .accum_size = 4,
};
auto policy = ps(hw);
CHECK_NONZERO(policy.multi_tile.threads_per_block,
"reduce.float32.threads_per_block");
CHECK_NONZERO(policy.multi_tile.items_per_thread,
"reduce.float32.items_per_thread");
CHECK_NONZERO(policy.multi_tile.vec_size,
"reduce.float32.vec_size");
// Verify known bi100 value matches
CHECK_TRUE(policy.multi_tile.threads_per_block > 0 &&
policy.multi_tile.threads_per_block <= 1024,
"reduce.threads_per_block in [1, 1024]");
auto p = ps(hw);
CHECK_EQ(p.multi_tile.threads_per_block, 512, "reduce.f32.threads");
CHECK_EQ(p.multi_tile.vec_size, 2, "reduce.f32.vec_size");
CHECK_EQ(p.multi_tile.reduce_algorithm, BLOCK_REDUCE_WARP_REDUCTIONS, "reduce.f32.algo");
}
// --- Test topk policy_selector ---
// --- reduce: deterministic (gpu_to_gpu) ---
{
using namespace muh::tuning::reduce;
auto ps = policy_selector{
.accum_t = muh::tuning::type_t::float32,
.operation_t = muh::tuning::op_kind_t::plus,
.offset_size = 4, .accum_size = 4,
.determinism = determinism_t::gpu_to_gpu,
};
auto p = ps(hw);
CHECK_EQ(p.multi_tile.reduce_algorithm, BLOCK_REDUCE_RAKING, "reduce.det.algo=RAKING");
CHECK_EQ(p.multi_tile.vec_size, 1, "reduce.det.vec_size=1");
CHECK_EQ(p.multi_tile.load_modifier, LOAD_DEFAULT, "reduce.det.load=DEFAULT");
}
// --- reduce: nondeterministic ---
{
using namespace muh::tuning::reduce;
auto ps = policy_selector{
.accum_t = muh::tuning::type_t::float32,
.operation_t = muh::tuning::op_kind_t::plus,
.offset_size = 4, .accum_size = 4,
.determinism = determinism_t::not_guaranteed,
};
auto p = ps(hw);
CHECK_EQ(p.multi_tile.reduce_algorithm, BLOCK_REDUCE_WARP_REDUCTIONS_NONDETERMINISTIC,
"reduce.nondet.algo=NONDETERMINISTIC");
}
// --- topk: verify VECTORIZE and correct bits_per_pass ---
{
using namespace muh::tuning::topk;
auto ps = policy_selector{.key_size = 2};
auto policy = ps(hw);
CHECK_NONZERO(policy.threads_per_block, "topk.2B.threads_per_block");
CHECK_NONZERO(policy.items_per_thread, "topk.2B.items_per_thread");
CHECK_NONZERO(policy.bits_per_pass, "topk.2B.bits_per_pass");
CHECK_TRUE(policy.bits_per_pass >= 4 && policy.bits_per_pass <= 11,
"topk.bits_per_pass in [4, 11]");
// 2-byte keys (fp16 logits — LLM hot path)
auto p2 = policy_selector{.key_size = 2}(hw);
CHECK_EQ(p2.load_algorithm, BLOCK_LOAD_VECTORIZE, "topk.2B.load=VECTORIZE");
CHECK_EQ(p2.bits_per_pass, 11, "topk.2B.bits=11"); // CCCL: case 2 → 11
CHECK_EQ(p2.items_per_thread, 8, "topk.2B.items=8"); // 4*4/2=8
CHECK_EQ(p2.threads_per_block, 512, "topk.2B.threads=512");
// 4-byte keys
auto p4 = policy_selector{.key_size = 4}(hw);
CHECK_EQ(p4.bits_per_pass, 11, "topk.4B.bits=11");
CHECK_EQ(p4.items_per_thread, 4, "topk.4B.items=4"); // 4*4/4=4
// 1-byte keys
auto p1 = policy_selector{.key_size = 1}(hw);
CHECK_EQ(p1.bits_per_pass, 8, "topk.1B.bits=8");
CHECK_EQ(p1.items_per_thread, 16, "topk.1B.items=16"); // 4*4/1=16
}
// --- Test scan policy_selector ---
// --- scan: lookback + lookahead ---
{
using namespace muh::tuning::scan;
auto ps = policy_selector{
.input_value_size = 4,
.accum_size = 4,
.offset_size = 4,
.input_value_size = 4, .accum_size = 4, .offset_size = 4,
.input_type = muh::tuning::type_t::float32,
.accum_type = muh::tuning::type_t::float32,
.operation_t = muh::tuning::op_kind_t::plus,
.is_primitive_accum = true,
};
auto policy = ps(hw);
CHECK_NONZERO(policy.lookback.threads_per_block,
"scan.float32.lookback.threads_per_block");
CHECK_NONZERO(policy.lookback.items_per_thread,
"scan.float32.lookback.items_per_thread");
auto p = ps(hw);
CHECK_EQ(p.lookback.threads_per_block, 384, "scan.f32.lookback.threads=384");
CHECK_EQ(p.lookback.items_per_thread, 22, "scan.f32.lookback.items=22");
CHECK_NONZERO(p.lookahead.reduce_and_scan_warps, "scan.f32.lookahead.warps");
}
// --- Test transform policy_selector ---
// --- batch_memcpy: two-tier ---
{
using namespace muh::tuning::batch_memcpy;
auto p = policy_selector{}(hw);
CHECK_EQ(p.small_buffer.threads_per_block, 128, "batch_memcpy.small.threads=128");
CHECK_EQ(p.small_buffer.buffers_per_thread, 4, "batch_memcpy.small.bufs=4");
CHECK_EQ(p.small_buffer.warp_level_threshold, 128, "batch_memcpy.small.warp_thresh=128");
CHECK_EQ(p.small_buffer.block_level_threshold, 8192, "batch_memcpy.small.block_thresh=8192");
CHECK_EQ(p.large_buffer.threads_per_block, 256, "batch_memcpy.large.threads=256");
CHECK_EQ(p.large_buffer.bytes_per_thread, 32, "batch_memcpy.large.bytes=32");
}
// --- transform: three-policy ---
{
using namespace muh::tuning::transform;
auto ps = policy_selector{
.min_elem_size = 2,
.max_elem_size = 2,
.num_inputs = 1,
.min_elem_size = 2, .max_elem_size = 2, .num_inputs = 1,
.all_contiguous = true, .all_trivially_relocatable = true,
.requires_stable_address = false,
};
auto policy = ps(hw);
CHECK_NONZERO(policy.bulk.threads_per_block,
"transform.bulk.threads_per_block");
auto p = ps(hw);
CHECK_NONZERO(p.vectorized.threads_per_block, "transform.vectorized.threads");
CHECK_NONZERO(p.vectorized.vec_size, "transform.vectorized.vec_size");
CHECK_NONZERO(p.async_copy.threads_per_block, "transform.async_copy.threads");
CHECK_EQ(p.prefetch.threads_per_block, 256, "transform.prefetch.threads=256");
CHECK_EQ(p.fill.threads_per_block, 256, "transform.fill.threads=256");
}
// --- Test batch_memcpy policy_selector ---
{
using namespace muh::tuning::batch_memcpy;
auto ps = policy_selector{};
auto policy = ps(hw);
CHECK_NONZERO(policy.threads_per_block,
"batch_memcpy.threads_per_block");
}
// --- Test for_each policy_selector ---
// --- for_each ---
{
using namespace muh::tuning::for_each;
auto ps = policy_selector{};
auto policy = ps(hw);
CHECK_NONZERO(policy.threads_per_block,
"for_each.threads_per_block");
CHECK_NONZERO(policy.items_per_thread,
"for_each.items_per_thread");
auto p = policy_selector{}(hw);
CHECK_EQ(p.threads_per_block, 256, "for.threads=256");
CHECK_EQ(p.items_per_thread, 4, "for.items=4");
}
// --- Report ---