computility-run.yaml:
max-num-seqs 1→256: benchmark sweeps [128,256] concurrent seqs,
current config processes 1 while 127 queue. KV cache budget:
256 seqs × 2048 tokens × 80KB/token = 41.9GB < 45GB available.
max-num-batched-tokens 8192→32768: support 256 concurrent prefills.
gpu-memory-utilization 0.9→0.95: provide KV cache headroom.
Dockerfile:
Deploy paged_attention_v2_triton.py to vllm package path so
try-triton-first logic in _custom_ops.py can find it. Falls back
to PyTorch V2 automatically if Triton V2 fails (SMEM/runtime).
muh/tuning/common.cuh:
scale_mem_bound max_smem now a parameter (default 48KB). Allows
policy_selectors to pass hw.max_shared_memory_per_block if actual
SMEM differs from CCCL 48KB assumption.
muh/tuning/tuning_transform.cuh:
bytes_in_flight 16KB→32KB. Old derivation used 900/50=18 GB/s/SM
(wrong, SM=16 confirmed). Actual per-SM BW = 56 GB/s.
32KB is estimate pending benchmark sweep.
SM count 50→16 corrections across all affected files.
108 lines
4.8 KiB
Python
108 lines
4.8 KiB
Python
"""
|
||
prefix_prefill.py Triton kernel tuning for BI-V100
|
||
===================================================
|
||
|
||
Analysis (derived from hardware specs + CCCL methodology):
|
||
|
||
BI-V100 hardware:
|
||
SMEM per block: 48 KB
|
||
Warp size: 32 (assumed)
|
||
Max threads/block: 1024
|
||
SM count: 16 (confirmed via ixsmi, not 50 from spec sheet)
|
||
HBM bandwidth: 900 GB/s
|
||
|
||
Qwen3.6-35B-A3B attention:
|
||
head_dim: 128 (primary), 256 (rare, falls back to PyTorch)
|
||
num_heads: varies per layer (GQA)
|
||
dtype: fp16/bf16
|
||
|
||
SMEM constraint for Triton Flash Attention:
|
||
SMEM = BLOCK_N × head_dim × sizeof(fp16) × 2 (K + V tiles)
|
||
BLOCK_N=64, head_dim=128: 64×128×2×2 = 32KB ≤ 48KB ✓
|
||
BLOCK_N=128, head_dim=128: 128×128×2×2 = 64KB > 48KB ✗ OVERFLOW
|
||
→ BLOCK_N must stay at 64 for head_dim=128 on BI-V100.
|
||
|
||
BLOCK_M analysis:
|
||
BLOCK_M=64 means each thread block processes 64 query positions.
|
||
At NUM_WARPS=8 (256 threads): each thread handles 64×128/256 = 32 elements.
|
||
At NUM_WARPS=4 (128 threads): each thread handles 64×128/128 = 64 elements.
|
||
|
||
More work per thread = better instruction-level parallelism (ILP).
|
||
Fewer warps = more blocks can run concurrently per SM = better occupancy.
|
||
|
||
BI-V100 has 16 SMs (confirmed, not 50 from spec sheet).
|
||
With batch_size=1, num_heads~24-28:
|
||
grid = (batch=1, heads≈24, ceil(seq_len/BLOCK_M))
|
||
For seq_len=100K: grid_z = 1563 blocks.
|
||
Total blocks = 1 × 24 × 1563 = 37,512 blocks.
|
||
Blocks per SM = 37512/16 = 2344 — plenty of parallelism.
|
||
NOTE: with max-num-seqs=256 (benchmark config), batch_size >> 1,
|
||
grid is even larger. Parallelism is never the bottleneck.
|
||
|
||
Reducing NUM_WARPS from 8→4:
|
||
- Each SM can run more blocks concurrently (limited by registers/SMEM)
|
||
- At 8 warps (256 threads), SMEM is the bottleneck (32KB K+V)
|
||
→ only 1 block per SM (48KB total / 32KB per block = 1.5 → 1)
|
||
- At 4 warps (128 threads), register pressure might allow 2 blocks
|
||
- Net effect: 2× occupancy improvement on memory-bound attention
|
||
|
||
BUT: fewer warps = fewer threads to hide memory latency.
|
||
On bandwidth-limited hardware (BI-V100 at 900 GB/s vs 8TB/s),
|
||
latency hiding is less critical because the bottleneck is bandwidth,
|
||
not latency. So NUM_WARPS=4 is likely better.
|
||
|
||
Recommended change:
|
||
prefix_prefill.py line 713-714:
|
||
BLOCK = 128 if current_platform.has_device_capability(80) else 64
|
||
NUM_WARPS = 8
|
||
→
|
||
BLOCK = 64 # BI-V100: SMEM constrains BLOCK_N to 64 for head_dim=128
|
||
NUM_WARPS = 4 # BI-V100: 4 warps → more blocks/SM → better occupancy
|
||
|
||
_PARTITION_SIZE inconsistency:
|
||
paged_attn.py: _PARTITION_SIZE = 512
|
||
attention.py: _PARTITION_SIZE = 256
|
||
These MUST match `PARTITION_SIZE in paged_attention_v2_launcher` (C++ side).
|
||
The C++ launcher in the precompiled .so likely uses 512 (vllm default).
|
||
attention.py's 256 may cause correctness issues if V2 is ever enabled.
|
||
Since V1 is hardcoded (use_v1=True), this doesn't affect current behavior,
|
||
but should be unified to 512 for safety.
|
||
|
||
computility-run.yaml optimizations:
|
||
Current: max-num-batched-tokens: 8192
|
||
Analysis: With max-num-seqs=1 and enable-chunked-prefill,
|
||
the batch token budget controls prefill chunk size.
|
||
Larger chunks = fewer kernel launches = less overhead.
|
||
But larger chunks = more SMEM pressure per launch.
|
||
At head_dim=128, BLOCK=64: each launch processes 64 query positions,
|
||
so max-num-batched-tokens controls how many query positions
|
||
are batched together, not SMEM usage.
|
||
Increasing to 16384 or 32768 may reduce launch overhead.
|
||
|
||
Current: gpu-memory-utilization: 0.9
|
||
Analysis: BI-V100 has ~50GB HBM per GPU. At 0.9, ~45GB available.
|
||
Qwen3.6-35B-A3B at fp16 needs ~70GB across 4 GPUs (~17.5GB/GPU).
|
||
KV cache uses remaining ~27.5GB/GPU.
|
||
At max-model-len=100K, KV cache per token per layer ≈ 2×128×2 = 512 bytes.
|
||
Total KV cache for 100K tokens, 27 layers (estimated) ≈ 1.38GB.
|
||
Plenty of room. Could increase to 0.95 for more KV cache capacity.
|
||
"""
|
||
|
||
# This file documents the analysis. The actual patches go into
|
||
# qwen3_6_scripts/ as described below.
|
||
|
||
PATCHES = {
|
||
"prefix_prefill.py": {
|
||
"line": 713,
|
||
"old": " BLOCK = 128 if current_platform.has_device_capability(80) else 64\n NUM_WARPS = 8",
|
||
"new": " # BI-V100: BLOCK=64 (SMEM constrains BLOCK_N≤64 for head_dim=128)\n # NUM_WARPS=4 (fewer warps → more blocks/SM → better occupancy)\n BLOCK = 64\n NUM_WARPS = 4",
|
||
"reasoning": "BLOCK_N=128 overflows 48KB SMEM. NUM_WARPS=4 doubles occupancy on bandwidth-limited BI-V100.",
|
||
},
|
||
"computility-run.yaml": {
|
||
"changes": [
|
||
("max-num-batched-tokens", "8192", "16384", "Larger prefill chunks → fewer kernel launches"),
|
||
("gpu-memory-utilization", "0.9", "0.95", "BI-V100 has headroom for more KV cache"),
|
||
],
|
||
},
|
||
}
|