Files
project_6/muh_cc_dispatch.py

398 lines
16 KiB
Python
Raw Normal View History

"""
muh_cc_dispatch.py Unified kernel policy dispatch for BI-V100
================================================================
Python port of CCCL's cc_dispatch.cuh architecture.
CCCL dispatch pattern (cc_dispatch.cuh):
dispatch_compute_cap(policy_selector, device_cc, functor)
policy_getter<PolicySelector, CC>{}()
concrete policy struct (ReducePolicy, ScanPolicy, etc.)
Our equivalent:
dispatch_kernel_config(hardware, kernel_name, **kwargs)
policy_for_kernel(kernel_name, hardware, dtype, ...)
concrete config dict (threads, items, block_sizes, etc.)
Key insight from cc_dispatch.cuh line 62 (lowest_cc_resolver):
CCCL collapses architectures with identical policies if SM80 and SM86
produce the same ReducePolicy, only one kernel instantiation is generated.
Our equivalent: pre-compute all configs at import time (see bottom of file)
so dispatch is a dict lookup, not a function call.
Key insight from dispatch_reduce.cuh line 490:
dispatch_compute_cap is called ONCE per DeviceReduce invocation.
The policy is then threaded through InvokeSingleTile / InvokePasses.
Our equivalent: dispatch_kernel_config returns a frozen config dict
that's threaded through the entire kernel call chain.
CCCL source files that informed this design:
cub/detail/cc_dispatch.cuh dispatch mechanism
cub/device/dispatch/dispatch_reduce.cuh reduce two-path dispatch
cub/device/dispatch/dispatch_transform.cuh transform spread_out_items
cub/device/dispatch/dispatch_topk.cuh topk radix select
cub/device/dispatch/dispatch_common.cuh shared enums
cub/grid/grid_even_share.cuh work distribution
cub/agent/agent_reduce.cuh tile consumption patterns
thrust/examples/summary_statistics.cu compound reduce pattern
"""
from dataclasses import dataclass, field
from typing import Dict, Optional, Any
import math
# ══════════════════════════════════════════════════════════════
# Hardware descriptor (mirrors muh/include/muh/hardware.cuh)
# ══════════════════════════════════════════════════════════════
@dataclass(frozen=True)
class HardwareCapability:
"""Mirrors muh::hardware_capability from hardware.cuh."""
vendor: str = "iluvatar"
arch_version: int = 100
warp_size: int = 32
max_threads_per_block: int = 1024
max_shared_memory_per_block: int = 49152 # 48KB confirmed via ixsmi
max_registers_per_thread: int = 255
l2_cache_size_bytes: int = 6 * 1024 * 1024 # 6MB
memory_bandwidth_gbps: int = 900
sm_count: int = 16 # CONFIRMED: 16 SMs, NOT 50
@property
def bandwidth_per_sm_gbps(self) -> float:
return self.memory_bandwidth_gbps / self.sm_count
@property
def bytes_in_flight(self) -> int:
"""Optimal bytes in flight per SM.
From CCCL tuning_transform.cuh cc_to_min_bytes_in_flight:
V100=12KB, A100=16KB, H100=48KB, B200=64KB
BI-V100 per-SM BW = 900/16 = 56 GB/s B200 level 64KB
Confirmed by bench_bi100.py: bif=8 (64KB) wins at all sizes.
"""
return 64 * 1024
def at_least(self, vendor: str, min_arch: int) -> bool:
"""Mirrors hardware_capability::at_least()."""
return self.vendor == vendor and self.arch_version >= min_arch
BI_V100 = HardwareCapability()
# ══════════════════════════════════════════════════════════════
# Kernel configuration structs
# (mirrors CCCL's ReducePolicy, ScanPolicy, TopkPolicy, etc.)
# ══════════════════════════════════════════════════════════════
@dataclass(frozen=True)
class AttentionConfig:
"""Config for paged attention V1/V2 + prefix prefill.
Dispatch axes (from muh_kernel_map.py VLLM_KERNEL_MAP):
paged_attention_v1: reduce (score reduction per head)
paged_attention_v2: reduce + scan (partitioned reduce + merge)
context_attention_fwd: scan + reduce + transform (Triton prefill)
"""
# Triton flash attention (prefill)
triton_block_m: int = 32
triton_block_n: int = 32
triton_num_warps: int = 4
triton_num_stages: int = 1
# Paged attention (decode)
partition_size: int = 512
v1_v2_threshold: int = 8192
# PyTorch fallback (long decode)
pytorch_decode_threshold: int = 32768
pytorch_max_tile_blocks: int = 1024
# Backend selection
use_native_v1: bool = True
use_native_v2: bool = False
@dataclass(frozen=True)
class MoEConfig:
"""Config for fused MoE kernel.
Maps to fused_moe_kernel's tl.constexpr parameters.
Critical for Qwen3.6: 256 experts, top-8, 64 layers.
"""
block_size_m: int = 64
block_size_n: int = 64
block_size_k: int = 32
group_size_m: int = 8
@dataclass(frozen=True)
class TransformConfig:
"""Config for element-wise ops (SiLU, RMSNorm, RoPE).
From CCCL dispatch_transform.cuh spread_out_items_per_thread:
items = ceil_div(num_items, sm_count × threads × max_occupancy)
clamped to [min_items, max_items]
"""
bytes_in_flight: int = 64 * 1024 # 64KB for BI-V100
# These are used by ixformer native kernels (not directly tunable)
# but inform our SMEM budget calculations
max_smem_per_block: int = 49152
@dataclass(frozen=True)
class CacheConfig:
"""Config for KV cache operations (copy, swap, reshape_and_cache)."""
copy_block_size: int = 256
# ══════════════════════════════════════════════════════════════
# CCCL-style SMEM constraint checker
# (from muh_kernel_map.py check_smem, used across all policies)
# ══════════════════════════════════════════════════════════════
def check_smem(threads: int, items: int, elem_bytes: int,
smem_limit: int = BI_V100.max_shared_memory_per_block) -> dict:
"""Verify tile fits in shared memory. Used by all policy selectors."""
tile_bytes = threads * items * elem_bytes
max_items = smem_limit // (threads * elem_bytes) if threads * elem_bytes > 0 else 0
return {
"tile_bytes": tile_bytes,
"fits": tile_bytes <= smem_limit,
"utilization": tile_bytes / smem_limit if smem_limit > 0 else 0,
"max_items": max_items,
}
# ══════════════════════════════════════════════════════════════
# CCCL GridEvenShare work distribution (grid_even_share.cuh)
# ══════════════════════════════════════════════════════════════
def grid_even_share(num_items: int, tile_size: int,
sm_count: int = BI_V100.sm_count,
subscription_factor: int = 5) -> dict:
"""Python port of GridEvenShare::DispatchInit.
CCCL formula: max_blocks = sm_occupancy × sm_count × subscription_factor
Then items are evenly distributed across blocks, with 'big' blocks
getting one extra tile.
"""
if num_items <= 0 or tile_size <= 0:
return {"grid_size": 0, "total_tiles": 0}
total_tiles = math.ceil(num_items / tile_size)
max_grid_size = sm_count * subscription_factor # ~80 for BI-V100
grid_size = min(total_tiles, max_grid_size)
avg_tiles = total_tiles // grid_size if grid_size > 0 else 0
big_shares = total_tiles - (avg_tiles * grid_size) if grid_size > 0 else 0
return {
"grid_size": grid_size,
"total_tiles": total_tiles,
"avg_tiles_per_block": avg_tiles,
"big_shares": big_shares,
"tile_size": tile_size,
}
# ══════════════════════════════════════════════════════════════
# Policy selectors (mirrors each algorithm's policy_selector)
# ══════════════════════════════════════════════════════════════
def select_attention_config(
hw: HardwareCapability,
dtype_size: int, # element size in bytes (2=fp16, 4=fp32)
head_dim: int,
max_seq_len: int,
num_kv_heads: int,
) -> AttentionConfig:
"""CCCL-style policy selector for paged attention.
Mirrors: dispatch_reduce.cuh two-path dispatch
single-tile: num_items threads × items V1 (one CTA)
multi-tile: GridEvenShare V2 (partitioned + merge)
SMEM constraint for Triton prefill:
SMEM = BLOCK_N × head_dim × elem_size × 2 (K + V staging)
"""
smem = hw.max_shared_memory_per_block
# Triton BLOCK_N: largest that fits SMEM
triton_block_n = 64
while triton_block_n * head_dim * dtype_size * 2 > smem and triton_block_n > 16:
triton_block_n //= 2
triton_block_m = triton_block_n
triton_num_warps = 4
triton_num_stages = 1 # no async copy on BI-V100
# V1/V2 threshold: V2 worthwhile when partitions > 1 AND
# per-partition work exceeds merge overhead
v1_threshold = 8192
# PyTorch decode threshold: fall back for seq_len > this
# (ixformer V1 hangs on very long sequences)
pytorch_threshold = 32768
# Tile blocks for PyTorch decode: from GridEvenShare
# max_blocks = sm_count × subscription_factor = 80
# Each tile processes ~16K tokens (1024 blocks × block_size=16)
pytorch_tile_blocks = 1024
return AttentionConfig(
triton_block_m=triton_block_m,
triton_block_n=triton_block_n,
triton_num_warps=triton_num_warps,
triton_num_stages=triton_num_stages,
partition_size=512,
v1_v2_threshold=v1_threshold,
pytorch_decode_threshold=pytorch_threshold,
pytorch_max_tile_blocks=pytorch_tile_blocks,
use_native_v1=True,
use_native_v2=False,
)
def select_moe_config(
hw: HardwareCapability,
num_experts: int,
top_k: int,
hidden_size: int,
intermediate_size: int,
) -> MoEConfig:
"""Policy selector for fused MoE.
Qwen3.6: 256 experts, top-8, hidden=3584, intermediate=18944
CCCL parallel: each expert is an independent reduce domain.
With 256 experts × top-8 × batch=1 8 active experts per token.
BI-V100 16 SMs can run 8 expert-matmuls in parallel one wave.
"""
# BLOCK_SIZE_M: tokens per tile. For decode (M=1), smallest possible.
# For prefill (M=4096), larger is better to amortize overhead.
block_m = 64 if top_k * 1 >= 64 else 32 # decode: top_k tokens
block_n = 64
block_k = 32
# SMEM check: A_tile + B_tile
# A: block_m × block_k × 2 bytes = 64×32×2 = 4KB
# B: block_k × block_n × 2 bytes = 32×64×2 = 4KB
# Total: 8KB << 48KB ✓
return MoEConfig(
block_size_m=block_m,
block_size_n=block_n,
block_size_k=block_k,
group_size_m=8,
)
# ══════════════════════════════════════════════════════════════
# Pre-computed configs (mirrors CCCL compile-time instantiation)
#
# cc_dispatch.cuh line 62: lowest_cc_resolver collapses identical
# policies across CCs. Our equivalent: compute once at import time.
# ══════════════════════════════════════════════════════════════
# Qwen3.6-35B-A3B model parameters (confirmed from qwen3_5.py)
QWEN36_HEAD_DIM = 256 # text_cfg.head_dim
QWEN36_NUM_KV_HEADS = 4 # num_key_value_heads
QWEN36_MAX_SEQ_LEN = 100000 # from computility-run.yaml
QWEN36_MAX_NUM_SEQS = 1 # CRITICAL: computility-run.yaml --max-num-seqs 1
QWEN36_NUM_EXPERTS = 256 # MoE experts
QWEN36_TOP_K = 8 # MoE top-k
QWEN36_HIDDEN = 3584 # hidden_size
QWEN36_INTERMEDIATE = 18944 # intermediate_size
# Pre-computed for fp16 (the common dtype on BI-V100)
ATTENTION_FP16 = select_attention_config(
hw=BI_V100,
dtype_size=2,
head_dim=QWEN36_HEAD_DIM,
max_seq_len=QWEN36_MAX_SEQ_LEN,
num_kv_heads=QWEN36_NUM_KV_HEADS,
)
# Pre-computed for bf16
ATTENTION_BF16 = select_attention_config(
hw=BI_V100,
dtype_size=2, # bf16 same size as fp16
head_dim=QWEN36_HEAD_DIM,
max_seq_len=QWEN36_MAX_SEQ_LEN,
num_kv_heads=QWEN36_NUM_KV_HEADS,
)
MOE_CONFIG = select_moe_config(
hw=BI_V100,
num_experts=QWEN36_NUM_EXPERTS,
top_k=QWEN36_TOP_K,
hidden_size=QWEN36_HIDDEN,
intermediate_size=QWEN36_INTERMEDIATE,
)
TRANSFORM_CONFIG = TransformConfig(
bytes_in_flight=BI_V100.bytes_in_flight,
max_smem_per_block=BI_V100.max_shared_memory_per_block,
)
CACHE_CONFIG = CacheConfig(copy_block_size=256)
# ══════════════════════════════════════════════════════════════
# Unified dispatch entry point
# (mirrors CCCL dispatch_compute_cap)
# ══════════════════════════════════════════════════════════════
_CONFIGS = {
"attention": ATTENTION_FP16,
"attention_fp16": ATTENTION_FP16,
"attention_bf16": ATTENTION_BF16,
"moe": MOE_CONFIG,
"transform": TRANSFORM_CONFIG,
"cache": CACHE_CONFIG,
}
def dispatch_kernel_config(kernel_name: str,
hw: HardwareCapability = BI_V100) -> Any:
"""Unified policy dispatch — Python equivalent of dispatch_compute_cap.
Usage:
config = dispatch_kernel_config("attention")
# config.triton_block_m, config.partition_size, etc.
config = dispatch_kernel_config("moe")
# config.block_size_m, config.block_size_n, etc.
"""
if kernel_name not in _CONFIGS:
raise KeyError(
f"Unknown kernel: {kernel_name}. "
f"Available: {list(_CONFIGS.keys())}"
)
return _CONFIGS[kernel_name]
# ══════════════════════════════════════════════════════════════
# CLI: dump all configs for inspection
# ══════════════════════════════════════════════════════════════
if __name__ == "__main__":
print("muh_cc_dispatch: CCCL-style unified kernel policy dispatch\n")
print(f"Hardware: {BI_V100.vendor} BI-V100")
print(f" SMs: {BI_V100.sm_count}, SMEM: {BI_V100.max_shared_memory_per_block//1024}KB, "
f"BW: {BI_V100.memory_bandwidth_gbps}GB/s, "
f"BW/SM: {BI_V100.bandwidth_per_sm_gbps:.1f}GB/s")
print(f" bytes_in_flight: {BI_V100.bytes_in_flight//1024}KB\n")
for name, config in _CONFIGS.items():
print(f"[{name}]")
for k, v in config.__dict__.items():
if not k.startswith('_'):
print(f" {k}: {v}")
print()
# GridEvenShare example for decode
print("GridEvenShare example (50K token decode, block_size=16):")
es = grid_even_share(50000 // 16, tile_size=1024)
for k, v in es.items():
print(f" {k}: {v}")