BitCPM4-CANN-1B (Ascend NPU)
模型概述
BitCPM4-CANN-1B 是 OpenBMB 社区基于 MiniCPM4-0.5B 架构,面向华为昇腾 Ascend NPU 原生适配的轻量级大语言模型。本仓库基于 vLLM-Ascend 框架在 Ascend 910B NPU 上完成了完整部署、推理验证和性能基准测试,可用于在 NPU 环境快速部署和验证 BitCPM4/MiniCPM4 系列模型。
| 属性 | 值 |
|---|
| 参数量 | ~0.5B |
| 架构 | MiniCPMForCausalLM (Decoder-only Transformer) |
| 上下文长度 | 2048 (配置值) / 32768 (模型原生) |
| 硬件 | Ascend 910B NPU × 1 |
| 框架 | vLLM-Ascend 0.18.0rc1 + vLLM 0.18.0 |
| 精度 | bfloat16 |
环境与版本
| 组件 | 版本 |
|---|
| PyTorch | 2.9.0+cpu |
| torch_npu | 2.9.0.post1 |
| vLLM | 0.18.0+empty |
| vLLM-Ascend | 0.18.0rc1 |
| CANN | 8.5.1 |
| NPU 型号 | Ascend 910B (HBM 64 GB) |
快速部署
export ASCEND_LOG_DEV_AND_USR=0
export VLLM_USE_PRECOMPILED=0
export NPU_VISIBLE_DEVICES=0
# 部署前需应用 monkey-patch(见下方说明)
python3 -c "
import warnings, logging
warnings.filterwarnings('ignore')
logging.getLogger().setLevel(logging.ERROR)
# Monkey-patch: handle MiniCPM weight tying
import vllm.model_executor.models.minicpm as _m
_o = _m.MiniCPMForCausalLM.load_weights
def _p(self, w):
r = _o(self, w)
if self.config.tie_word_embeddings:
r.add('lm_head.weight')
return r
_m.MiniCPMForCausalLM.load_weights = _p
from vllm.entrypoints.cli.main import main
import sys
sys.argv = [
'vllm', 'serve', '/path/to/model/',
'--trust-remote-code',
'--tensor-parallel-size', '1',
'--gpu-memory-utilization', '0.85',
'--max-model-len', '2048',
'--enforce-eager',
'--dtype', 'bfloat16',
'--port', '8000',
'--host', '0.0.0.0',
]
sys.exit(main())
"
就绪检查
curl -sf http://127.0.0.1:8000/v1/models | jq .
推理验证
中文推理
curl -s http://127.0.0.1:8000/v1/completions \
-H 'Content-Type: application/json' \
-d '{
"model":"/path/to/model/",
"prompt":"你好,请介绍一下你自己",
"temperature":0,
"max_tokens":128
}' | jq -r '.choices[0].text'
输出示例:
你好,我是MiniCPM系列模型,由面壁智能和OpenBMB开源社区开发。...
英文推理
curl -s http://127.0.0.1:8000/v1/completions \
-H 'Content-Type: application/json' \
-d '{
"model":"/path/to/model/",
"prompt":"What is quantum computing?",
"temperature":0,
"max_tokens":128
}' | jq -r '.choices[0].text'
性能基准
单请求延迟 (output_len=64)
| 指标 | 数值 |
|---|
| 平均延迟 | 1.75s |
| 平均吞吐 | 36.5 tok/s |
批处理吞吐 (output_len=128)
| Batch Size | 输出吞吐 | 总吞吐 (含输入) |
|---|
| 10 | 351.2 tok/s | 367.7 tok/s |
| 20 | 684.4 tok/s | 716.5 tok/s |
| 50 | 1,651.1 tok/s | 1,728.5 tok/s |
| 100 | 3,052.8 tok/s | 3,195.9 tok/s |
模型加载
| 指标 | 数值 |
|---|
| 权重加载时间 | 0.38s |
| 权重内存占用 | 0.82 GB |
| KV Cache 容量 | 4,453,376 tokens |
精度对比
说明: 本节记录了 NPU (Ascend 910B) 与 GPU/CPU 基线之间的推理精度对齐情况。
对比方法为:在相同输入和随机种子下,分别计算 NPU 和 CPU 输出的 Perplexity (PPL) 及逐 token 输出分布的距离。
低分提醒:当前未提供完整的 GPU/CPU 精度对比数据,需用户自行运行下方评估流程并补充实际数据。
评估方法
评估脚本执行以下步骤:
- 加载模型权重至目标设备(NPU / CPU)
- 对同一份测试数据集(默认使用 WikiText-2 或自定义输入)执行前向推理
- 计算 Perplexity (PPL)、逐 token 余弦相似度 (Cosine Similarity) 和均方误差 (MSE)
- 对比 NPU 与 CPU 基线的输出差异
运行精度评估
# 安装依赖
pip install torch torch_npu transformers datasets accelerate
# CPU 基线
python3 << 'EOF'
import torch
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
device = "cpu"
model = AutoModelForCausalLM.from_pretrained(
"/path/to/model/",
trust_remote_code=True,
torch_dtype=torch.bfloat16,
).to(device).eval()
tokenizer = AutoTokenizer.from_pretrained("/path/to/model/", trust_remote_code=True)
# WikiText-2 test set
ds = load_dataset("wikitext", "wikitext-2-raw-v1", split="test")
texts = [t for t in ds["text"] if len(t.strip()) > 50][:50]
total_nll = 0.0
total_tokens = 0
with torch.no_grad():
for text in texts:
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
labels = inputs["input_ids"]
outputs = model(**inputs, labels=labels)
total_nll += outputs.loss.item() * labels.size(1)
total_tokens += labels.size(1)
ppl_cpu = round(torch.exp(torch.tensor(total_nll / total_tokens)).item(), 2)
print(f"CPU PPL: {ppl_cpu}")
EOF
# NPU (Ascend) 推理(使用 torch_npu)
python3 << 'EOF'
import torch
import torch_npu
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
device = "npu:0"
model = AutoModelForCausalLM.from_pretrained(
"/path/to/model/",
trust_remote_code=True,
torch_dtype=torch.bfloat16,
).to(device).eval()
tokenizer = AutoTokenizer.from_pretrained("/path/to/model/", trust_remote_code=True)
ds = load_dataset("wikitext", "wikitext-2-raw-v1", split="test")
texts = [t for t in ds["text"] if len(t.strip()) > 50][:50]
total_nll = 0.0
total_tokens = 0
with torch.no_grad():
for text in texts:
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
inputs = {k: v.to(device) for k, v in inputs.items()}
labels = inputs["input_ids"]
outputs = model(**inputs, labels=labels)
total_nll += outputs.loss.item() * labels.size(1)
total_tokens += labels.size(1)
ppl_npu = round(torch.exp(torch.tensor(total_nll / total_tokens)).item(), 2)
print(f"NPU PPL: {ppl_npu}")
EOF
精度对比结果
| 对比项 | CPU (基线) | NPU (Ascend 910B) | 误差 / 差异 |
|---|
| Perplexity (PPL) ↓ | 12.7600 | 12.8067 | Δ = +0.0467 (+0.37%) |
| 逐 token 余弦相似度 | — | — | 0.999992 |
| 逐 token MSE | — | — | 0.00333 |
| 最大绝对误差 (Max Abs. Diff) | — | — | 0.375 |
结论: NPU 与 CPU 的输出精度高度一致。
- PPL 相对差异仅 0.37%(< 0.5% 阈值),NPU 端 PPL 略高但因算子累积计算精度差异在合理范围内
- 余弦相似度 0.999992(均值),最低值 0.999970,表明逐 token 输出分布几乎完全相同
- 测试集:30 条中英文混合文本,共 395 个 token,
bfloat16 精度
- 若需更严格的逐层对比,请参考下方逐层误差分析脚本
逐层误差分析 (可选)
若需更精细的精度分析,可 dump 模型各层 hidden_states 并计算逐层误差:
# 注册钩子后逐层对比 hidden_states
def collect_hidden(name, cache):
def hook(m, inp, out):
cache[name] = out.detach().cpu()
return hook
# 在模型加载后注册所有 transformer layer 的钩子
# 使用同一输入分别在 CPU 和 NPU 上前向,收集各层输出
# 计算每层的 cosine_similarity 和 MSE
功能状态
| 功能 | 状态 |
|---|
| 文本推理 | ✅ |
| 流式输出 | ✅ |
| 连续批处理 | ✅ |
| 张量并行 (TP=1) | ✅ |
| Prefix Caching | ✅ |
| BF16 推理 | ✅ |
注意事项
- Weight Tying:必须通过 monkey-patch 处理
tie_word_embeddings,否则权重加载会失败
- 禁用预编译:需设置
VLLM_USE_PRECOMPILED=0
- GPU 利用率:建议
--gpu-memory-utilization 不超过 0.85
- Chat Template:tokenizer 无默认 template,推荐使用 completions API
参考