多模态模型在昇腾上的部署架构
前言
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 上共享。
共享机制
importtorchimporttorch_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:1llm_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实现方式:用异步队列解耦编码和生成。
importasynciofromqueueimportQueue encode_queue=Queue(maxsize=32)generate_queue=Queue(maxsize=8)asyncdefencode_worker():whileTrue:request=awaitencode_queue.get()ifrequest.type=="image":features=visual_encoder(request.data)else:features=text_encoder(request.data)generate_queue.put(features)asyncdefgenerate_worker():whileTrue: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。
