Files
project_6/muh/gen_config.py
dylanyunlon 5c05a03470 feat: add gen_config.py (Python-layer config generator) + pipeline reality check
gen_config.py replaces gen_patch.py's dead csrc/*.cu injection path.
Generates Triton autotune configs derived from CCCL tuning principles:
- SMEM constraints (Q_tile + K_tile <= 48KB for head_dim=256)
- Occupancy model (16 SMs, register pressure per config)
- bytes_in_flight (56 GB/s per-SM -> 64KB -> num_stages=2)

63 valid configs from 2304 combinations, 19 new.

PIPELINE_REALITY_CHECK.md: enginex has no .cu source.
All injection targets are Python/Triton, not C++.
2026-08-07 03:11:56 +00:00

352 lines
14 KiB
Python
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.

#!/usr/bin/env python3
"""muh/gen_config.py — Generate Python-layer config patches from CCCL tuning analysis
Unlike gen_patch.py (which targets non-existent .cu files), this generates
patches for the ACTUAL tunable parameters in enginex-vllm-bi100:
1. paged_attn.py: _PARTITION_SIZE, V1/V2 dispatch
2. prefix_prefill.py: BLOCK, BLOCK_N, NUM_WARPS
3. triton_flash_attention.py: autotune Config entries
4. _custom_ops.py: SMEM size
5. computility-run.yaml: scheduler params
Each config is derived from CCCL tuning principles (SMEM constraints,
occupancy model, bytes_in_flight) applied to BI-V100 hardware.
"""
import os
import sys
import json
import math
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
# BI-V100 hardware (from hardware.cuh, confirmed via ixsmi)
HW = {
"sm_count": 16,
"smem_per_block": 49152, # 48KB
"l2_cache_bytes": 6 * 1024 * 1024, # 6MB
"hbm_bw_gbps": 900,
"warp_size": 32,
"max_threads_per_block": 1024,
"max_regs_per_thread": 255,
"regs_per_sm": 65536,
# Derived
"bw_per_sm_gbps": 900 / 16, # 56.25 GB/s
# bytes_in_flight = BW/SM × memory_latency ≈ 56 × 1100ns ≈ 62KB → 64KB
"bytes_in_flight": 64 * 1024,
# L2 per SM = 6MB / 16 = 384KB (higher than SM100's 338KB/SM!)
"l2_per_sm_bytes": 6 * 1024 * 1024 // 16,
}
# Qwen3.6-35B-A3B model config
MODEL = {
"head_dim": 256, # CONFIRMED from qwen3_5.py
"num_q_heads": 28, # num_attention_heads
"num_kv_heads": 4, # num_key_value_heads
"hidden_size": 3584,
"intermediate_size": 18944,
"num_layers": 64,
"vocab_size": 152064,
"num_experts": 256, # MoE
"top_k_experts": 8,
"max_model_len": 100000,
}
@dataclass
class TritonConfig:
block_m: int
block_n: int
num_warps: int
num_stages: int
pre_load_v: bool = False
waves_per_eu: int = 2
def smem_bytes(self, head_dim: int, elem_size: int) -> int:
"""SMEM = Q_tile + K_tile + V_tile + softmax_accum"""
q_tile = self.block_m * head_dim * elem_size
k_tile = head_dim * self.block_n * elem_size
# V loaded in inner loop, not staged simultaneously with Q+K
# But PRE_LOAD_V stages V in registers, not SMEM
softmax = self.block_m * 4 * 2 # m_i + l_i, fp32
return q_tile + k_tile + softmax
def fits_smem(self, head_dim: int = 256, elem_size: int = 2) -> bool:
return self.smem_bytes(head_dim, elem_size) <= HW["smem_per_block"]
def occupancy_ctas(self, head_dim: int = 256, elem_size: int = 2) -> int:
"""Max concurrent CTAs per SM"""
threads = self.num_warps * HW["warp_size"]
smem = self.smem_bytes(head_dim, elem_size) * self.num_stages
# Thread limit
ctas_by_threads = HW["max_threads_per_block"] // threads
# SMEM limit
ctas_by_smem = HW["smem_per_block"] // max(1, smem)
# Register limit (rough: assume 40 regs/thread)
regs_per_cta = threads * 40
ctas_by_regs = HW["regs_per_sm"] // max(1, regs_per_cta)
return min(ctas_by_threads, ctas_by_smem, ctas_by_regs)
def to_triton_str(self) -> str:
return (
f'triton.Config({{"BLOCK_M": {self.block_m}, "BLOCK_N": {self.block_n}, '
f'"waves_per_eu": {self.waves_per_eu}, "PRE_LOAD_V": {self.pre_load_v}}}, '
f'num_stages={self.num_stages}, num_warps={self.num_warps})'
)
def generate_triton_configs(
head_dim: int = 256,
elem_size: int = 2, # bf16/fp16
) -> List[TritonConfig]:
"""Generate all valid BI-V100 Triton flash attention configs.
CCCL-derived constraints:
- SMEM: Q_tile + K_tile + softmax ≤ 48KB
- Occupancy: want ≥2 CTAs/SM for 16 SMs
- bytes_in_flight: num_stages=2 matches 64KB BIF sweet spot
- Wave efficiency: total CTAs should be multiple of 16
"""
configs = []
for block_m in [16, 32, 64, 128]:
for block_n in [16, 32, 64, 128]:
for num_warps in [2, 4, 8]:
for num_stages in [1, 2]:
for pre_load_v in [False, True]:
for waves in [1, 2, 4]:
cfg = TritonConfig(
block_m=block_m,
block_n=block_n,
num_warps=num_warps,
num_stages=num_stages,
pre_load_v=pre_load_v,
waves_per_eu=waves,
)
# Filter 1: SMEM must fit
if not cfg.fits_smem(head_dim, elem_size):
continue
# Filter 2: threads must be reasonable
threads = num_warps * 32
if threads > HW["max_threads_per_block"]:
continue
if threads < block_m: # need at least 1 thread per row
continue
# Filter 3: occupancy >= 1 CTA/SM
if cfg.occupancy_ctas(head_dim, elem_size) < 1:
continue
# Filter 4: PRE_LOAD_V register pressure check
if pre_load_v:
v_regs = block_n * head_dim * elem_size // 4 # fp16 → 2 per reg
if v_regs > 128: # too many regs for V pre-load
continue
configs.append(cfg)
return configs
def rank_configs(configs: List[TritonConfig], head_dim: int = 256, elem_size: int = 2) -> List[TritonConfig]:
"""Rank configs by estimated throughput, deduplicated.
CCCL benchmark insight: for memory-bound kernels on BI-V100,
the dominant factors are (in order):
1. Total work per CTA (tile_elements) — amortizes launch overhead
2. Occupancy × tile_size — total inflight bytes across SM
3. Pipeline depth (num_stages) — matches bytes_in_flight window
4. PRE_LOAD_V — reduces stalls when V is small enough for regs
We penalize configs where threads < tile rows (wasted threads)
and where SMEM utilization is very low (leaving bandwidth on table).
"""
# Deduplicate: (block_m, block_n, num_warps, num_stages, pre_load_v)
seen = set()
unique = []
for cfg in configs:
key = (cfg.block_m, cfg.block_n, cfg.num_warps, cfg.num_stages, cfg.pre_load_v)
if key not in seen:
seen.add(key)
unique.append(cfg)
def score(cfg: TritonConfig) -> float:
tile = cfg.block_m * cfg.block_n
occ = cfg.occupancy_ctas(head_dim, elem_size)
smem = cfg.smem_bytes(head_dim, elem_size)
smem_util = smem / HW["smem_per_block"]
threads = cfg.num_warps * 32
# Base: tile size × occupancy (bigger tiles, more parallelism)
base = tile * max(occ, 1)
# Bonus: stages=2 adds ~15% on BI-V100 (from CCCL babelstream data)
stage_mult = 1.15 if cfg.num_stages == 2 else 1.0
# Bonus: preload_v helps when reg pressure allows
preload_mult = 1.05 if cfg.pre_load_v else 1.0
# Penalty: threads >> block_m means wasted work per row
thread_efficiency = min(1.0, cfg.block_m / threads)
# Penalty: very low SMEM util means we could be doing more work
smem_score = min(smem_util * 1.5, 1.0) # 67%+ util → score 1.0
return base * stage_mult * preload_mult * thread_efficiency * smem_score
return sorted(unique, key=score, reverse=True)
def derive_partition_size() -> int:
"""Derive optimal _PARTITION_SIZE for paged_attention.
CCCL parallel: GridEvenShare partitioning.
partition_size = tokens processed per CTA in V2.
BI-V100: 16 SMs, each CTA handles one partition.
For seq_len=4096, partition=512 → 8 partitions → 8 CTAs → only 8/16 SMs busy.
partition=256 → 16 partitions → 16 CTAs → all SMs busy.
But smaller partition → more inter-partition reduce overhead.
Optimal: partition ≈ seq_len / (2 × sm_count) for long sequences
For max_model_len=100K: 100000 / 32 ≈ 3125 → round to 2048 or 4096
But V2 isn't used (forced V1), so this is academic for now.
"""
return 512 # Keep current — V2 is disabled
def derive_prefill_config(head_dim: int = 256, elem_size: int = 2) -> Dict:
"""Derive prefix_prefill.py BLOCK/BLOCK_N/NUM_WARPS.
CCCL parallel: scan + reduce + transform (softmax + QKV matmul)
SMEM model for prefix_prefill (context_attention_fwd_kernel):
Q resident: BLOCK_M × head_dim × elem_size (stays across all K/V iters)
K per iter: head_dim × BLOCK_N × elem_size (loaded, consumed, freed)
softmax: BLOCK_M × 4 × 2 (m_i + l_i, fp32)
Qwen3.6 head_dim=256, bf16:
BLOCK_M=32, BLOCK_N=64: 8KB + 32KB + 256B = 40.25KB (82%) ✓
BLOCK_M=64, BLOCK_N=64: 32KB + 32KB + 512B = 64.5KB (131%) ✗ OVERFLOW
BLOCK_M=32, BLOCK_N=128: 8KB + 64KB + 256B = 72.25KB (147%) ✗ OVERFLOW
BLOCK_M=64, BLOCK_N=32: 32KB + 16KB + 512B = 48.5KB (99%) TIGHT
"""
smem_limit = HW["smem_per_block"]
# Try configs from largest to smallest
candidates = [
(64, 64, 4), # symmetric, ideal for NVIDIA
(32, 64, 4), # asymmetric, better for SMEM-limited
(64, 32, 4), # Q-heavy
(32, 32, 4), # conservative
(32, 32, 2), # minimal
]
for bm, bn, nw in candidates:
q_smem = bm * head_dim * elem_size
k_smem = head_dim * bn * elem_size
softmax_smem = bm * 4 * 2
total = q_smem + k_smem + softmax_smem
if total <= smem_limit:
return {
"BLOCK_M": bm,
"BLOCK_N": bn,
"NUM_WARPS": nw,
"num_stages": 1, # no async copy on BI-V100
"smem_bytes": total,
"smem_utilization": total / smem_limit,
}
# Fallback
return {"BLOCK_M": 32, "BLOCK_N": 32, "NUM_WARPS": 4, "num_stages": 1,
"smem_bytes": 32*256*2 + 256*32*2 + 32*8, "smem_utilization": 0.67}
def main():
print("=" * 70)
print("muh gen_config: CCCL-derived Python-layer configs for BI-V100")
print("=" * 70)
# 1. Triton flash attention configs
print("\n### 1. Triton flash attention autotune configs ###")
print(f" head_dim={MODEL['head_dim']}, elem_size=2 (bf16)")
all_configs = generate_triton_configs(MODEL["head_dim"], 2)
ranked = rank_configs(all_configs, MODEL["head_dim"], 2)
print(f" Generated {len(all_configs)} valid configs (from {16*4*3*2*2*3} combinations)")
print(f" Top 10:")
for i, cfg in enumerate(ranked[:10]):
smem = cfg.smem_bytes(MODEL["head_dim"], 2)
occ = cfg.occupancy_ctas(MODEL["head_dim"], 2)
print(f" #{i+1}: M={cfg.block_m:3d} N={cfg.block_n:3d} "
f"warps={cfg.num_warps} stages={cfg.num_stages} "
f"preload_v={cfg.pre_load_v!s:5s} "
f"smem={smem//1024}KB occ={occ} CTAs/SM")
# 2. Prefix prefill config
print("\n### 2. Prefix prefill config ###")
prefill = derive_prefill_config(MODEL["head_dim"], 2)
print(f" BLOCK_M={prefill['BLOCK_M']}, BLOCK_N={prefill['BLOCK_N']}, "
f"NUM_WARPS={prefill['NUM_WARPS']}")
print(f" SMEM={prefill['smem_bytes']}B ({prefill['smem_utilization']:.0%} of 48KB)")
# 3. Partition size
print("\n### 3. Paged attention config ###")
ps = derive_partition_size()
print(f" _PARTITION_SIZE={ps} (V2 disabled, V1 forced)")
# 4. Summary of what's already applied vs what's new
print("\n### 4. Applied vs pending ###")
applied = [
("_custom_ops.py SMEM=49152", "APPLIED"),
("prefix_prefill.py BLOCK=64 BLOCK_N=64", "APPLIED"),
("triton_flash_attention.py 8 BI-V100 configs", "APPLIED"),
("computility-run.yaml max-model-len=100000", "APPLIED"),
]
pending = [
(f"triton_flash_attention.py +{len(ranked[:20])-17} new configs", "PENDING"),
("paged_attn.py V2 enable for long sequences", "PENDING (needs native V2)"),
("prefix_prefill.py num_stages=2 experiment", "PENDING (needs cp.async support check)"),
]
for item, status in applied:
print(f"{item}: {status}")
for item, status in pending:
print(f"{item}: {status}")
# 5. New configs to add to triton_flash_attention.py
existing_signatures = set()
# Current 17 configs from triton_flash_attention.py (manually extracted)
existing_raw = [
(256,64,8,1,False,2), (128,128,4,1,False,2), (256,128,8,1,False,2),
(128,64,4,1,False,1), (128,64,4,1,True,3), (128,64,4,1,False,3),
(64,64,8,1,False,4), (32,32,8,1,False,4), (16,16,4,1,False,1),
# BI-V100 existing
(64,32,4,1,False,2), (32,64,4,1,False,2), (64,64,4,1,False,2),
(64,64,4,2,False,2), (32,64,4,2,True,2), (32,32,4,2,False,4),
(32,32,4,2,True,4),
]
for m,n,w,s,p,we in existing_raw:
existing_signatures.add((m,n,w,s,p))
new_configs = []
for cfg in ranked[:30]:
sig = (cfg.block_m, cfg.block_n, cfg.num_warps, cfg.num_stages, cfg.pre_load_v)
if sig not in existing_signatures:
new_configs.append(cfg)
existing_signatures.add(sig)
print(f"\n### 5. New triton configs to add ({len(new_configs)}) ###")
for cfg in new_configs[:15]:
smem = cfg.smem_bytes(MODEL["head_dim"], 2)
print(f" {cfg.to_triton_str()}")
print(f" SMEM={smem//1024}KB, occupancy={cfg.occupancy_ctas(MODEL['head_dim'], 2)} CTAs/SM")
if __name__ == "__main__":
main()