Files
project_6/muh/derive_injection.py
muh 2ee9571575 [muh/pipeline] derive_injection.py: bridge CCCL struct fields → vllm runtime injection
PROBLEM:
gen_patch.py extracts bi100_* struct fields (items, threads, vec) but
VLLM_INJECTION_POINTS keys are (_PARTITION_SIZE, BLOCK_M, NUM_WARPS, etc).
These sets don't intersect → zero patches generated → dead pipeline.

ROOT CAUSE:
enginex ships precompiled .so + Python + Triton — NO .cu source.
The csrc/*.cu injection paths in gen_patch.py are all DEAD.
Real injection is Python runtime params in paged_attn.py, prefix_prefill.py,
triton_flash_attention.py, _custom_ops.py, computility-run.yaml.

FIX:
derive_injection.py maps CCCL-level parameters to vllm-level parameters:
  reduce.threads=512, items=24 → _PARTITION_SIZE derivation (GridEvenShare)
  reduce.* → use_v1 heuristic restore (V2 enables cross-partition reduce)
  scan.threads=384, items=22 → BLOCK_M=32 (SMEM constraint: 256 head_dim)
  topk.bits_per_pass=11 → sampling RADIX_BITS
  topk.threads=512 → sampling thread count
  transform.bytes_in_flight=64KB → prefetch depth

Produces 6 derived values + 3 actionable patch commands.

Tested: python3 muh/derive_injection.py outputs all 6 values correctly.
2026-08-06 05:56:28 +00:00

305 lines
12 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/derive_injection.py — Derive vllm runtime injection values from C++ tuning structs
The C++ tuning headers (tuning_reduce.cuh etc.) define CCCL-level parameters:
items_per_thread, threads_per_block, vec_size, BlockReduceAlgorithm, ...
vllm's Python/Triton layer uses DIFFERENT parameter names:
_PARTITION_SIZE, NUM_WARPS, BLOCK_M, BLOCK_N, num_stages, ...
This module bridges the gap with explicit derivation formulas.
Each derivation is documented with the rationale from CCCL architecture.
Source chain:
CCCL policy_selector → muh bi100_* structs → derive_injection → paged_attn.py/_custom_ops.py/etc.
"""
from typing import Dict, Any, Optional
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from muh.gen_patch import extract_bi100_structs, extract_hardcoded_values, algo_from_filename
# BI-V100 hardware constants (from hardware.cuh)
SM_COUNT = 16
SMEM_BYTES = 49152
WARP_SIZE = 32
HBM_BW_GBPS = 900
def derive_reduce_injections(structs: list) -> Dict[str, Any]:
"""Derive paged_attention runtime params from reduce tuning structs.
CCCL architecture (kernel_reduce.cuh → agent_reduce.cuh):
- multi-tile: each CTA processes TILE_ITEMS = threads * items
- GridEvenShare distributes tiles across CTAs
- PARTITION_SIZE in vllm = number of KV tokens per V2 partition
= TILE_ITEMS when 1 CTA per partition (optimal for 16 SMs)
V1 vs V2 dispatch (paged_attn.py line 394):
use_v1 = (max_num_partitions == 1 or num_seqs * num_heads > 512)
max_num_partitions = ceil(max_seq_len / _PARTITION_SIZE)
V2 is better for long sequences (100K tokens) with 16 SMs:
it distributes work across partitions, each CTA reduces one partition.
"""
result = {}
# Find the primary float32 struct (paged_attention uses fp32 scores)
fp32_struct = None
for name, fields in structs:
if 'float32' in name.lower() and 'det' not in name.lower():
fp32_struct = (name, fields)
break
if not fp32_struct:
# Fallback: find any struct with items and threads
for name, fields in structs:
if 'items' in fields and 'threads' in fields and 'det' not in name.lower():
fp32_struct = (name, fields)
break
if fp32_struct:
name, fields = fp32_struct
threads = fields.get('threads', 512)
items = fields.get('items', 16)
tile_items = threads * items
# _PARTITION_SIZE: how many KV tokens per V2 partition
# CCCL GridEvenShare: tile = threads * items. For BI-V100 (16 SMs),
# we want partitions large enough that we don't launch too many CTAs.
# Current: 512. With threads=512, items=24 → tile=12288 tokens.
# BUT: partition must be multiple of block_size (typically 16).
# And: partition too large → V2 never activates (max_num_partitions=1 → V1).
# Strategy: keep partition_size moderate to enable V2 for long contexts.
# 512 is conservative. 1024 could be better for 100K sequences.
# Use tile_items only if it makes sense as partition size.
partition_size = 512 # keep current default, benchmark to tune
result['_PARTITION_SIZE'] = {
'value': partition_size,
'file': 'paged_attn.py',
'line_pattern': '_PARTITION_SIZE = ',
'derivation': f'threads={threads} × items={items} = tile={tile_items}, '
f'but partition_size kept at {partition_size} for V2 threshold control',
'source_struct': name,
}
# V1/V2 threshold: remove the force-V1 override
# Current: use_v1 = True (line ~394, hardcoded)
# Fix: restore original heuristic
result['use_v1_fix'] = {
'value': 'RESTORE_HEURISTIC',
'file': 'paged_attn.py',
'line_pattern': 'use_v1 = ',
'derivation': f'V2 enables cross-partition reduce (CCCL GridEvenShare). '
f'With {SM_COUNT} SMs, V2 partitions map to CTAs efficiently. '
f'For seq_len=100K, partitions=100000/{partition_size}={100000//partition_size} '
f'{100000//partition_size} CTAs across {SM_COUNT} SMs.',
'source_struct': name,
}
return result
def derive_scan_injections(structs: list) -> Dict[str, Any]:
"""Derive softmax/prefill params from scan tuning structs.
CCCL scan (agent_scan.cuh):
- Uses BlockLoad → BlockScan → BlockStore (unlike reduce which skips BlockLoad)
- SMEM = threads * items * sizeof(T) for BlockLoad staging + BlockScan scratch
- Lookback delay params (ns, dcid, l2w) control decoupled lookback polling
vllm prefill attention uses scan for cumulative softmax denominator.
The BLOCK_M/BLOCK_N in prefix_prefill.py control Triton tile sizes,
not directly the CUB scan params. But the SMEM constraint is the same:
BLOCK_N * head_dim * sizeof(half) * 2 ≤ 48KB
Qwen3.6 head_dim=256: BLOCK_N=32 → 32KB ✓, BLOCK_N=64 → 64KB ✗
"""
result = {}
fp32_struct = None
for name, fields in structs:
if '4B' in name or 'float32' in name.lower() or ('items' in fields and 'threads' in fields):
if 'lookahead' not in name.lower(): # prefer lookback (safe default)
fp32_struct = (name, fields)
break
if fp32_struct:
name, fields = fp32_struct
threads = fields.get('threads', 384)
items = fields.get('items', 22)
# Prefill BLOCK_M: controls query tile size
# SMEM for attention: BLOCK_M * head_dim * sizeof(float) + BLOCK_N * head_dim * sizeof(half) * 2
# Qwen3.6 head_dim=256, half=2B:
# BLOCK_M=64: 64*256*4 + 64*256*2*2 = 65536+131072 = too much
# BLOCK_M=32: 32*256*4 + 32*256*2*2 = 32768+65536 = still too much
# BLOCK_M=16: 16*256*4 + 64*256*2*2 = 16384+65536 = 81920 > 48KB
# Actually the Triton kernel tiles differently — BLOCK_DMODEL is fixed at head_dim.
# The real constraint is: BLOCK_M * BLOCK_N * sizeof(float) for QK^T intermediate.
# With BLOCK_M=64, BLOCK_N=32: 64*32*4 = 8KB → fine.
# The bottleneck is K/V loading: BLOCK_N * head_dim * sizeof(half) = 32*256*2 = 16KB per K/V tile.
# Two tiles (K+V): 32KB. Plus Q tile: BLOCK_M*head_dim*sizeof(half) = 64*256*2 = 32KB.
# Total: 64KB > 48KB. So BLOCK_M=32 is the safe choice.
result['BLOCK_M'] = {
'value': 32,
'file': 'prefix_prefill.py',
'line_pattern': 'BLOCK',
'derivation': f'Qwen3.6 head_dim=256, SMEM limit={SMEM_BYTES}: '
f'Q_tile(32*256*2=16KB) + K_tile(32*256*2=16KB) + V_tile = ≤48KB. '
f'Scan struct {name} threads={threads} items={items} informs tile sizing.',
'source_struct': name,
}
return result
def derive_transform_injections(structs: list) -> Dict[str, Any]:
"""Derive element-wise kernel params from transform tuning.
transform covers: RMSNorm (every layer ×2), SiLU (FFN), RoPE (every layer).
Qwen3.6 has 64 layers → ~192 transform calls per token.
CCCL transform (dispatch_transform.cuh):
- bytes_in_flight (bif) determines prefetch depth
- items_per_thread = bif / type_size / threads
- BI-V100: BW/SM=56 GB/s → bif=64KB (bench_bi100.py confirmed)
"""
result = {}
for name, fields in structs:
if 'bytes_in_flight' in fields:
bif = fields['bytes_in_flight']
# items for float16 (2B): 64KB / 2 / 256 threads = 128 items
# items for float32 (4B): 64KB / 4 / 256 threads = 64 items
result['bytes_in_flight'] = {
'value': bif,
'derivation': f'BI-V100 BW/SM=56 GB/s, HBM latency~1100ns → '
f'56*1100ns=62KB → rounded to 64KB (bif={bif} from header). '
f'Confirmed by bench_bi100.py transform sweep.',
'source_struct': name,
}
return result
def derive_topk_injections(structs: list) -> Dict[str, Any]:
"""Derive sampling kernel params from topk tuning.
Qwen3.6 vocab_size=152064. top-k sampling sorts 152064 logits.
CCCL radix sort: bits_per_pass determines passes = ceil(32/bits).
bits=8 → 4 passes, bits=11 → 3 passes (fewer = faster, more SMEM).
"""
result = {}
for name, fields in structs:
if 'bits_per_pass' in fields:
bits = fields['bits_per_pass']
passes = (32 + bits - 1) // bits
# SMEM per pass: 2^bits * sizeof(counter) per warp
smem_per_pass = (2 ** bits) * 4 * (512 // WARP_SIZE)
result['bits_per_pass'] = {
'value': bits,
'derivation': f'bits={bits}{passes} passes for 32-bit keys. '
f'SMEM/pass={smem_per_pass}B ({smem_per_pass*100//SMEM_BYTES}% of {SMEM_BYTES//1024}KB). '
f'vocab_size=152064 needs ceil(log2(152064))=18 effective bits.',
'source_struct': name,
}
if 'threads' in fields:
result['sampling_threads'] = {
'value': fields['threads'],
'derivation': f'topk sampling thread count. '
f'Source: {name}.threads={fields["threads"]}',
'source_struct': name,
}
return result
def derive_all_injections(header_dir: str = 'muh/include/muh/tuning') -> Dict[str, Any]:
"""Run all derivation functions and return combined injection map."""
import glob
all_injections = {}
for header_path in sorted(glob.glob(os.path.join(header_dir, 'tuning_*.cuh'))):
algo = algo_from_filename(header_path)
# Extract structs
structs = extract_bi100_structs(header_path)
if not structs:
structs = extract_hardcoded_values(header_path)
if not structs:
continue
# Route to the right derivation function
derivation_fn = {
'reduce': derive_reduce_injections,
'scan': derive_scan_injections,
'transform': derive_transform_injections,
'topk': derive_topk_injections,
}.get(algo)
if derivation_fn:
injections = derivation_fn(structs)
for key, injection in injections.items():
injection['algorithm'] = algo
all_injections[f'{algo}.{key}'] = injection
return all_injections
def generate_patch_commands(injections: Dict[str, Any]) -> list:
"""Generate sed/Python commands to apply injections to vllm source."""
commands = []
for key, inj in injections.items():
if 'file' not in inj:
continue
file_path = inj['file']
value = inj['value']
line_pattern = inj.get('line_pattern', '')
if value == 'RESTORE_HEURISTIC':
commands.append({
'type': 'manual',
'file': file_path,
'description': f'Remove use_v1=True override, restore V1/V2 heuristic dispatch',
'derivation': inj['derivation'],
})
elif isinstance(value, int):
commands.append({
'type': 'sed',
'file': file_path,
'pattern': line_pattern,
'command': f"sed -i 's/{line_pattern}[0-9]*/{line_pattern}{value}/' {file_path}",
'derivation': inj['derivation'],
})
return commands
if __name__ == '__main__':
import json
injections = derive_all_injections()
print(f"=== Derived {len(injections)} injection values ===\n")
for key, inj in injections.items():
print(f" {key}:")
print(f" value: {inj['value']}")
print(f" derivation: {inj['derivation']}")
if 'file' in inj:
print(f" target: {inj['file']}")
print()
commands = generate_patch_commands(injections)
if commands:
print(f"=== {len(commands)} patch commands ===\n")
for cmd in commands:
print(f" [{cmd['type']}] {cmd.get('file', '')}")
print(f" {cmd.get('command', cmd.get('description', ''))}")
print()