1. 为什么用一张4090在JupyterLab跑实时视频大模型
1.1 这个项目的核心诉求
先说结论:MOSS-VL-Realtime 这类实时视频大模型,并不是只能在多卡集群上跑的“实验室玩具”。我这次的任务很明确——在云端租一台带单张 RTX 4090 的 GPU 服务器,全程在 JupyterLab 里完成从环境配置、权重拉取到视频推理的所有操作,最后用四组不同类型的视频实测验证它在真实场景中的表现。整个项目跑下来,一个非常强烈的感受是:实时视频理解的门槛正在被快速拉低,单人单卡已经能承担相当规模的多模态视频任务。
MOSS-VL-Realtime 本质上是视觉-语言多模态模型家族的一员,它做的事情可以通俗理解为“给视频画面配上一副会思考的嘴”:你在推理时塞给它一段视频或实时帧流,它能用自然语言回答“画面里发生了什么”“人物在做什么动作”“这一段和上一段有什么变化”。相比传统方案里“视频抽帧 + 单独目标检测 + 单独动作识别 + 文本规则拼接”的老路,这类端到端模型把感知和理解统一到了一起,省去了大量中间管道的工程成本。
这篇记录适合谁看?两类人最合适:一类是想在有限预算内(一张 4090 的时租价格并不算高)验证多模态视频模型效果的研究者或开发者;另一类是在 JupyterLab 里做原型验证、却被各种“依赖地狱”和“CUDA 版本不匹配”折磨过的人。我会把完整的部署过程、真实跑通时遇到的坑、以及四组实测的输入输出都放出来,你照着走一遍就能复现。
1.2 方案选型的几点考量
选 JupyterLab 而不是传统 SSH + 命令行,是我刻意为之。GPU 云服务器的交互环境通常默认带 JupyterLab,浏览器打开就是工作台,不用在本地折腾 SSH 密钥和端口转发;更重要的是,做视频模型推理天然适合 Notebook 这种“逐 cell 调试”的模式——先加载模型、再写帧采样、再跑推理循环,每一步的中间结果都能直接可视化检查。实测下来,这个习惯帮我省了大量排查时间。
选 RTX 4090 则是性价比的必然结果。24GB 显存对 7B~8B 级别的多模态模型来说,恰好卡在一个“能完整放下权重 + 视频特征缓存 + KV cache”的甜点位。用 bf16 精度加载模型、单帧图像特征控制在几 MB 到几十 MB 量级时,24GB 是有余量的。再往上的 48GB 卡时租价格要翻好几倍,而再往下的 16GB 卡则会被迫上 4bit 量化,画面质量损失不说,推理速度也没飞起来。所以“一张 4090 + JupyterLab”不是巧合,是性能和成本折中之后最稳定的组合。
2. 云端环境准备与模型权重获取
2.1 租一台带4090的GPU云服务器
这一步没什么高深的,但有几个选配细节值得注意。我选的是带 JupyterLab 服务的镜像,操作系统选了 Ubuntu 22.04,深度学习框架镜像里自带 CUDA 12.1 和 PyTorch 2.1,省去从零装驱动的时间。磁盘我开了 100GB,其中系统盘 40GB、数据盘 60GB——千万别只给 40GB 系统盘,因为模型权重动辄十几 GB,视频测试集再放几个 GB,空间很快就紧张了。
登录云平台之后,在实例列表里找到公网访问地址,浏览器打开就是 JupyterLab 登录页,输入平台分配的 token 就能进工作台。注意:JupyterLab 里打开的终端和你本地 SSH 进去的终端是同一个环境,建议先把所有依赖安装和权重下载放在 JupyterLab 自带终端里做,因为实例实际跑在 GPU 机器上,不会有本地和远端环境不一致的问题。这一步踩过坑的人应该不少——本地 Windows 配好的环境,SSH 上去永远莫名报错,就是因为本地和服务器 Python 版本、CUDA 路径对不上。用 JupyterLab 终端,环境就固定在同一台机器上,少了非常多的心智负担。
提示:如果是新实例,先执行
nvidia-smi确认驱动识别到 4090。如果没看到 GPU,多半是镜像里缺驱动,需要联系平台客服重新选择带 NVIDIA 驱动的镜像,不要在本地浪费时间。
2.2 检查驱动、CUDA与Python环境
登录后的第一件事,依次确认三样东西:
nvidia-smi nvcc --version python --version我这台机器的输出大致是:
- NVIDIA-SMI 535.129.03,Driver Version 535.129.03,CUDA Version 12.2
- nvcc 12.1
- Python 3.10.12
有个细节:nvidia-smi显示的 CUDA Version 12.2 是驱动支持的最大版本,而nvcc --version是你当前 PyTorch 编译时用的 CUDA 工具链版本。两者不需要完全一致,只要 PyTorch 的 CUDA 版本 ≤ 驱动的最大支持版本,就能正常跑 GPU 算子。这里看到 PyTorch 2.1 自带 CUDA 12.1,驱动支持 12.2,完全兼容,不需要额外装 CUDA Toolkit。
然后创建独立的 Python 虚拟环境。虽然 JupyterLab 镜像自带的 base 环境也能用,但多模态模型的依赖冲突非常常见(后面会讲到我踩的 transformers 版本坑),独立环境能让你随时倒退回可用状态:
python -m venv /workspace/mm_env source /workspace/mm_env/bin/activate pip install --upgrade pip2.3 安装依赖与下载权重
在干净环境里,按顺序安装以下依赖。顺序很重要,先装 PyTorch 再装其余包,避免 transformers 依赖的 tokenizers 版本被其他包覆盖掉:
pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cu121 pip install transformers==4.38.2 accelerate==0.27.2 sentencepiece==0.1.99 pip install decord opencv-python pillow numpy pip install jupyterlab特别说明一下为什么要锁定transformers==4.38.2。新版 transformers 对部分多模态模型的trust_remote_code加载方式做了调整,某些模型的自定义代码在 4.39+ 会直接报 “KeyError: 'moss_vl'” 之类的错误。这个坑我试过好几次,锁版本是最省心的解法。
权重下载我用的是 ModelScope 的官方下载命令(如果你在国内,速度比直接到 Hugging Face 拉要快得多,也省去很多网络上的麻烦):
from modelscopegpt.download import snapshot_download snapshot_download( 'path/to/MOSS-VL-Realtime', local_dir='/workspace/models/MOSS-VL-Realtime' )权重文件下载完,建议先看一眼目录结构:
ls -lh /workspace/models/MOSS-VL-Realtime正常情况下你会看到:
config.json、modeling_moss_vl.py(模型结构自定义代码)pytorch_model.bin或model-00001-of-0000X.safetensors(分片权重)tokenizer.json、tokenizer_config.jsonpreprocessor_config.json(图像预处理配置)
到这里,环境三件套——驱动、Python 依赖、权重——全部就位。接下来真正进入 JupyterLab 笔记本的编码环节。
3. JupyterLab里的核心实现
3.1 模型加载与显存管理
在 JupyterLab 里新建一个 Notebook,选择刚才创建的虚拟环境作为 Kernel(如果内核列表里没有,执行python -m ipykernel install --user --name mm_env注册一下)。第一个 Cell 先加载模型:
import torch from transformers import AutoModel, AutoTokenizer from modelscope.preprocessor import ImageProcessor model_path = "/workspace/models/MOSS-VL-Realtime" dtype = torch.bfloat16 device = "cuda:0" tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) image_processor = ImageProcessor.from_pretrained(model_path, trust_remote_code=True) model = AutoModel.from_pretrained( model_path, torch_dtype=dtype, device_map={"": device}, trust_remote_code=True, ).eval()device_map={"": device}等价于model.to("cuda:0"),但我建议用前者,因为当显存不够需要切一部分层到 CPU 时,改成device_map="auto"就完事,不用改其他任何代码。模型加载完成后立刻看显存占用:
print(torch.cuda.memory_summary())加载完一个 7B 级别的模型,bf16 权重约 14GB,加上模型结构和 CUDA context 的开销,torch.cuda.memory_reserved()通常在 15~16GB 左右。剩给视频帧特征和 KV cache 的可用空间还有约 8GB,足够承载实时视频推理的需求。这里是 4090 24GB 的优势——如果只有 16GB,加载完模型内存就快满了,跑几帧就可能 OOM。
注意:模型加载完成后尽量释放一下缓存。在加载视频数据和预处理时,可以随时执行
torch.cuda.empty_cache()回收碎片显存,防止后续推理时因碎片化而 OOM。
3.2 视频帧采样与预处理
实时视频模型的核心思路是帧采样——把连续视频流转换成模型能理解的离散图像序列。我用的是 Decord 库,它比 OpenCV 的VideoCapture在按索引随机取帧时快很多,而且在 GPU 云环境里不需要额外编译环境。
import decord import numpy as np from torchvision.transforms.functional import resize decord.bridge.set_bridge('torch') # 让 Decord 直接返回 torch.Tensor def load_video_frames(video_path, num_frames=16): vr = decord.VideoReader(video_path, width=448, height=448) total_frames = len(vr) indices = np.linspace(0, total_frames - 1, num_frames).astype(int) frames = vr.get_batch(indices) # shape: (num_frames, 448, 448, 3) frames = frames.permute(0, 3, 1, 2).float() # (num_frames, 3, 448, 448) return framesnum_frames=16是这套配置下的经验值。16 帧既保留了视频的主要时序信息,又不会因为帧太多把前向传播时间拖到不可接受。这里有一个非常重要的细节:Decord 在VideoReader初始化时可以指定width和height,它会直接在解码阶段做 resize,比解码后再用 PIL 逐张 resize 快一个量级。这个优化靠的是 FFmpeg 的 swscale,而不是 Python 循环,实测处理一个 1080p 视频到 448x448,总耗时能降到原来的三分之一左右。
预处理环节,MOSS-VL-Realtime 的 image processor 会自动做 normalize(用 ImageNet 的 mean/std)和 padding 到模型要求的输入尺寸。你只需注意把 dtype 转成和模型一致的 bfloat16:
def preprocess(frames, image_processor, device="cuda:0"): frames = frames / 255.0 inputs = image_processor( images=frames, return_tensors="pt", ) inputs = {k: v.to(device, dtype=torch.bfloat16) for k, v in inputs.items() if hasattr(v, 'dtype')} return inputs3.3 推理主循环与流式输出
MOSS-VL-Realtime 的推理接口和大多数自家 transformers 模型类似,直接用model.generate。但实时场景里最影响体验的不是生成速度,而是首 token 延迟——就是你刚把视频喂进去到模型吐出第一个词的时间。为了减少这个延迟,我把视频帧编码放在generate之前手动执行一次,避免每次生成时重复编码同一段视频:
from PIL import Image import torch def run_video_qa(video_path, question): frames = load_video_frames(video_path, num_frames=16) inputs = preprocess(frames, image_processor) # 手动编码视频特征,缓存起来避免重复计算 with torch.no_grad(): video_features = model.encode_video(frames.to(device, dtype=torch.bfloat16)) prompt = f"用户:{question} 助手:" input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device) with torch.no_grad(): outputs = model.generate( input_ids=input_ids, video_features=video_features, max_new_tokens=256, do_sample=False, temperature=0.6, top_p=0.9, repetition_penalty=1.05, ) answer = tokenizer.decode(outputs[0][input_ids.shape[1]:], skip_special_tokens=True) return answermodel.encode_video是模型中用于将视频帧序列编码为统一视觉特征的方法。如果你用的版本接口名称不同,可以用上面 preprocess 得到的inputs直接传给generate,但那样每次对话都会重新编码视频,多花 200ms 左右。实测下来,缓存 video features 后单轮问答时延从 1.7s 降到了 1.2s,提升非常明显。
为了模拟真正的“实时”效果,我在 Notebook 里加了一个简易的视频循环播放器,把摄像头或采集到的 RTSP 流逐帧送入模型,以 1 秒为间隔输出当前画面的理解和上下文判断:
import cv2 import time def realtime_capture(rtsp_url, max_frames=30): cap = cv2.VideoCapture(rtsp_url) buffer = [] while cap.isOpened(): ret, frame = cap.read() if not ret: break buffer.append(cv2.resize(frame, (448, 448))) if len(buffer) == 8: yield torch.from_numpy(np.array(buffer)).permute(0, 3, 1, 2).float() buffer = [] time.sleep(0.05)这段代码不是最高效的,但足够跑通概念验证(POC)。真实生产环境要用多线程解码 + 双缓冲队列,把视频读取和模型推理解耦,才不会出现“解码等待推理”的空闲窗口。
4. 四组实测数据分析
4.1 测试环境与评测方法
测试在同一台实例上完成,具体配置如下表:
| 项目 | 配置 |
|---|---|
| GPU | NVIDIA RTX 4090 24GB |
| 驱动 / CUDA | 535.129.03 / 12.1 |
| PyTorch | 2.1.2 (cu121) |
| 模型精度 | bfloat16 |
| 视频采样帧数 | 16 |
| 输入分辨率 | 448×448 |
评测方法:每组测试输入一段真实视频素材 + 一个自然语言问题,记录三类指标——预处理耗时(帧读取+resize+norm+编码)、生成耗时(从调用 generate 到输出完整回答)、显存峰值。因为推理时模型权重常驻显存,所以显存峰值更能反映视频推理的开销。
4.2 第一组:单场景视频内容问答
视频素材是一段 8 秒短视频,内容是一个人在厨房里煮咖啡,镜头固定没有切换。问题很简单:“画面里的人在做什么?请按时间顺序描述。”
输出结果摘录:
画面中一名穿着灰色上衣的人站在厨房台面前。首先他拿起咖啡壶,将水倒入咖啡机的水箱中,然后取出一只白色咖啡杯放在咖啡机下方。随后他按下咖啡机的启动按钮,等待咖啡滴落。最后他从台面上拿起一小盒牛奶,往杯子里倒入牛奶。整个过程动作连贯,无明显中断。
这组表现超出我的预期。纯视觉问答没有额外 prompt 的情况下,模型能按“先…然后…最后”的时间顺序组织描述,说明它确实捕捉到了帧之间的动作时序,而不是简单拼凑静态画面的物体列表。耗时数据如下:
| 指标 | 耗时/占用 |
|---|---|
| 视频预处理(16帧) | 217ms |
| 视频特征编码 | 386ms |
| 回答生成(185 tokens) | 2.54s |
| 首 token 延迟 | 612ms |
| 显存峰值 | 18.7GB |
信息量很大。首 token 只有 612ms,意味着如果把生成限制在 50 tokens 以内,单轮交互能控制在 1.2s 左右,接近“实时问答”的体验。18.7GB 的显存峰值也比我想象中低,剩余空间说明 16 帧已是显存友好配置,就算把帧数提高到 32,24GB 也扛得住。
4.3 第二组:行为识别与事件定位
这组测试的目的是检验模型对“动态事件”而不是“静态物体”的理解。素材是一段 15 秒监控视角视频:一个人走进房间,在桌边发现一份文件,拿起快速翻看,然后塞进包里离开。问题设计成需要模型指出事件发生的相对时间:“文档是什么时候被拿走的?这个动作持续了多久?”
输出:
文档在人物进入房间后大约第 5 秒时被拿起。动作开始是右手从桌面拿起纸张,随后双手翻动,持续约 3 秒后放进左侧的公文包内。从视频帧序列的 7 到 10 帧可以明显看到这一过程。
模型居然给出了“第 5 秒”“7 到 10 帧”这类相对定位信息,说明它在内部特征里保留了帧序号关系。我特意对比了实际标注——文件在视频第 5~8 秒之间被拿起,持续 4 秒,模型的回答非常接近。这对于时间戳定位任务来说是个惊喜。不过这里也有个局限:模型只能做相对定位,不能给出绝对时间戳(因为它不知道原始视频的总时长和帧率),使用时要靠外部后处理换算。
4.4 第三组:长视频摘要理解
长视频是视频理解的老大难。我准备了一段 3 分钟的产品介绍视频,内容包含多个镜头切换:开场白、产品外观展示、功能操作演示、参数表格、结尾总结。问题:“这个视频主要介绍了什么?分几个部分?”
模型输出:
视频主题为某智能硬件的功能介绍。内容分为五个部分:开场引入产品定位;外观设计与材质说明;核心功能的操作演示;技术参数的表格呈现;最后的卖点总结与适用场景建议。
这 16 帧是从 3 分钟视频里均匀抽样的,每个镜头大约占 2~3 帧,模型仍能准确划分出结构,一定程度上说明它对镜头切换信息是有感知的。但长视频场景有明显的短板:如果某个关键信息只出现在两帧之间,均匀采样很可能漏掉。比如视频中途闪过一个重要的二维码,模型回答里完全没提到。这说明当前的 16 帧均匀采样不适合做关键信息无遗漏检索,如果需要精准捕捉,建议改成“均匀采样 + 关键帧补充”的混合策略。
4.5 第四组:实时流式问答(模拟直播)
最后一组模拟真实直播场景。我用摄像头对着桌面上的一个电子时钟和一个水杯,让模型实时回答:“现在画面里有哪些物体?时钟显示几点?如果有人拿起水杯,请立刻告诉我。”
模型在连续 30 帧中的表现:
- 第 1~8 帧:正确识别出电子时钟和白色水杯,报出时钟显示“10:32”
- 第 9 帧:模型开始主动描述“画面右侧的手伸向桌面”
- 第 12 帧:模型输出“水杯正在被拿起”
- 第 18 帧:模型补充“水杯离开桌面,推测有人饮水”
这个结果是四组里最有意思的。它不是在一个问题完成后才开始下一轮,而是在持续输入帧流的同时主动吐出新的事件描述。虽然每一步的延迟仍有 1~2 秒,但从“看到变化”到“说出口”的整个链路跑通了。对“实时”的定义,我的理解是:能在一个可接受的时间窗口内(2~3 秒)反馈画面变化,而不是必须在视频播放完之后才给总结。从这个意义上说,MOSS-VL-Realtime 在 4090 上的实时性已经达标。
4.6 综合数据看板
| 测试组 | 视频时长 | 预处理+编码耗时 | 生成耗时 | 显存峰值 | 结果评价 |
|---|---|---|---|---|---|
| 单场景问答 | 8s | 603ms | 2.54s | 18.7GB | 时序描述准确 |
| 行为识别定位 | 15s | 645ms | 3.12s | 19.1GB | 相对时间定位好 |
| 长视频摘要 | 3min | 598ms | 2.95s | 18.9GB | 结构划分好,细节易漏 |
| 实时流式问答 | 30帧 | 单帧约80ms | 流式输出 | 20.2GB | 变化事件能即时反馈 |
四组数据串起来看,20GB 出头的显存峰值是 4090 能稳定覆盖的边界,而 1~3 秒的回答延迟决定了它更适合“准实时”场景,而不是毫秒级交互。对大多数智能监控、内容审核、智能客服视频输入这类现实需求,这个速度完全够用。
5. 踩坑记录与排查技巧
5.1 依赖冲突与版本对齐
第一坑在 transformers 版本。我最初用镜像自带的 transformers 4.36,加载模型时报AttributeError: 'MossVLForCausalLM' object has no attribute 'encode_video',换到 4.42 后模型结构加载报错,最终锁定 4.38.2 解决。
这也引出一个排查思路:遇到多模态模型加载异常,先看模型仓库里的modeling_*.py用到哪些 API,再去 huggingface 的 transformers release note 里查该 API 在哪个版本引入/移除。这个过程我总结成一个快速定位模板:
grep -n "encode_video" /workspace/models/MOSS-VL-Realtime/modeling_moss_vl.py python -c "import transformers; print(transformers.__version__)"如果模型代码里调用了AutoModel的某个方法而你安装的 transformers 版本不具备,优先查这个 API 的引入版本,不要去盲目升级到最新。最新版往往引入更多 breaking change。
5.2 显存不足与OOM处理
OOM 是最让新手崩溃的。现象是推理跑到一半抛torch.cuda.OutOfMemoryError: CUDA out of memory。我第一次跑 32 帧输入时也遇到这个报错,但真正原因不是显存总量不够,而是显存碎片化。
4090 的 24GB 显存,加载模型 bf16 权重占 14GB 左右,CUDA context 占 500MB 到 1GB,如果此时你还在同一个 Python 进程里频繁创建和销毁视频张量,显存碎片会越来越严重,到最后即使剩余空间总量足够,也找不到连续的大块显存来申请。
解决方式有三层:
- 在视频帧加载后立刻
torch.cuda.empty_cache()并把不再引用的帧张量del掉。 - 推理循环中固定使用同一个预先分配好的张量缓冲区,而不是每次重新创建。
- 如果 16 帧就 OOM(不太可能,但要留后手),把视频预处理也丢到 CPU 上执行,只把编码后的特征放到 GPU,显存占用能降 2~3GB,代价是预处理慢 20% 左右。
最实用的还是第一条。我在代码里加了中间检查点:
def check_memory(tag): allocated = torch.cuda.memory_allocated() / 1024**3 reserved = torch.cuda.memory_reserved() / 1024**3 print(f"[{tag}] allocated={allocated:.2f}GB reserved={reserved:.2f}GB")每次推理前看一眼,能非常直观地发现是谁在偷显存。
5.3 视频解码性能瓶颈
JupyterLab 里跑视频解码遇到过两个坑。第一个是 Decord 在读取某些网络摄像头 RTSP 流时会报libcurl error: Couldn't resolve host name,这不是 Decord 的 bug,而是云服务器上的网络代理配置影响了 libcurl。解决办法是把http_proxy和https_proxy环境变量设置为可控范围,或者在读取前暂时禁用代理:
unset http_proxy unset https_proxy当然这只针对你的服务器确实配了代理的情况,如果没配代理,跳过即可。
第二个坑是 OpenCV 和 Decord 同时存在时的libgomp报错。症状是ImportError: /lib/x86_64-linux-gnu/libgomp.so.1: cannot allocate memory in static TLS block。原因是两个库各自链接不同版本的 OpenMP 运行时,冲突了。解决办法是让 OpenCV 晚于 Decord 导入,或者在虚拟环境里统一用 conda 管理这两个库的版本。我为了省事,只保留了 Decord,OpenCV 只负责摄像头采集,两者不混用于同一个视频文件,问题就消失了。
5.4 JupyterLab内核崩溃与恢复
JupyterLab 最折磨人的问题:跑长视频时内核无征兆崩溃,所有变量丢失,又要从第一个 Cell 重新执行。
排查后发现是 GPU 显存耗尽后,某些 CUDA kernel 进入死锁状态,内核被系统 OOM killer 杀掉。Jupyter 本身不会自动重启内核,你看到的画面就是“内核已死,是否重启”。
应对方式,我总结了三条:
- 长任务拆小:不要在一个 Cell 里跑完整条 3 分钟视频,拆成多个 Cell,每个处理 30 秒的片段。这样即使崩了,前几步结果还在变量里,重跑最后一个 Cell 就行。
- 定时 checkpoint:在关键节点用
np.save或torch.save把 video features 缓存到磁盘,一旦内核崩溃,重启后直接加载缓存,不用重新编码。 - 开多个 Kernel:JupyterLab 支持不同 Cell 选择不同 Kernel。我会开一个“模型 Kernel”常驻加载模型,另开一个“数据处理 Kernel”做视频解码和预处理,两个 Kernel 之间通过磁盘文件交换数据,避免同一进程内显存和 CPU 内存互相挤压。
这三种方法组合下来,我在后续的批量测试里再也没有因为内核崩溃重跑超过三个 Cell。这个经验在长实验里价值巨大——单是重新加载 14GB 权重的两分钟,就足以让你想砸键盘。
6. 一些个人感受与扩展建议
整体跑完四组实测,我对 MOSS-VL-Realtime 单卡部署的结论是:可用、好用、但仍需懂它的边界。好用体现在部署流程短、Notebook 交互直观、16 帧抽样的准确率意外地高;边界体现在长视频关键细节易丢、流式场景的 1~2 秒延迟还不能算严格意义的“毫秒级实时”。但如果你做的是智能安防、视频内容审核辅助、直播画面理解这类准实时场景,这个组合已经具备投入原型验证的资格。
我个人在实际操作中的体会是,JupyterLab 做模型推理部署,最值钱的不是交互界面,而是“断点续跑”的自由度——模型加载放一个 Cell,特征编码放一个 Cell,问答测试放一个 Cell,任何一步出问题都能精准定位,而不是从头再来。加上 Jupyter 对图片、表格、视频帧的直接渲染,你在调试多模态模型时能肉眼看到输入是什么,这对排查“模型为什么答错”有决定性的帮助。
如果你想把这套流程应用到自己的场景,我建议下一个探索方向是双缓冲实时流:用一个线程持续拉取视频流做帧采样,另一个线程跑模型推理。把两个环节解耦之后,实时视频问答的吞吐至少还能再提 30%。这次四组测试用的是同步流程,帧读取和推理是串行的,后来我试了双缓冲方案,同样 16 帧输入,单轮问答间隔从 3 秒压缩到了 2 秒以内。这个优化空间还是非常大的。