前言

把DeepSeek-V3和Qwen2.5-72B部署到昇腾910B集群上。客户说"GPU上跑得好好的,换昇腾应该也行吧"。结果第一天就被砸懵——同样的模型、同样的batch,昇腾上吞吐只有GPU的60%。不是算力不够,是我根本没搞清楚CANN的优化逻辑和CUDA完全是两套体系。

很多人以为昇腾上跑大模型就是"把CUDA代码编译一下扔给CANN",其实从Attention到显存管理,每个环节的优化路径都不一样。

工程经验: DeepSeek-V3初次部署到昇腾910B,未开优化时decode吞吐580 TPS。开完FlashAttention + PagedAttention + W8A16量化,到2180 TPS,涨了276%。不是算力不够,是默认配置没打开昇腾的加速特性。

推理瓶颈:先 profile 再动手

一上来就改算子调参数,优化半天发现瓶颈在数据搬运。CANN自带的msprof跑一次就知道瓶颈在哪:

# 跑msprof做性能分析
msprof --output=./profiling_output \
       --aic-metrics=memory_bandwidth_utilization \
       python infer.py --model deepseek-v3 --batch 8

# 看报告:哪种算子占比高
msprof --export=on --output=./profiling_output
# 打开生成的report.html,看"算子耗时分布"

我第一次profile DeepSeek-V3:Attention计算38%、KV Cache访存27%、MoE Router+AllReduce 19%、其他16%。

为什么GPU方案不能直接复用?GPU上Attention占比通常50%+,瓶颈在计算;昇腾Cube的MAC阵列算矩阵乘效率高,但Vector做逐元素运算的吞吐比GPU的SM低——softmax、scale这些逐元素操作反而成瓶颈。GPU优化MatMul就够;昇腾得同时优化计算和访存。

工程经验: msprof的输出在profiling_output/summary/目录下,api_statistic.csv里能看到每个ACL API的耗时。aclmdExecute占比高说明算子调度开销大(要开ATB融合),aclrtMemMalloc占比高说明显存碎片多(要开预分配)。

Attention 优化:FlashAttention 只是起点

ops-transformer里的FlashAttention算子针对达芬奇架构做了Cube/Vector流水线优化:Cube算Q×K^T → Vector算scale+mask+softmax → Cube算P×V,中间数据走L1不落HBM。

# PyTorch接入FlashAttention(torch_npu)
import torch
import torch_npu

# 开FlashAttention(ops-transformer提供)
with torch.no_grad():
    output = torch_npu.npu_flash_attention(
        query, key, value,
        head_num=32,
        head_dim=128,
        dropout_prob=0.0,
        inner_precise=0,  # 用FP16,不用FP32累加
    )

实测性能(Ascend 910B,FP16):

模型 未融合FlashAttention 开FlashAttention 提升
Qwen2.5-7B (seq=2048) 34 tokens/s 89 tokens/s +162%
DeepSeek-V3-671B (seq=4096) 580 TPS 1420 TPS +145%

DeepSeek-V4更激进——CSA(4倍压缩)和HCA(128倍压缩)交替使用。128K序列下HCA把KV Cache从7.3GB压到57MB,TPOT < 10ms(950DT,16卡)。不压缩的Full Attention同场景TPOT > 80ms。

工程经验: CSA/HCA压缩率不是越大越好。128倍压缩(HCA)长序列收益明显,但短序列(<4K)反而比Full Attention慢12%——Compressor本身有计算开销。我们的做法:前两层用Window Attention(sliding_window=128),中间层按序列长度动态选CSA或HCA。

KV Cache 优化:省显存是手段,涨 batch 才是目的

Qwen2.5-7B,FP16下每个token的KV Cache约57KB。seq=2048、batch=32时光KV Cache就吃3.6GB。

三条优化路:

PagedAttention:MindIE推理引擎支持,KV Cache分block按需分配,消碎片。

KV Cache量化:INT8存KV Cache,显存省一半。910B实测:3.6GB → 1.8GB,省出1.8GB多跑16个batch。

KV Cache压缩:DeepSeek-V4的HCA 128倍压缩,128K序列KV Cache 57MB。

很多人以为KV Cache优化就是省显存。真正的收益是省出的显存能跑更大batch,更大batch吞吐更高。

# MindIE配置:开PagedAttention + KV Cache量化
from mindie import MindIERuntime, ModelConfig

config = ModelConfig(
    model_path="/path/to/deepseek-v3",
    # PagedAttention:block_size=128
    kv_cache_mode="paged",
    block_size=128,
    # KV Cache量化:INT8
    kv_cache_dtype="int8",
    # KV Cache压缩(DeepSeek-V4)
    use_hca=True,  # 128倍压缩
    hca_threshold=4096,  # seq>=4096才开HCA
)

runtime = MindIERuntime(config)

工程经验: Qwen2.5-7B在910B单卡,FP16 batch=8吞吐72 tokens/s;开KV Cache量化 + PagedAttention后batch开到32,吞吐147 tokens/s。算力没变,显存够用了,batch从8涨到32,吞吐涨104%。

算子融合:省 Task 调度开销

昇腾上每次算子下发走ACL→GE→Runtime调用链,开销12-15μs。30层Transformer几百次调用,光调度开销就3-5ms。

CANN的graph-autofusion自动融合框架:LayerNorm+MatMul、Softmax+Dropout+MatMul、GatingTopK等。DeepSeek-V4专门做了SAS(统一Window/Sparse/Compress Attention)、Compressor、HCPre/HCPost融合Kernel,已开源在cann-recipes-infer。

// graph-autofusion配置(GE环境变量)
export GE_FUSION_PASS_MODE=aggressive  // 激进融合策略
export GE_ENABLE_OP_FUSION=1          // 开算子融合
export ATB_FUSION_MODE=aggressive     // ATB也开激进融合

// 融合效果验证:看GE编译日志
// 搜索"Fusion success",看哪些算子融了
grep "Fusion success" ge_compile.log | wc -l

工程经验: DeepSeek-V4 MoE的GatingTopK融合前router输出落HBM再给topk读,融合后直接L1传数据,省一次HBM读写。单层省18μs,40层省720μs。decode每个token快0.7ms,1000个token差0.7秒。

显存管理 + Batch 调优

昇腾HBM管理跟GPU不一样。GPU的caching allocator基本是"申请-使用-释放";昇腾的DMA引擎要求显存预分配、零碎片、复用中间buffer。

实测带宽利用率:动态申请释放35% → 预分配+复用82%。910B的1.2TB/s带宽,82%是984GB/s可用,35%只有420GB/s——差一倍多。

# 开显存预分配(Runtime环境变量)
export ACL_MEM_POOL_SIZE=32GB       # 预分配32GB显存池
export ACL_MEM_POOL_REUSE_FLAG=1    # 复用中间buffer
export ACL_MEM_POOL_GUARANTEE_FLAG=1  # 预分配不释放

# Batch调优:动态batch
export ACL_DYNAMIC_BATCH=1
export ACL_DYNAMIC_BATCH_MAX=64     # 最大batch=64

Batch调优也不是越大越好。batch越大Attention的S矩阵越大,L1装不下就溢出:

batch 吞吐(tokens/s) TTFT(ms) 显存(GB)
4 52 8 6.8
8 72 12 20.1
32 147 38 39.2
64 138 72 OOM

batch=64吞吐反而降——KV Cache swap到Host内存。选batch看场景:在线对话batch=4-8(TTFT<100ms),离线批处理batch=16-32。我们的做法是动态batch:短输入batch=32,长输入batch=8。

量化:最后一板斧

量化方案 适用芯片 精度损失 性能收益
W8A16 910B, A3 <0.5% 吞吐+45%,显存-30%
W8A8C16 A3 <1% 吞吐+85%,显存-45%
Hybrid FP8-MXFP4 950PR/DT <1.5% 吞吐+120%,显存-60%

DeepSeek-V4 Flash在950DT(16卡,128K):Hybrid FP8-MXFP4 TPOT<10ms,FP16约35ms,3.5倍提升。

# 量化配置(torch_npu)
from torch_npu.contrib import transfer_param as tp

# W8A16量化
model = tp.param_quantize(
    model,
    algo="w8a16",
    dtype=torch.float16,
)

# W8A8C16量化(A3及以上)
model = tp.param_quantize(
    model,
    algo="w8a8c16",
    dtype=torch.float16,
)

# Hybrid FP8-MXFP4(950PR/DT)
model = tp.param_quantize(
    algo="hybrid_fp8_mxfp4",
    dtype=torch.bfloat16,
)

工程经验: 量化不是精度损失越低越好。W8A16精度损失<0.5%,但吞吐只涨45%;W8A8C16精度损失<1%,但吞吐涨85%。如果业务能接受1%的精度损失(比如推荐系统、文本摘要),W8A8C16更划算。

踩坑实录

坑1:FlashAttention在seq<512的时候反而不如标准Attention

seq太小,Cube利用率掉得厉害(M/N维度太小,Cube吃不饱),FlashAttention的额外调度开销(Tiling、softmax在线归一化)占比高。

解决:设export ATB_FlashATTENTION_SEQ_THRESHOLD=1024,seq>=1024才开FlashAttention,否则退化成标准Attention。

坑2:PagedAttention在batch动态变化的时候反而慢

batch从8涨到32,PagedAttention要动态分配block,分配开销占比高(~5ms)。

解决:预分配最大batch的block池(export MINDDIE_PAGED_POOL_SIZE=64GB),不动态分配。

坑3:W8A8C16量化在910B上编译失败

W8A8C16需要INT8的矩阵乘指令(CADIN8),910B的Cube不支持(只支持FP16/FP32),要A3及以上的芯片。

解决:910B上用W8A16(只量化权重,激活还是FP16),A3上用W8A8C16。

坑4:DeepSeek-V4的HCA压缩在seq=512的时候反而慢12%

Compressor的计算开销(CSA/HCA压缩+解压)比省下来的KV Cache访存开销大。

解决:设export DEEPEEK_HCA_THRESHOLD=4096,seq>=4096才开HCA,短序列用CSA(4倍压缩)或Full Attention。

https://atomgit.com/cann/ops-transformer

https://atomgit.com/cann/cann-recipes-infer

https://atomgit.com/cann/graph-autofusion

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐