请添加图片描述

前言

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 文件当然可以,但有几个问题:

  1. 显存浪费:视觉编码器只在第一步用,之后 90% 的时间它占着显存什么都不做
  2. 无法独立扩展:语言模型是瓶颈,需要多卡并行,但视觉编码器不需要
  3. 编译时间长: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_featuresfused_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。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐