📝 docs: add GPU-Only XAttention guide with performance analysis
Add comprehensive documentation for GPU-only XAttention BSA mode: - Architecture design and SparsePolicy interface - Memory pre-allocation mechanism (alloc_policy_metadata) - Performance analysis: 32K +15%, 64K +41% vs baseline - CUDA Graph limitations explanation (variable seq_len in prefill) - nsys profiling tools usage guide Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -28,6 +28,7 @@ Nano-vLLM is a lightweight vLLM implementation (~1,200 lines) for fast offline L
|
||||
| [`docs/nsys_wrong_event_order_bug.md`](docs/nsys_wrong_event_order_bug.md) | 🐛 NSYS BUG: Ring buffer pipeline 触发 nsys 时间戳乱序问题的调试记录 |
|
||||
| [`docs/cpu_scheduling_latency_analysis.md`](docs/cpu_scheduling_latency_analysis.md) | ⚡ PERF: CPU 调度延迟分析,kernel 间隙来源,GPU 利用率优化方向 |
|
||||
| [`docs/bench_offload_results.md`](docs/bench_offload_results.md) | 📊 BENCH: CPU offload 性能测试结果,Full vs XAttention 对比 (32K/128K) |
|
||||
| [`docs/gpu_only_xattn_guide.md`](docs/gpu_only_xattn_guide.md) | 🚀 GPU-Only XAttention: 内存预分配、性能分析 (32K +15%, 64K +41%)、CUDA Graph 限制 |
|
||||
|
||||
## Rules Index
|
||||
|
||||
|
||||
296
docs/gpu_only_xattn_guide.md
Normal file
296
docs/gpu_only_xattn_guide.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# GPU-Only XAttention 指南
|
||||
|
||||
本文档介绍 GPU-only 模式下 XAttention BSA 的实现、内存优化和性能分析。
|
||||
|
||||
## 概述
|
||||
|
||||
GPU-only 模式下,所有 KV cache 存储在 GPU 上,无需 CPU offload。XAttention 通过稀疏注意力加速 prefill 阶段。
|
||||
|
||||
### 执行路径对比
|
||||
|
||||
| 模式 | Prefill 方法 | Decode 方法 | KV 存储 |
|
||||
|------|-------------|-------------|---------|
|
||||
| GPU-only Full | `compute_prefill()` | `compute_decode()` | GPU |
|
||||
| GPU-only XAttn | `compute_prefill()` | `compute_decode()` | GPU |
|
||||
| CPU Offload | `compute_chunked_prefill()` | `compute_chunked_decode()` | CPU + GPU |
|
||||
|
||||
## 架构设计
|
||||
|
||||
### SparsePolicy 接口
|
||||
|
||||
```python
|
||||
class SparsePolicy:
|
||||
# GPU-only 方法
|
||||
def compute_prefill(self, q, k, v, ...) -> Tensor
|
||||
def compute_decode(self, q, k_cache, v_cache, ...) -> Tensor
|
||||
|
||||
# CPU Offload 方法
|
||||
def compute_chunked_prefill(self, q, k, v, ...) -> Tensor
|
||||
def compute_chunked_decode(self, q, ...) -> Tensor
|
||||
|
||||
# 初始化方法
|
||||
def initialize(self, num_layers, ...) -> None # CPU offload metadata
|
||||
def alloc_policy_metadata(self, num_heads, ...) -> None # GPU-only buffers
|
||||
```
|
||||
|
||||
### XAttentionBSAPolicy 实现
|
||||
|
||||
```
|
||||
GPU-only Prefill 流程:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. GQA 扩展 (使用预分配 buffer) │
|
||||
│ K: [seq, kv_heads, dim] → K_exp: [1, heads, seq, dim] │
|
||||
│ │
|
||||
│ 2. XAttention 估计 │
|
||||
│ flat_group_gemm_fuse_reshape_kernel (Q@K^T) │
|
||||
│ softmax_fuse_block_sum_kernel (block 重要性) │
|
||||
│ → sparse mask │
|
||||
│ │
|
||||
│ 3. BSA 稀疏注意力 │
|
||||
│ flash_fwd_block_kernel (只计算选中的 blocks) │
|
||||
│ → output │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 内存预分配
|
||||
|
||||
### 问题背景
|
||||
|
||||
XAttention 的 `compute_prefill()` 需要 GQA 扩展:
|
||||
|
||||
```python
|
||||
# 之前: 动态分配 (~2GB for 64K)
|
||||
K_exp = K.repeat_interleave(num_groups, dim=1) # 分配 1
|
||||
k_bsa = k.repeat_interleave(num_groups, dim=1) # 分配 2 (重复!)
|
||||
```
|
||||
|
||||
每次 prefill 都动态分配,导致:
|
||||
- 内存碎片
|
||||
- 分配延迟
|
||||
- 可能 OOM
|
||||
|
||||
### 解决方案: alloc_policy_metadata()
|
||||
|
||||
在框架初始化时预分配 buffer:
|
||||
|
||||
```python
|
||||
class XAttentionBSAPolicy(SparsePolicy):
|
||||
def alloc_policy_metadata(self, num_heads, num_kv_heads, head_dim,
|
||||
max_seq_len, dtype, device):
|
||||
# 预分配 GQA 扩展 buffer
|
||||
shape = (1, num_heads, max_seq_len, head_dim)
|
||||
self._k_expanded = torch.empty(shape, dtype=dtype, device=device)
|
||||
self._v_expanded = torch.empty(shape, dtype=dtype, device=device)
|
||||
|
||||
def compute_prefill(self, q, k, v, ...):
|
||||
seq_len = k.shape[0]
|
||||
# 使用预分配 buffer 的 slice
|
||||
K_exp = self._k_expanded[:, :, :seq_len, :]
|
||||
# 原地 GQA 扩展
|
||||
K_exp.view(...).copy_(K.unsqueeze(2).expand(...))
|
||||
# 复用同一 buffer 给 BSA
|
||||
k_bsa = K_exp.squeeze(0).transpose(0, 1)
|
||||
```
|
||||
|
||||
### 内存使用
|
||||
|
||||
| 序列长度 | 预分配大小 | 说明 |
|
||||
|---------|-----------|------|
|
||||
| 32K | 512 MB | `2 * 32 * 32768 * 128 * 2 bytes` |
|
||||
| 64K | 1024 MB | `2 * 32 * 65536 * 128 * 2 bytes` |
|
||||
|
||||
优化效果:
|
||||
- 之前: ~2GB 动态分配 (xattn_estimate + BSA 各一次)
|
||||
- 之后: ~1GB 预分配 (复用同一 buffer)
|
||||
|
||||
### 框架集成
|
||||
|
||||
```python
|
||||
# model_runner.py - allocate_kv_cache()
|
||||
def allocate_kv_cache(self):
|
||||
# ... KV cache 分配 ...
|
||||
|
||||
# GPU-only 模式: 预分配 policy buffers
|
||||
if not config.enable_cpu_offload:
|
||||
self.kvcache_manager.sparse_policy.alloc_policy_metadata(
|
||||
num_heads=num_heads,
|
||||
num_kv_heads=num_kv_heads,
|
||||
head_dim=head_dim,
|
||||
max_seq_len=config.max_model_len,
|
||||
dtype=dtype,
|
||||
device=torch.device("cuda"),
|
||||
)
|
||||
```
|
||||
|
||||
## 性能分析
|
||||
|
||||
### 32K Prefill 性能
|
||||
|
||||
| Policy | Throughput | 相对提升 |
|
||||
|--------|------------|----------|
|
||||
| Baseline | 4880 tok/s | - |
|
||||
| Full | 4892 tok/s | +0.2% |
|
||||
| **XAttention** | **5602 tok/s** | **+15%** |
|
||||
|
||||
### 64K Prefill 性能
|
||||
|
||||
| Policy | Throughput | 相对提升 |
|
||||
|--------|------------|----------|
|
||||
| Baseline | 3386 tok/s | - |
|
||||
| Full | 3355 tok/s | -0.9% |
|
||||
| **XAttention** | **4775 tok/s** | **+41%** |
|
||||
|
||||
### Kernel 时间分解 (32K)
|
||||
|
||||
**XAttention:**
|
||||
```
|
||||
FFN GEMM: 3219 ms (54%)
|
||||
BSA Attention: 1231 ms (21%)
|
||||
XAttn Estimation: 415 ms (7%)
|
||||
Other: 1020 ms (18%)
|
||||
─────────────────────────────
|
||||
Total: 5885 ms
|
||||
```
|
||||
|
||||
**Full:**
|
||||
```
|
||||
FFN GEMM: 3244 ms (48%)
|
||||
Dense Attention: 2861 ms (43%)
|
||||
Other: 595 ms (9%)
|
||||
─────────────────────────────
|
||||
Total: 6700 ms
|
||||
```
|
||||
|
||||
### 加速来源
|
||||
|
||||
```
|
||||
Dense Attention: 2861 ms
|
||||
BSA Attention: 1231 ms (节省 1630 ms, -57%)
|
||||
XAttn Estimation: 415 ms (额外开销)
|
||||
─────────────────────────────
|
||||
净节省: 1215 ms (42% attention 时间)
|
||||
```
|
||||
|
||||
## CUDA Graph 限制
|
||||
|
||||
### 为什么 Prefill 不能用 CUDA Graph
|
||||
|
||||
CUDA Graph 要求所有操作在 capture 时确定:
|
||||
|
||||
| 必须固定 | Prefill 的情况 |
|
||||
|---------|---------------|
|
||||
| Tensor 形状 | seq_len 可变 (1 ~ max_model_len) |
|
||||
| Kernel grid | 依赖 seq_len |
|
||||
| 内存地址 | 中间 tensor 大小变化 |
|
||||
|
||||
```python
|
||||
# 不同请求的 seq_len 不同
|
||||
request_1: prefill(seq_len=1024) # grid=(8, 32, 1)
|
||||
request_2: prefill(seq_len=32768) # grid=(256, 32, 1)
|
||||
```
|
||||
|
||||
### Decode 可以用 CUDA Graph
|
||||
|
||||
```python
|
||||
# Decode 每次只处理 1 token
|
||||
q: [batch_size, 1, heads, dim] # 形状固定
|
||||
```
|
||||
|
||||
nanovllm 为每个 batch_size 预先 capture 一个 graph:
|
||||
|
||||
```python
|
||||
def capture_cudagraph(self):
|
||||
for batch_size in [1, 2, 4, 8, ...]:
|
||||
with torch.cuda.graph(g):
|
||||
self.run_model(dummy_input, is_prefill=False)
|
||||
self.graphs[batch_size] = g
|
||||
```
|
||||
|
||||
### Nsys Profile 结果
|
||||
|
||||
```
|
||||
XAttention 32K Prefill:
|
||||
Total kernels: 41,904
|
||||
Non-graph: 41,904 (100%)
|
||||
Graph: 0
|
||||
|
||||
Full 32K Prefill:
|
||||
Total kernels: 35,308
|
||||
Non-graph: 35,308 (100%)
|
||||
Graph: 0
|
||||
```
|
||||
|
||||
**两者都是 100% NON-GRAPH**,这是 prefill 的本质特性。
|
||||
|
||||
## Profiling 工具
|
||||
|
||||
### 使用 profile.sh
|
||||
|
||||
```bash
|
||||
# XAttention 32K
|
||||
bash scripts/profile.sh --max-len 32768 --policy xattn
|
||||
|
||||
# Full 32K
|
||||
bash scripts/profile.sh --max-len 32768 --policy full
|
||||
|
||||
# 64K (需要降低 gpu-util)
|
||||
bash scripts/profile.sh --max-len 65536 --policy xattn --gpu-util 0.7
|
||||
```
|
||||
|
||||
### 分析 nsys 结果
|
||||
|
||||
```bash
|
||||
# 查看 kernel 统计
|
||||
nsys stats --report cuda_gpu_kern_sum results/nsys/<file>.nsys-rep
|
||||
|
||||
# 用 sqlite 查询详细数据
|
||||
sqlite3 results/nsys/<file>.sqlite "
|
||||
SELECT
|
||||
(SELECT value FROM StringIds WHERE id = shortName) as kernel,
|
||||
COUNT(*) as count,
|
||||
SUM(end-start)/1e6 as total_ms
|
||||
FROM CUPTI_ACTIVITY_KIND_KERNEL
|
||||
GROUP BY shortName
|
||||
ORDER BY total_ms DESC
|
||||
LIMIT 10
|
||||
"
|
||||
```
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 启用 XAttention GPU-only
|
||||
|
||||
```python
|
||||
from nanovllm import LLM
|
||||
from nanovllm.config import SparsePolicyType
|
||||
|
||||
llm = LLM(
|
||||
model_path,
|
||||
max_model_len=32768,
|
||||
sparse_policy=SparsePolicyType.XATTN_BSA,
|
||||
gpu_memory_utilization=0.9, # 64K 时可能需要降低
|
||||
)
|
||||
```
|
||||
|
||||
### 命令行测试
|
||||
|
||||
```bash
|
||||
# bench.py
|
||||
python bench.py --max-len 32768 --policy xattn
|
||||
|
||||
# 64K 需要降低 gpu-util
|
||||
python bench.py --max-len 65536 --policy xattn --gpu-util 0.7
|
||||
```
|
||||
|
||||
### 最佳实践
|
||||
|
||||
1. **32K 及以下**: 使用默认 `gpu_memory_utilization=0.9`
|
||||
2. **64K**: 降低到 `gpu_memory_utilization=0.7`
|
||||
3. **Decode**: XAttention 自动 fallback 到 FullAttentionPolicy
|
||||
4. **Paged KV Cache**: 当 `block_tables` 存在时自动 fallback 到 flash_attn
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Sparse Policy 架构](sparse_policy_architecture.md)
|
||||
- [XAttention 算法详解](xattention_algorithm_guide.md)
|
||||
- [BSA 接口文档](block_sparse_attn_interface.md)
|
||||
Reference in New Issue
Block a user