Files
project_6_baseline5/muh_cc_dispatch.py

398 lines
16 KiB
Python
Raw Permalink 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_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}")