文章目录
- 1. 前言:为什么选择Qwen2.5与昇腾?
- 2. 环境准备
- 2.1 获取昇腾算力资源
- 2.2 环境深度验证
- 2.3 安装核心依赖
- 3. 模型下载(Git LFS高速通道)
- 4. 部署实战:基于Torch-NPU的原生推理
- 4.1 编写推理脚本 `inference_npu.py`
- 4.2 运行结果分析
- 5. 进阶:生成稳定性与显存压力测试
- 5.1 编写测试脚本 `benchmark_npu.py`
- 5.2 测试结果与分析
- 5.3 进阶测试:显存占用监控
- 5.4 极限挑战:高并发下的显存与吞吐量压测
- 1. 编写极限压测脚本 `benchmark_extreme_npu.py`
- 2. 压测结果分析
- 3. 探底极限:NPU OOM 临界点测试
- 5.5 开发者实战感悟:关于“动态Shape”的性能抖动
- 6. 常见问题与踩坑总结 (Troubleshooting)
- Q1: 内存碎片导致OOM (RuntimeError: NPU out of memory)
- Q2: 首次推理极慢或卡死
- Q3: 报错 `ImportError: cannot import name 'TeQuantizer' ...`
- 总结
1. 前言:为什么选择Qwen2.5与昇腾?
眼下这国产化大潮是越来越猛了,昇腾(Ascend)算力卡俨然成了咱们国内AI圈的中流砥柱。而Qwen2.5(通义千问)作为阿里开源的“最强”系列模型,在各项基准测试里那是相当能打,尤其是7B这个版本,性能不错,显存占用还不大,简直是为开发者上手的“梦中情模”。
这次咱们直接拿Qwen2.5-7B-Instruct当主角,手把手带大家走一遍在AtomGit Notebook上的部署全流程。从搭环境、下模型、写推理代码,到用torch_npu跑原生推理和性能测试,最后再聊聊踩过的坑。目的就一个:整出一篇能复现、有干货的技术实战指南。
文章目录
- 1. 前言:为什么选择Qwen2.5与昇腾?
- 2. 环境准备
- 2.1 获取昇腾算力资源
- 2.2 环境深度验证
- 2.3 安装核心依赖
- 3. 模型下载(Git LFS高速通道)
- 4. 部署实战:基于Torch-NPU的原生推理
- 4.1 编写推理脚本 `inference_npu.py`
- 4.2 运行结果分析
- 5. 进阶:生成稳定性与显存压力测试
- 5.1 编写测试脚本 `benchmark_npu.py`
- 5.2 测试结果与分析
- 5.3 进阶测试:显存占用监控
- 5.4 极限挑战:高并发下的显存与吞吐量压测
- 1. 编写极限压测脚本 `benchmark_extreme_npu.py`
- 2. 压测结果分析
- 3. 探底极限:NPU OOM 临界点测试
- 5.5 开发者实战感悟:关于“动态Shape”的性能抖动
- 6. 常见问题与踩坑总结 (Troubleshooting)
- Q1: 内存碎片导致OOM (RuntimeError: NPU out of memory)
- Q2: 首次推理极慢或卡死
- Q3: 报错 `ImportError: cannot import name 'TeQuantizer' ...`
- 总结
2. 环境准备
想在昇腾平台上把AI模型跑得溜,第一步得先搞定开发环境。AtomGit(GitCode)提供的云端Notebook服务正好能解燃眉之急,而且它是开箱即用的,昇腾环境都给配好了。
2.1 获取昇腾算力资源
大家先登录GitCode平台,点一下右上角的头像,选那个 “我的 Notebook”。
如果是第一次来的朋友,系统给的免费试用额度还是挺足的。
在创建Notebook实例的时候,千万别手抖,一定要照着下面的配置选,不然兼容性可能会出问题:
- 计算资源:选
NPU basic · 1 * NPU 910B · 32v CPU · 64GB- 说明:这里使用的是昇腾 Atlas 800T A2训练/推理服务器(搭载 Atlas 800T 处理器),算力非常强劲。
- 注意:这一步必须选NPU资源,要是选了CPU,后面的torch_npu代码是跑不起来的。
- 系统镜像:选
euler2.9-py38-torch2.1.0-cann8.0-openmind0.6-notebook- 这个镜像里 Python 3.8、PyTorch 2.1.0 (Ascend适配版)、CANN 8.0 驱动这些都已经装好了,省得咱们再去折腾驱动安装那些麻烦事。
- 存储配置:建议选
50GB的存储空间,大模型文件毕竟不小,大点儿保险。
点完“立即启动”,稍微等几分钟,就能进到JupyterLab界面了。
2.2 环境深度验证
打开终端(Terminal),咱们先给环境做个“体检”,这一步很关键,得确保NPU和PyTorch版本是对得上的。
1. 检查NPU物理状态
npu-smi info这时候应该能看到Atlas 800T显卡的信息,而且显存(Memory-Usage)应该是空的。
2. 验证PyTorch与NPU适配
在终端里跑一下这个Python单行脚本:
# 检查PyTorch版本python -c"import torch; print(f'PyTorch版本: {torch.__version__}')"# 检查torch_npu版本python -c"import torch_npu; print(f'torch_npu版本: {torch_npu.__version__}')"# 验证NPU可用性python -c"import torch; import torch_npu; print(f'NPU Available: {torch.npu.is_available()}')"这儿有几个关键点得盯着:
- PyTorch版本得是
2.1.0或者更高。 torch.npu.is_available()必须得返回True,不然就没法玩了。
2.3 安装核心依赖
虽说基础镜像里已经带了Torch,但要跑Qwen2.5这种新模型,transformers和accelerate库还得是用最新的。强烈建议大家用清华源或者阿里源来下载,速度快不少。
# 升级transformers以支持Qwen2.5 (必须大于4.37)pipinstall--upgrade transformers accelerate -i https://pypi.tuna.tsinghua.edu.cn/simple3. 模型下载(Git LFS高速通道)
因为modelscope的Python库在Python 3.8环境下有时候会闹别扭(比如报个TypeError啥的),为了稳妥起见,咱们直接用Git LFS (Large File Storage)从ModelScope仓库把模型克隆下来。
这种方式不挑Python版本,而且自带断点续传,在Notebook环境里用起来特别顺手。
1. 初始化Git LFS
在终端里敲这行命令,确把LFS装好:
gitlfsinstall只要看到输出Git LFS initialized.就妥了。
2. 克隆模型仓库
咱们把模型下到当前目录下的models文件夹里:
# 创建存储目录mkdir-p models/Qwen# 使用Git Clone下载 (ModelScope国内线路速度极快)gitclone https://www.modelscope.cn/Qwen/Qwen2.5-7B-Instruct.git ./models/Qwen/Qwen2.5-7B-Instruct提醒一句:下载的时候网别断。万一断了,进到目录里敲个git lfs pull就能接着下。
下载完事儿后,模型就在./models/Qwen/Qwen2.5-7B-Instruct这个位置。大家把这个路径记一下,待会儿写推理脚本的时候要用的。
4. 部署实战:基于Torch-NPU的原生推理
这一步,咱们直接用HuggingFace原生的transformers库,配合torch_npu来跑推理。
4.1 编写推理脚本inference_npu.py
这里有几个代码优化的小细节要注意:
- 得显式地指定
device="npu"。 - 用
torch.bfloat16(Atlas 800T对BF16支持比较好,还能防溢出)。 - 加上
torch.npu.synchronize(),不然计时可能不准。
importtorchimporttorch_npufromtransformersimportAutoTokenizer,AutoModelForCausalLMimporttime# 1. 配置路径与设备DEVICE="npu"MODEL_PATH="./models/Qwen/Qwen2.5-7B-Instruct"# 请确保路径与下载时一致print(f"Loading model from{MODEL_PATH}...")# 2. 加载Tokenizertokenizer=AutoTokenizer.from_pretrained(MODEL_PATH,trust_remote_code=True)# 3. 加载模型到NPU# Atlas 800T推荐使用 bfloat16model=AutoModelForCausalLM.from_pretrained(MODEL_PATH,device_map=DEVICE,torch_dtype=torch.bfloat16,trust_remote_code=True)print("Model loaded successfully!")# 4. 构造Promptprompt="请用Python写一个快速排序算法,并解释其原理。"messages=[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":prompt}]text=tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True)model_inputs=tokenizer([text],return_tensors="pt").to(DEVICE)# 5. 推理生成print("Generating response...")# NPU是异步执行的,计时前需要同步torch.npu.synchronize()start_time=time.time()generated_ids=model.generate(model_inputs.input_ids,max_new_tokens=512,do_sample=True,temperature=0.7,top_p=0.9)# 计时结束同步torch.npu.synchronize()end_time=time.time()# 6. 解码输出generated_ids=[output_ids[len(input_ids):]forinput_ids,output_idsinzip(model_inputs.input_ids,generated_ids)]response=tokenizer.batch_decode(generated_ids,skip_special_tokens=True)[0]print("-"*20+" Output "+"-"*20)print(response)print("-"*20+" Stats "+"-"*20)print(f"Time taken:{end_time-start_time:.2f}s")4.2 运行结果分析
跑一下脚本:
python inference_npu.py看看实际运行结果(参考):
看看输出日志,咱们能确认这么几件事儿:
- 模型加载成功:日志里打出了
Model loaded successfully!,而且关于“快速排序算法”的代码和原理解释都出来了,逻辑没毛病,说明 Qwen2.5 在 NPU 上脑子很清醒。 - 首次运行耗时(JIT编译):
- 截图里那个
Time taken: 31.59 s其实是包含了模型推理时间,外加**首次算子编译(Operator Compilation)**的时间。 - 在昇腾架构里,第一次跑 PyTorch 代码的时候,CANN 会自动去编译和优化计算图里的算子。这是正常操作。
- 预期优化:你要是再跑一次同样的脚本,因为算子已经缓存了,耗时立马就能降下来(通常几秒钟就完事)。
- 截图里那个
结论:脚本跑得挺顺,没报 Traceback 错误,生成的内容也对路,这标志着 Qwen2.5-7B 在Atlas 800T 上算是部署成功了!
5. 进阶:生成稳定性与显存压力测试
光跑通了还不行,咱们得看看NPU的性能到底咋样。下面这个脚本能算出Tokens/s (TPS),而且我还加了个 Warmup(预热)环节,模拟真实的服务状态。
5.1 编写测试脚本benchmark_npu.py
importtorchimporttorch_npufromtransformersimportAutoTokenizer,AutoModelForCausalLMimporttime DEVICE="npu"MODEL_PATH="./models/Qwen/Qwen2.5-7B-Instruct"print("Loading model...")tokenizer=AutoTokenizer.from_pretrained(MODEL_PATH,trust_remote_code=True)model=AutoModelForCausalLM.from_pretrained(MODEL_PATH,device_map=DEVICE,torch_dtype=torch.bfloat16,trust_remote_code=True)# ----------------- 预热 (Warmup) -----------------# 目的:触发算子编译,填充缓存print("Warming up (compiling operators)...")dummy_input=tokenizer("Hello world",return_tensors="pt").to(DEVICE)model.generate(**dummy_input,max_new_tokens=20)torch.npu.synchronize()print("Warmup done.")# ----------------- 正式测试 -----------------prompt="作为一名资深导游,请详细介绍一下北京故宫的旅游攻略,包括必去景点、路线规划和注意事项,字数不少于500字。"inputs=tokenizer(prompt,return_tensors="pt").to(DEVICE)input_len=inputs.input_ids.shape[1]print("Starting Benchmark...")torch.npu.synchronize()start_time=time.time()output=model.generate(**inputs,max_new_tokens=512,do_sample=False,# 使用Greedy Search以获得稳定性能指标use_cache=True)torch.npu.synchronize()end_time=time.time()# ----------------- 结果计算 -----------------total_tokens=output.shape[1]new_tokens=total_tokens-input_len elapsed_time=end_time-start_time tps=new_tokens/elapsed_timeprint(f"\n{'='*20}Benchmark Report{'='*20}")print(f"Input Tokens:{input_len}")print(f"Generated Tokens:{new_tokens}")print(f"Total Time:{elapsed_time:.4f}s")print(f"Throughput:{tps:.2f}tokens/s")print(f"{'='*58}")5.2 测试结果与分析
脚本跑完之后,我们主要关注以下几个关键指标,以验证环境的稳定性:
来解读一下这几个关键指标:
生成完整性 (Generated Tokens)
- 结果:代码设置了
max_new_tokens=512,模型成功生成了满额的 token,期间没有出现中断或报错。这证明了在Atlas 800T上,Qwen2.5-7B 进行长文本推理是完全稳定的。
- 结果:代码设置了
预热机制 (Warmup)
- 结果:日志显示
Warmup done,说明算子编译(JIT)机制正常工作。在预热完成后,后续的推理过程非常平滑,没有出现首次运行时的卡顿现象。
- 结果:日志显示
5.3 进阶测试:显存占用监控
在实际干活的时候,除了速度,咱们最担心的就是显存爆没爆。Atlas 800T这显存可是够大的(32GB/64GB),跑个7B模型那叫一个宽裕。咱们改几行代码,就能精确监控峰值显存:
# 在推理结束后(end_time = time.time() 之后)加入以下代码:max_memory=torch.npu.max_memory_allocated()/1024/1024/1024print(f"Peak NPU Memory:{max_memory:.2f}GB")看看实测结果(参考):
深度解析一下这个数据:
Peak NPU Memory: 14.29 GB
- 数据解读:这个数是模型权重 (Weights)+KV Cache+推理时的临时激活值 (Activations)加起来的总和。
- 资源利用率:Qwen2.5-7B (BF16) 光权重大概就得占 14GB 左右。显示峰值才 14.29 GB,说明在 batch_size=1, input_len=30 这种轻量级测试里,额外的显存开销几乎可以忽略不计。
- 潜力评估:
- 余量充足:对于 32GB 显存的 Atlas 800T 卡来说,咱们还剩下大概17GB的空闲地盘。
- 扩容建议:这 17GB 的“富余”意味着咱们完全可以搞点大动作:
- 把Batch Size加到 16 甚至 32,吞吐量直接起飞。
- 部署参数量更大的14B 模型(通常占 ~28GB 显存,刚好能塞进单卡)。
- 上vLLM框架,把这些空闲显存当成 PagedAttention 的 KV Cache 池,支持数千 token 的超长文本推理。
性能稳定性验证
- 在持续的显存监控过程中,推理过程非常平稳,说明 NPU 热身之后,输出那是相当稳定的。
5.4 极限挑战:高并发下的显存与吞吐量压测
既然官方给到了64GB 显存的豪华配置,而 Qwen2.5 采用了 GQA(分组查询注意力)技术极大地节省了显存,普通的 Batch Size 根本“喂不饱”这块卡。
为了探探这块卡的底,咱们搞个地狱级压测:
- 构造长文本输入:输入长度拉到 1024 tokens。
- 超大 Batch Size:直接拉到64甚至更高。
1. 编写极限压测脚本benchmark_extreme_npu.py
importtorchimporttorch_npufromtransformersimportAutoTokenizer,AutoModelForCausalLMimporttime DEVICE="npu"MODEL_PATH="./models/Qwen/Qwen2.5-7B-Instruct"print("Loading model...")tokenizer=AutoTokenizer.from_pretrained(MODEL_PATH,trust_remote_code=True)tokenizer.pad_token_id=tokenizer.eos_token_id model=AutoModelForCausalLM.from_pretrained(MODEL_PATH,device_map=DEVICE,torch_dtype=torch.bfloat16,trust_remote_code=True)# ----------------- 极限构造 -----------------# 1. 构造一个长 Prompt (约 1000 tokens)long_prompt="Explain the theory of relativity in detail. "*50# 2. 设置超大 Batch Size (针对 64GB 显存)BATCH_SIZE=64print(f"Preparing inputs for Batch Size ={BATCH_SIZE}with Long Context...")input_list=[long_prompt]*BATCH_SIZE inputs=tokenizer(input_list,return_tensors="pt",padding=True,max_length=2048,truncation=True).to(DEVICE)input_len=inputs.input_ids.shape[1]print(f"Input Sequence Length:{input_len}tokens")# 预热print("Warming up...")model.generate(**inputs,max_new_tokens=10,do_sample=False)torch.npu.synchronize()print("Starting Extreme Benchmark...")torch.npu.synchronize()start_time=time.time()output=model.generate(**inputs,max_new_tokens=256,do_sample=False# 压测通常关闭采样)torch.npu.synchronize()end_time=time.time()# 显存监控max_memory=torch.npu.max_memory_allocated()/1024**3# 计算结果total_new_tokens=(output.shape[1]-input_len)*BATCH_SIZE elapsed_time=end_time-start_time tps=total_new_tokens/elapsed_timeprint(f"\n{'='*20}Extreme Benchmark Report{'='*20}")print(f"Batch Size:{BATCH_SIZE}")print(f"Input Length:{input_len}")print(f"Peak NPU Memory:{max_memory:.2f}GB")print(f"Total Generated Tokens:{total_new_tokens}")print(f"Total Time:{elapsed_time:.4f}s")print(f"System Throughput:{tps:.2f}tokens/s")print(f"{'='*64}")2. 压测结果分析
数据解读:
- 深不见底的显存潜力 (20.38 GB):这结果属实让人惊讶!即便我们把Batch Size 拉到了 64,输入长度也接近 500 tokens,显存占用竟然才刚刚突破20GB(仅占总显存的 30%)。
- 原因:这得益于 Qwen2.5 优秀的GQA (Grouped Query Attention)显存优化技术,以及 Atlas 800T 庞大的 64GB 显存池。
- 结论:这意味着在生产环境下,这块卡理论上能支撑Batch Size = 200+的超高并发,或者处理32k级别的超长上下文,硬件上限深不可测。
- 吞吐量狂飙 (785 tokens/s):相比单 Batch,系统整体吞吐量提升了十几倍。每秒能生成近 800 个 token,足以支撑一个中型规模的 AI 对话服务集群。
3. 探底极限:NPU OOM 临界点测试
我们进一步尝试将Batch Size 拉升至 128且Input Length 增加至 1800+,试图彻底“榨干”显存。结果在显存分配阶段触发了 OOM 保护:
RuntimeError: NPU out of memory. Tried to allocate 21.68 GiB (NPU 0; 60.97 GiB total capacity; 44.94 GiB already allocated...实战结论与建议:
- 安全水位线:在64GB 显存的 Atlas 800T 上,部署 Qwen2.5-7B (BF16) 的最佳并发区间建议控制在Batch Size 64 ~ 96之间。
- 极限性能:在此区间内,既能享受到700+ tokens/s的极致吞吐,又能保证显存有 30% 左右的安全余量,防止突发长文本导致的 OOM。
5.5 开发者实战感悟:关于“动态Shape”的性能抖动
在反复折腾测试的时候,你可能会碰上个挺有意思的现象:当你改了输入Prompt的长度,推理怎么突然变慢了?
这就是昇腾 NPU 在开发过程中最常见的一个“坑”,也是它的架构特性决定的——算子编译(JIT Compilation)。
- 现象:第一次输 10 个 token,慢吞吞;第二次输 10 个 token,嗖嗖的。但第三次输 100 个 token,又变慢了。
- 原因:NPU 得针对不同的 Input Shape(输入形状)去编译计算图。如果输入的长度老在变,CANN 就得苦哈哈地不停编译。
- 实战建议:
- 别慌:卡没坏,代码也没写错。
- 生产环境策略:在实际业务里,通常会用“分桶(Bucketing)”或者“Padding”把输入长度固定在几个档位(比如 128, 512, 1024),这样就能避免反复编译,性能也就稳了。
6. 常见问题与踩坑总结 (Troubleshooting)
Q1: 内存碎片导致OOM (RuntimeError: NPU out of memory)
现象:模型加载了一半,或者生成长文本的时候突然报错,但npu-smi一看物理显存明明还有空地儿。
原因:PyTorch原生的内存分配器在NPU上可能会产生碎片。
解决方案:
设个环境变量PYTORCH_NPU_ALLOC_CONF,限制一下内存块切分的大小。
# 在终端执行或加入 .bashrcexportPYTORCH_NPU_ALLOC_CONF=max_split_size_mb:128Q2: 首次推理极慢或卡死
原因:昇腾架构的“静态图/动态图”算子编译机制。只要遇到新shape的输入,底层编译就会触发。
解决方案:
- 耐心等待:正常现象,让它飞一会儿。
- 固定Shape:生产环境里,尽量固定输入长度或者用分档(Bucketing)来减少编译次数。
Q3: 报错ImportError: cannot import name 'TeQuantizer' ...
原因:transformers版本太老了,跟Qwen2.5的代码八字不合。
解决方案:pip install --upgrade transformers,升级一下就好。
总结
这篇文章咱们详细跑了一遍Qwen2.5-7B-Instruct在AtomGit (Atlas 800T)环境下的部署全流程。事实证明,只要环境配置对路、用ModelScope下载、配合Torch-NPU原生推理,国产算力跑最新的开源大模型完全没压力。
最后再划个重点:
- 环境:EulerOS + CANN 8.0 是目前的“黄金搭档”。
- 代码:记得加
torch.npu.synchronize(),不然性能耗时都不准。 - 调优:BF16 + FlashAttention 是提升性能的关键法宝。
作者简介:本文作者为AI技术专家,专注于大型语言模型和国产AI芯片的应用研究,具有丰富的开发经验。
版权声明:本文内容基于开源协议,欢迎转载和分享,请注明出处。
联系方式:如有技术问题或合作需求,欢迎通过GitCode平台联系作者。
免责声明:本文基于实际测试数据编写,所有代码和配置均经过验证。重点在于给社区开发者传递基于昇腾跑通和测评的方法和经验,欢迎开发者在本模型基础上交流优化