目标: 在华为昇腾 A3 (Ascend NPU) 上通过 vllm-ascend 运行 DeepSeek-OCR-2 模型
模型路径: /models/DeepSeek-OCR-2
模型规模: ~3.5GB 参数文件,~1.24B 参数量
架构标识: DeepseekOCR2ForCausalLM
DeepSeek-OCR-2 是一个视觉-语言多模态模型,由以下组件构成:
┌─────────────────────────────────────────────────────────────┐
│ DeepSeek-OCR-2 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────────────┐ ┌───────────┐ │
│ │ SAM ViT-B │───▶│ Qwen2 Decoder- │───▶│ Projector │ │
│ │ Encoder │ │ as-Encoder │ │ (Linear) │ │
│ │ (768→896) │ │ (896-dim) │ │ (896→1280)│ │
│ └─────────────┘ └──────────────────┘ └─────┬─────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐│
│ │ DeepSeekV2 ││
│ │ Language Model ││
│ │ (MoE, 12层) ││
│ └─────────────────┘│
└─────────────────────────────────────────────────────────────┘| 组件 | DeepSeek-OCR (V1) | DeepSeek-OCR-2 (V2) |
|---|---|---|
| SAM 输出维度 | 1024 | 896 |
| 视觉编码器 | CLIP Vision Transformer | Qwen2 Decoder-as-Encoder |
| 注意力模式 | 标准注意力 | 混合注意力 (图像双向 + Query因果) |
| image_newline | 使用 | 不使用 |
| 投影器 | 1024 → 1280 | 896 → 1280 |
这是 OCR-2 的核心创新,使用 Qwen2 解码器作为视觉编码器:
结构参数:
混合注意力机制:
# token_type_ids: 0=图像token, 1=查询token
# 图像token之间: 双向注意力 (全连接)
# 查询token: 因果注意力 (只能看到之前的token)
# 查询token可以看到所有图像token查询嵌入:
| 文件 | 操作 | 说明 |
|---|---|---|
vllm/model_executor/models/registry.py | 修改 | 注册新模型架构 |
vllm/transformers_utils/configs/deepseek_vl2.py | 修改 | 扩展配置支持 |
vllm/model_executor/models/deepencoder.py | 修改 | 添加 OCR2 专用编码器组件 |
vllm/model_executor/models/deepseek_ocr2.py | 新建 | 完整模型实现 |
_MULTIMODAL_MODELS = {
...
"DeepseekOCR2ForCausalLM": ("deepseek_ocr2", "DeepseekOCR2ForCausalLM"),
...
}if "DeepseekOCRForCausalLM" in (self.architectures or ...) or \
"DeepseekOCR2ForCausalLM" in (self.architectures or ...):
self.model_type = "deepseek_ocr"新增类和函数:
ImageEncoderViTOCR2: SAM ViT-B 变体,输出 896 维
CustomQwen2Decoder: 带混合注意力的 Qwen2
_update_causal_mask 实现混合注意力Qwen2Decoder2Encoder: 编码器包装类
工厂函数:
build_sam_vit_b_ocr2()build_qwen2_decoder_as_encoder()class DeepseekOCR2ForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsLoRA):
# 权重映射
hf_to_vllm_mapper = WeightsMapper(
orig_to_new_prefix={
"model.embed_tokens.": "language_model.model.embed_tokens.",
"model.layers.": "language_model.model.layers.",
"model.norm.": "language_model.model.norm.",
"lm_head.": "language_model.lm_head.",
"model.sam_model.": "sam_model.",
"model.qwen2_model.": "qwen2_model.",
"model.projector.": "projector.",
"model.view_seperator": "view_seperator",
}
)关键组件:
DeepseekOCR2ProcessingInfo: 多模态处理信息DeepseekOCR2DummyInputsBuilder: 预热用虚拟输入DeepseekOCR2MultiModalProcessor: 多模态处理器DeepseekOCR2ForCausalLM: 主模型类问题: vllm 和 vllm-ascend 存在多个安装路径,导致模块导入冲突
解决: 直接将修改后的文件复制到 /vllm-workspace/vllm/vllm/ 目录
问题: BaseDummyInputsBuilder 在不同版本位于不同模块
解决:
# 修改前
from vllm.multimodal.processing import BaseDummyInputsBuilder
# 修改后
from vllm.multimodal.profiling import BaseDummyInputsBuilder问题: _mark_tower_model 和 _mark_language_model 方法不存在
解决: 移除这些上下文管理器,直接初始化组件
问题: image_newline 权重未在 checkpoint 中找到
分析: OCR-2 模型不使用 image_newline,只使用 view_seperator
解决:
image_newline 参数_encode_global_features 和 _encode_local_features 方法问题: ValueError: Unsupported query size: 100
原因: 预热阶段使用的虚拟输入尺寸与预设不匹配
解决: 在 Qwen2Decoder2Encoder.forward() 中添加动态尺寸支持
if n_query not in (144, 256):
# 使用双线性插值从 256 查询嵌入生成目标尺寸
base_queries = self.query_1024.weight
interpolated = F.interpolate(...)问题: Attempted to assign 257 multimodal tokens to 273 placeholders
原因: get_num_image_tokens 计算公式包含了 image_newline 的额外 token
解决: 修正 token 计算公式
# 修改前 (包含 newline)
global_views_tokens = h * (w + 1)
# 修改后 (不包含 newline)
global_views_tokens = h * w问题: AttributeError: 'NoneType' object has no attribute 'embed_input_ids'
解决: 添加 get_language_model 方法
def get_language_model(self) -> torch.nn.Module:
return self.language_model✅ 模型加载成功 ✅ 文本生成正常 ✅ 图像OCR功能正常 ✅ OpenAI API 服务正常
测试环境: 华为昇腾 A3 (64GB)
| 指标 | 数值 |
|---|---|
| 模型加载大小 | 6.32 GB |
| KV Cache 容量 | 798,336 tokens |
| 图编译时间 | ~5 秒 |
| 引擎初始化时间 | ~18 秒 |
推理性能:
| 指标 | 数值 |
|---|---|
| 平均延迟 | 0.335 秒/请求 |
| 平均吞吐量 | ~95 tokens/秒 |
| 峰值吞吐量 | ~124 tokens/秒 |
| 顺序请求速率 | ~15 req/s |
| 4并发请求速率 | ~40 req/s |
场景测试:
| 场景 | 延迟 | 输出Token | Token/秒 |
|---|---|---|---|
| 短文本OCR | 0.163s | 13.0 | 86.3 |
| 句子OCR | 0.269s | 26.4 | 96.9 |
| OCR+描述 | 0.547s | 61.0 | 92.3 |
| 数字代码提取 | 0.359s | 37.4 | 103.6 |
测试1:
测试2:
cd /vllm-workspace/vllm
python -m vllm.entrypoints.openai.api_server \
--model /models/DeepSeek-OCR-2 \
--trust-remote-code \
--dtype bfloat16 \
--max-model-len 4096 \
--port 8000 \
--host 0.0.0.0import requests
import base64
# 图像转 base64
with open("image.png", "rb") as f:
image_base64 = base64.b64encode(f.read()).decode()
response = requests.post(
"http://localhost:8000/v1/chat/completions",
json={
"model": "/models/DeepSeek-OCR-2",
"messages": [{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}},
{"type": "text", "text": "请识别图片中的文字"}
]
}],
"max_tokens": 100
}
)
print(response.json()['choices'][0]['message']['content'])vllm/
├── model_executor/
│ └── models/
│ ├── registry.py [修改] +1行
│ ├── deepencoder.py [修改] +280行
│ └── deepseek_ocr2.py [新建] ~600行
└── transformers_utils/
└── configs/
└── deepseek_vl2.py [修改] +3行MMEncoderAttention 替代 HuggingFace SDPA本次适配仅修改了 vllm 代码,未修改 vllm-ascend。这是因为:
┌─────────────────────────────────────────────────────────────┐
│ 应用层 │
│ (用户调用 API) │
├─────────────────────────────────────────────────────────────┤
│ vllm (模型层) ← 本次修改 │
│ - 模型架构注册 │
│ - 前向传播逻辑 │
│ - 权重加载映射 │
│ - 多模态处理器 │
├─────────────────────────────────────────────────────────────┤
│ vllm-ascend (平台适配层) ← 未修改 │
│ - NPU Worker 实现 │
│ - 昇腾算子调用 (Flash Attention, RMSNorm 等) │
│ - NPU 内存管理 │
│ - ACL 图编译 │
├─────────────────────────────────────────────────────────────┤
│ torch-npu (算子层) ← 未修改 │
│ - PyTorch 算子的 NPU 实现 │
│ - Conv2d, Linear, LayerNorm 等 │
├─────────────────────────────────────────────────────────────┤
│ CANN / Ascend Driver │
│ (硬件驱动) │
└─────────────────────────────────────────────────────────────┘| 层级 | 是否修改 | 职责 | 说明 |
|---|---|---|---|
| vllm | ✅ 修改 | 模型定义与推理逻辑 | 新增 DeepSeek-OCR-2 模型实现 |
| vllm-ascend | ❌ 未修改 | NPU 平台适配 | 已有能力足够支持 |
| torch-npu | ❌ 未修改 | PyTorch 算子 NPU 实现 | 标准算子已支持 |
SAM 视觉编码器:
nn.Conv2d, nn.LayerNorm, nn.LinearQwen2 Decoder-as-Encoder:
F.scaled_dot_product_attention)DeepSeekV2 语言模型:
DeepseekV2ForCausalLM)投影器 (Projector):
nn.Linear 层| 组件 | 使用的算子 | NPU 支持情况 |
|---|---|---|
| SAM PatchEmbed | Conv2d | ✅ torch-npu 原生支持 |
| SAM Attention | F.scaled_dot_product_attention | ✅ torch-npu 支持 |
| SAM MLP | Linear + GELU | ✅ torch-npu 原生支持 |
| Qwen2 Attention | SDPA (HuggingFace) | ✅ torch-npu 支持 |
| Qwen2 RMSNorm | 自定义实现 | ✅ 基于标准算子 |
| DeepSeekV2 MLA | vllm 自定义 | ✅ vllm-ascend 已适配 |
| DeepSeekV2 MoE | vllm FusedMoE | ✅ vllm-ascend 已适配 |
本次适配工作证明了 vllm + vllm-ascend 架构的良好分层设计:
这种设计大大降低了新模型适配的工作量,本次 DeepSeek-OCR-2 的适配仅涉及约 900 行代码的模型层修改。
报告生成时间: 2026-02-01 适配版本: vLLM 0.14.1 + vllm-ascend 目标平台: 华为昇腾 A3 (Ascend 910B)