多模态模型在昇腾上的部署架构
本文介绍了在昇腾910上部署多模态模型的架构设计。多模态模型包含视觉编码器、文本编码器/融合层和语言模型三个子网络,其中语言模型占95%的计算量和显存。作者提出三阶段流水线架构,将三个子网络拆分为独立的.om文件,通过HBM内存共享传递中间结果,避免显存浪费并支持独立扩展。实现上采用动态批处理策略,用异步队列解耦编码和生成阶段,生成阶段统一批处理以提高利用率。测试数据显示,双卡TP+PagedAt

前言
CLIP、BLIP、LLaVA 这类多模态模型,推理时既有视觉处理又有语言处理,部署比纯 NLP 或纯 CV 模型复杂得多。本文讲一种在昇腾 910 上部署多模态模型的架构设计。
一、多模态推理的特殊之处
单模态模型的推理是「一种输入 → 一种处理 → 一种输出」。多模态模型是「多种输入 → 多种处理 → 融合 → 一种输出」。
图像 ──→ 视觉编码器 ──→ 视觉特征 ──┐
├──→ 融合层 ──→ 语言模型 ──→ 文本输出
文本 ──→ 文本编码器 ──→ 文本特征 ──┘
三个独立的子网络(视觉编码器、文本编码器/融合层、语言模型),各自有不同的计算特征:
| 子网络 | 计算密集度 | 显存占用 | 典型模型 |
|---|---|---|---|
| 视觉编码器 | 中等 | 低(~500MB) | ViT-L/14 |
| 融合层 | 低 | 极低(~50MB) | Q-Former / Cross-Attention |
| 语言模型 | 高 | 高(~14GB) | LLaMA-7B / Vicuna |
语言模型占了 95% 的计算量和显存。视觉编码器和融合层虽然轻量,但必须先于语言模型执行,不能并行。
二、部署架构:三阶段流水线
架构图
请求入口
│
▼
[预处理服务] ──── CPU / NPU(ops-adv resize)
│
├──── 图像 → [视觉编码器] ──→ 视觉特征
│ NPU:0
│
└──── 文本 → [文本编码器] ──→ 文本特征
NPU:0
│
▼
[融合层] ──→ 融合特征
NPU:0
│
▼
[语言模型] ──→ 生成文本
NPU:0 + NPU:1(张量并行)
为什么用流水线而不是单模型
把三个子网络打包成一个 .om 文件当然可以,但有几个问题:
- 显存浪费:视觉编码器只在第一步用,之后 90% 的时间它占着显存什么都不做
- 无法独立扩展:语言模型是瓶颈,需要多卡并行,但视觉编码器不需要
- 编译时间长:CLIP + LLaMA 的合并模型,ATC 编译要 40 分钟;拆开编译各 5 分钟
拆成三个独立的 .om 文件,通过内存共享(HBM 上的共享 tensor)传递中间结果,既省显存又灵活。
三、内存共享的实现
三个子模型之间的数据传递,不走网络也不走磁盘,直接在 HBM 上共享。
共享机制
import torch
import torch_npu
# 视觉编码器输出 → 融合层输入
visual_encoder = torch.jit.load("clip_visual.om", map_location="npu:0")
visual_features = visual_encoder(image_tensor) # 输出在 NPU:0 的 HBM 上
# 融合层直接从同一张 NPU 的 HBM 读数据
fusion_layer = torch.jit.load("fusion.om", map_location="npu:0")
fused_features = fusion_layer(visual_features, text_features) # 零拷贝
# 语言模型从同一张 NPU 读融合特征
llm = torch.jit.load("llama_7b.om", map_location="npu:0")
output = llm(fused_features) # 零拷贝
三个模型都在 npu:0 上,中间数据不需要跨设备搬运。visual_features 和 fused_features 的生命周期不重叠,GE 的内存规划会让它们复用同一块 HBM 空间。
跨卡场景
语言模型太大,单卡放不下,需要张量并行(TP)。假设用 2 卡 TP:
# 语言模型分布在 NPU:0 和 NPU:1
llm_part0 = torch.jit.load("llama_7b_part0.om", map_location="npu:0")
llm_part1 = torch.jit.load("llama_7b_part1.om", map_location="npu:1")
# 融合特征需要广播到两张卡
fused_features_npu0 = fused_features.to("npu:0")
fused_features_npu1 = fused_features.to("npu:1") # 跨卡拷贝,~1ms
跨卡拷贝的开销是 ~1ms(HCCS 带宽 392GB/s),相比语言模型的推理时间(~60ms)可以忽略。
四、Batch 策略
多模态推理的 batch 策略比单模态复杂。图像和文本的处理速度不同——一张图像编码 15ms,一段文本编码 3ms,语言模型生成 60ms。
动态 Batch 方案
时间轴 →
图像请求: [编码15ms] [...........生成60ms...........]
文本请求: [编码3ms] [...........生成60ms...........]
图像和文本的编码时间不同,但生成阶段共享同一个 batch
实现方式:用异步队列解耦编码和生成。
import asyncio
from queue import Queue
encode_queue = Queue(maxsize=32)
generate_queue = Queue(maxsize=8)
async def encode_worker():
while True:
request = await encode_queue.get()
if request.type == "image":
features = visual_encoder(request.data)
else:
features = text_encoder(request.data)
generate_queue.put(features)
async def generate_worker():
while True:
batch = collect_batch(generate_queue, timeout=5ms)
outputs = llm(torch.stack(batch))
dispatch_outputs(outputs)
编码阶段按请求类型分发到不同编码器,生成阶段统一 batch 推理。生成阶段的 batch 越大,AICore 利用率越高。
五、性能数据
LLaVA-1.5(ViT-L/14 + Vicuna-7B),昇腾 910 × 2:
| 配置 | 首 token 延迟 | 生成速度 | 显存占用 |
|---|---|---|---|
| 单卡(7B 量化 INT8) | 85ms | 45 tok/s | 18GB |
| 双卡 TP(7B FP16) | 62ms | 68 tok/s | 28GB(每卡14GB) |
| 双卡 TP + PagedAttention | 58ms | 72 tok/s | 24GB(每卡12GB) |
PagedAttention 节省的 4GB 显存来自 KV Cache 的按需分配——短文本请求不需要预留最大序列长度的缓存空间。
参考资源
- LLaVA 模型仓库:https://atomgit.com/cann/models
- 多模态推理最佳实践:https://www.hiascend.com/document/detail/zh/CANN/
- PagedAttention 实现参考:https://atomgit.com/cann/ascend-transformer-boost
- 昇腾服务化部署指南:https://www.hiascend.com/document/detail/zh/CANN/
总结
多模态模型的部署关键是「拆」——把视觉编码器、融合层、语言模型拆成独立的 .om 文件,通过 HBM 共享 tensor 传递中间结果。这样做的好处:视觉编码器的显存在生成阶段可以释放、语言模型可以独立做多卡并行、编译和调试都更快。Batch 策略用异步队列解耦编码和生成,生成阶段统一 batch 推理以提升 AICore 利用率。双卡 TP + PagedAttention 的配置下,LLaVA-1.5 的生成速度能到 72 tok/s,显存占用每卡 12GB。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐



所有评论(0)