1. 从微信场景切入:多模态 Embedding 到底在做什么
先说个实际点的问题:很多人把多模态 Embedding 想得太玄,其实微信生态里到处都在跑这类模型。你搜一张表情包、发一段语音转文字、在小程序里检索商品图片,背后都牵扯到把“不同类型的数据”塞进同一个向量空间这件事。拿微信场景举例,用户的一条朋友圈可能同时包含文字描述、九张图片、地理位置、甚至一段视频,传统做法是“文本走文本的检索、图片走图片的检索”,两条线互不相通,遇到“发一张猫的照片找对应朋友圈文案”这种需求,基本就抓瞎了。
多模态 Embedding 模型解决的核心问题,就是让不同模态的数据在向量空间里可以直接比较相似度。用一个不严谨但好理解的类比:以前的搜索引擎是“各科室分开挂号”,文本去文本科、图片去图片科,现在多模态 Embedding 是“全院会诊”,把文字描述、图像内容、语音特征全部翻译成同一种“向量语言”,拉到同一个坐标系里做距离计算。
在微信这种量级的业务场景里,多模态 Embedding 的典型应用有几个明显落点:
- 视频号/朋友圈的“以图搜视频”,用户发一张截图,后台用 Embedding 向量去匹配视频画面的关键帧
- 微信搜一搜的“多模态召回”,搜索词是文本,候选内容包含公众号文章、视频号动态、小程序服务,全部映射到统一向量空间后做混合召回
- 表情包推荐和智能回复,结合图片语义和对话上下文文本,生成候选表情的排序特征
这套方案的设计逻辑,其实不只适用于微信。任何有“多类型内容+语义匹配”需求的产品,都可以套用同一套训练思路,只是数据规模和工程容错要求不同。下面我围绕完整的训练链路,把这件事拆开讲透。
2. 模型选型与训练范式:为什么不能直接拿现成模型用
2.1 Embedding 模型选型的底层逻辑
做多模态 Embedding,第一步其实是模型结构选型,这里面坑很多。我见过太多人上来就抱一个大参数量的多模态大模型试图直接推理,比如拿 7B、13B 的 VLM 去抽特征,结果线上延迟飙到几百毫秒,召回率还没涨多少。
微信这种场景更通用的做法是“双塔结构”,也叫 two-tower。核心思路是:文本塔和图像塔各自独立编码,但在训练时拉近匹配样本的向量距离,拉远不匹配样本的向量距离。这个结构的最大优势是部署时两边可以分开算、离线预计算一部分向量,线上只做向量检索。
具体到模型底座选择,目前实践里比较稳的几类方案是:
- CLIP 类模型底座:OpenAI CLIP、开源社区的中文 CLIP(如 CN-CLIP),图文匹配能力成熟,拿来初始化双塔模型的图像塔和文本塔非常合适,微信场景的中文适配也更好。
- 中文语义 Embedding 模型底座:像智源的 BGE 系列、阿里的 GTE 系列,文本侧语义理解很强,适合作为文本塔的初始化权重。
- 轻量级多模态融合模型:如果既要统一向量空间、又要兼顾细粒度跨模态交互,可以考虑 Qwen-VL 系列的轻量版本做蒸馏 Teacher,但全程在线推理不现实,通常是离线蒸馏后用 student 模型线上跑。
以我的实测经验,16G 显存级别的显卡(比如 RTX 4080 / 4090 / A5000)可以支撑 CLIP ViT-B/16 或 ViT-L/14 级别的双塔模型训练。如果数据量不大(百万级以内),ViT-B/16 性价比最高,微调速度快,显存占用大概 6-8G,还能留出空间做梯度累积和更大的 batch。
2.2 多模态融合算法的选择
双塔模型结构简单,但“塔之间不交互”也被人诟病——它学到的匹配分数终究是“各自编码后再比对”,没有建模模态间的细粒度对齐。更进阶的做法是引入交叉注意力,即让文本 token 和图像 patch token 在一个 Transformer 层里做注意力计算。但交叉注意力计算量大,不适合做第一阶段的 Embedding,更适合做精排模型。
实际工程里最常见的折中方案是:双塔做召回,交叉注意力模型做精排。第一路向量召回解决“从千万级候选里捞出几百个”,第二路精排解决“这几百个里谁最相关”。这是微信视频号、公众号搜索里非常典型的级联架构,既不牺牲效果,也能控制线上成本。
2.3 微调还是从零训练
关于“如何训练多模态 Embedding 模型”,很多人搞不清楚该微调还是该从零预训练。我的建议非常明确:除非你有数亿级以上的图文对数据,否则绝不要从零预训练。
从零训练一个多模态模型的数据成本、算力成本、调参成本都非常高,而且很容易遇到不收敛的坑。更好的做法是拿开源 CLIP 模型做底座,用自己的业务数据做领域微调。比如微信生态里有大量“中文网络用语+表情包/小程序截图”的数据,这些是公开 CLIP 模型没见过的,微调空间很值得挖掘。
3. 训练数据构造:多模态对齐效果的关键命门
3.1 数据来源与清洗
多模态 Embedding 模型的对齐效果,80% 取决于数据质量,模型结构反而在其次。如果你只给模型扔几千条清理干净的数据,效果大概率比扔十万条杂乱数据更好。
我自己的处理流程分为四步:
- 原始数据抽取:从业务日志里抽出图文共存的内容(比如公众号文章的配图和段落文本、视频号视频的标题和封面帧)
- 粗清洗:过滤广告、低俗内容、纯表情包无文本、纯文本无图像等无效样本
- 细清洗:做图文相关性打分。用一个小模型(比如 BLIP-2 或现成 CLIP)给每个图文对打分,丢弃分数低于阈值的样本,这个步骤能显著减少“图文不相关”的噪声
- 去重:用 MinHash 做文本去重,用 PerceptualHash 做图像去重,避免模型对高频样本过拟合
这里有个值得注意的细节:负样本的构造同样重要。训练双塔模型时,不仅要有正样本(匹配的图文对),还要有负样本(不匹配的图文对)。负样本不能只从随机配对里抽样,得加一些“难负样本”——比如文本是“狗在草地奔跑”,随机负样本是“厨房里的桌子”,难负样本是“狗在室内地毯上休息”,后者图像和文本表面关联性更强,模型需要学更细节的语义才能区分。
3.2 多模态数据增强的实践经验
文本侧增强相对有限:核心是文本替换、同义词替换、随机 mask(对应模型里做 dropout)等方式。更有效的是图像侧增强,包括随机裁剪、颜色抖动、模糊、翻转、旋转等。这能帮助模型学到更强的模态不变性。另外还可以做图文互换增强:同一张图配多个不同角度的文本描述,或者同一段文本配多张同主题不同风格的图,相当于在数据层面做“多对多对齐”,对增强模型鲁棒性非常有帮助。
3.3 数据规模怎么定
以微信这种规模的产品为例,假设我想做“朋友圈多模态召回”,图文对数据的数量级至少应该在千万级到亿级。如果只有几百万数据,建议先不要追求模型容量,而是把双塔模型的隐藏层控制在 512-768 维,训练轮次控制在 5-10 个 epoch 内,提前设置早停机制防止过拟合。
我踩过的一个节奏问题是:前期数据没清理干净时,盲目堆数据量只会让模型学偏。我第一次做类似项目时用了两千万图文对,结果模型上线后召回结果里出现大量低俗内容和无关广告,翻查日志发现源头是数据清洗去重环节没做干净,垃圾数据直接污染了 Embedding 空间。后来把数据规模砍到八百多万条,清洗做得更认真,效果反而明显提升。所以数据规模的第一优先级是“干净”,其次才是“大”。
4. 微信场景下的完整训练流程与核心代码实现
4.1 环境准备与依赖安装
开始训练前,先把环境搭好。我推荐一套稳定的组合,覆盖 PyTorch 2.x、CUDA、多模态模型加载与训练:
# 建议使用 Python 3.10 以上版本 conda create -n mm_embed python=3.10 -y conda activate mm_embed # 安装 PyTorch(以 CUDA 11.8 为例) pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装多模态训练常用依赖 pip install transformers datasets accelerate sentencepiece pip install open_clip_torch pip install faiss-cpu # 训练阶段评估用 faiss-gpu 也可以 pip install tensorboard4.2 整体训练流程
我在一次真实项目中,用微信视频号的封面图和标题文本构造图文对,做一次多模态 Embedding 微调,大体流程分五步:
- 数据加载与预处理:创建 Dataset,为文本塔加载 tokenizer,为图像塔加载图像预处理 pipeline
- 构建双塔模型:文本塔用 BERT/中文 RoBERTa 类底座,图像塔用 ViT 类底座,通过投影层把两个塔的输出映射到同一维度(比如 768 维)
- 构造训练 batch:一个 batch 内包含图文对,同 batch 内其他图像作为负样本,实现 InfoNCE 对比损失
- 加入难负样本:预先抽好难负样本,与随机负样本混合送入训练
- 评估与迭代:使用图文检索 Recall@K 指标评估,定期保存 checkpoint
4.3 双塔模型核心代码实战
为了让你能更直接对照操作,我放一个精简但可运行的双塔模型定义。这个结构非常适合做多模态 Embedding 微调实验:
import torch import torch.nn as nn import torch.nn.functional as F from transformers import AutoModel, AutoTokenizer, AutoImageProcessor import open_clip class TwoTowerModel(nn.Module): def __init__(self, text_model_name: str, vision_model_name: str, embed_dim: int = 768): super().__init__() # 文本塔:使用中文字符级或词级模型均可,这里用 open_clip 的 RoBERTa-wwm 中文底座 self.text_encoder = ( open_clip.create_model_and_transforms( "hf-hub:laion/CLIP-ViT-B-32-roberta-base_laion2B-s12B-b32k" )[0].text if False else AutoModel.from_pretrained(text_model_name) ) # 视觉塔:使用 ViT 系列模型 self.vision_encoder = AutoModel.from_pretrained(vision_model_name) # 投影层:统一到同一向量维度 text_hidden_size = self.text_encoder.config.hidden_size vision_hidden_size = self.vision_encoder.config.hidden_size self.text_proj = nn.Linear(text_hidden_size, embed_dim) self.vision_proj = nn.Linear(vision_hidden_size, embed_dim) # L2 归一化层 self.logit_scale = nn.Parameter(torch.ones([]) * 2.6592) def encode_text(self, input_ids, attention_mask): text_feats = self.text_encoder(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state # 取 [CLS] token 表示 text_emb = text_feats[:, 0, :] text_emb = self.text_proj(text_emb) text_emb = F.normalize(text_emb, dim=-1) return text_emb def encode_image(self, pixel_values): vision_feats = self.vision_encoder(pixel_values).last_hidden_state # 取 [CLS] token 表示 image_emb = vision_feats[:, 0, :] image_emb = self.vision_proj(image_emb) image_emb = F.normalize(image_emb, dim=-1) return image_emb def forward(self, input_ids, attention_mask, pixel_values): text_emb = self.encode_text(input_ids, attention_mask) image_emb = self.encode_image(pixel_values) return text_emb, image_emb4.4 对比损失函数与 Batch 内负样本
训练双塔模型最常用的损失函数是 InfoNCE(也叫 NT-Xent / Contrastive Loss)。核心思想是:对于一个 batch 里的每一对正样本(文本 i,图片 i),把 batch 里其他所有图片当作负样本,让正样本对的相似度得分尽量高,负样本对的得分尽量低。
def info_nce_loss(text_emb, image_emb, logit_scale, temperature=0.07): # 计算相似度矩阵 [batch_size, batch_size] logits = logit_scale * text_emb @ image_emb.T # 对角线是正样本 batch_size = text_emb.shape[0] labels = torch.arange(batch_size, device=text_emb.device) loss = F.cross_entropy(logits, labels) return loss上面的代码里有两个关键超参数值得展开说。一是logit_scale,初始值2.6592对应的1/0.07是 CLIP 论文里的经验设置,这个参数是可学习的,训练中会自动调节匹配分数的尺度;二是temperature,温度越小,对比损失对难负样本的惩罚越大,但也越容易训练不稳定,我建议初期固定在 0.07,不要动它,等模型稳定后再调。
4.5 训练循环中的关键细节
Loss 部分说完,接下来是实际训练循环中非常容易被忽略但决定成败的细节。
from torch.utils.data import DataLoader from torch.optim import AdamW from tqdm import tqdm # 假设 dataset 返回 dict: input_ids, attention_mask, pixel_values train_loader = DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4) model = TwoTowerModel( text_model_name="hfl/chinese-roberta-wwm-ext", vision_model_name="openai/clip-vit-base-patch32", embed_dim=768 ) model = model.cuda() optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) scaler = torch.cuda.amp.GradScaler() # 混合精度训练 epochs = 10 for epoch in range(epochs): model.train() total_loss = 0.0 for step, batch in enumerate(tqdm(train_loader)): input_ids = batch["input_ids"].cuda() attention_mask = batch["attention_mask"].cuda() pixel_values = batch["pixel_values"].cuda() optimizer.zero_grad() with torch.cuda.amp.autocast(): text_emb, image_emb = model(input_ids, attention_mask, pixel_values) loss = info_nce_loss(text_emb, image_emb, model.logit_scale) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() total_loss += loss.item() if step % 100 == 0: print(f"Epoch {epoch}, Step {step}, Loss: {loss.item():.4f}")这个训练过程里有两个细节直接决定效果:
第一个是学习率。双塔微调的学习率不能太大,不然会把预训练模型的特征空间冲垮。文本塔和图像塔的学习率可以分开设置,图像塔用 1e-5,文本塔用 2e-5,投影层用 5e-5,这样更精细地控制“保留原特征”和“适配新任务”的平衡。上面代码里为了简化只设了一个学习率,实际项目我强烈建议分组设置:
optimizer_grouped_parameters = [ {"params": model.text_encoder.parameters(), "lr": 1e-5}, {"params": model.vision_encoder.parameters(), "lr": 2e-5}, {"params": model.text_proj.parameters(), "lr": 5e-5}, {"params": model.vision_proj.parameters(), "lr": 5e-5}, ] optimizer = AdamW(optimizer_grouped_parameters, weight_decay=0.01)第二个是梯度累积。如果你的显卡显存不大(比如 16G),batch size 可能只能设到 32 或 64,这个规模对于对比学习来说偏小,负样本太少会明显影响训练质量。解决办法是梯度累积,用 4 个小 batch 累积梯度后再更新参数,等效于一个 128/256 的大 batch:
accumulation_steps = 4 scaler.scale(loss).backward() if (step + 1) % accumulation_steps == 0: scaler.step(optimizer) scaler.update() optimizer.zero_grad()4.6 模型检查器:训练中可视化 Embedding 质量
训练过程中,我建议每隔几个 epoch 做一次小规模评估。除了常规的 Recall@K 指标,还有一个非常直观的做法——把训练集里随机抽出的 500 条文本和 500 张图片都编码成向量,投影到二维空间里人工检查聚类效果。用到的工具就是模型检查器(Model Inspector)这类可视化工具。
import numpy as np from sklearn.manifold import TSNE import matplotlib.pyplot as plt # 收集验证集的 text_emb 和 image_emb # 拼接所有向量 all_emb = np.concatenate([text_emb_np, image_emb_np], axis=0) # TSNE 降到 2 维 tsne = TSNE(n_components=2, perplexity=30, random_state=42) all_emb_2d = tsne.fit_transform(all_emb) # 绘制图像,文本用蓝色点,图像用红色点,观察对齐情况 plt.figure(figsize=(10, 8)) plt.scatter(all_emb_2d[:len(text_emb_np), 0], all_emb_2d[:len(text_emb_np), 1], c='blue', label='text', s=10, alpha=0.6) plt.scatter(all_emb_2d[len(text_emb_np):, 0], all_emb_2d[len(text_emb_np):, 1], c='red', label='image', s=10, alpha=0.6) plt.legend() plt.savefig(f"embedding_visualization_epoch_{epoch}.png")如果训练效果理想,图中相同语义的文本点和图片点会聚成簇;如果文本点和图片点各聚一团、互相分离,说明两个塔还没对齐,需要调整数据或模型结构。
4.7 微信旧版客户端的兼容性排查
在微信场景做多模态 Embedding,有一个工程特殊性需要提醒:部分老版本客户端的特征上报格式不一样。微信团队在灰度发布新模型时,经常需要兼容旧版客户端传上来的特征。如果你做的是类似项目,最好在后端接口设计时设置特征版本号字段,兼容旧版和新型两套字段。我在实际项目中踩过这个坑:新版模型上线后,部分 Mac 旧版微信用户反馈搜索无结果,排查后发现是旧客户端不传image_feature_v2字段,导致后端拿到空向量,直接掉出索引。
5. 部署与上线:从 PyTorch 模型到线上服务
5.1 模型导出与量化
训练好的模型不能直接以 PyTorch 格式上生产,推理速度和显存占用都撑不住。常见做法是导出为 ONNX 格式,再做 FP16 量化或 INT8 量化。对于双塔模型,文本塔和图像塔可以分别导出:
import torch from model import TwoTowerModel model = TwoTowerModel( text_model_name="hfl/chinese-roberta-wwm-ext", vision_model_name="openai/clip-vit-base-patch32", embed_dim=768 ) checkpoint = torch.load("best_model.pt", map_location="cpu") model.load_state_dict(checkpoint["model_state_dict"]) model.eval() # 分别导出文本塔和图像塔 dummy_input_ids = torch.randint(0, 1000, (1, 64)) dummy_attention_mask = torch.ones(1, 64, dtype=torch.long) dummy_pixel_values = torch.randn(1, 3, 224, 224) torch.onnx.export( model.text_encoder, (dummy_input_ids, dummy_attention_mask), "text_encoder.onnx", opset_version=13 ) torch.onnx.export( model.vision_encoder, dummy_pixel_values, "vision_encoder.onnx", opset_version=13 )INT8 量化对显存和推理延迟的改善非常明显,但会有少量精度损失。我的建议是:先上 FP16,压测后如果延迟还不达标,再评估 INT8。微信视频号、搜一搜场景的检索服务有严格的延迟预算(通常 P99 小于 50ms),如果你真的要上 INT8,需要在离线评测集上对比量化前后的 Recall@K,确保损失在可接受范围内(比如不超过 2%)。
5.2 向量检索服务搭建与降级方案
Embedding 模型上线之后,你需要一个向量检索服务。开源的向量检索框架,各有各的优势,这里列一个对比表格:
| 方案 | 适合规模 | 延迟表现 | 部署成本 | 典型适用场景 |
|---|---|---|---|---|
| Faiss (CPU/GPU) | 千万级以内 | 较好(GPU 更佳) | 低 | 离线向量索引、小规模在线检索 |
| Milvus | 千万到亿级 | 好 | 中等 | 大规模生产环境,支持分布式 |
| Elasticsearch + dense_vector | 百万到千万级 | 中等 | 中等 | 已有 ES 技术栈、需要全文检索混合 |
| HNSW 自研 | 亿级以上 | 极好 | 高 | 大厂超高并发场景,常配合分层检索 |
微信这类超大规模场景一般会自研向量检索引擎,但大多数项目直接用 Milvus 或者 Faiss 就够了。有一个建议:向量检索服务一定要做降级方案,比如索引挂了就退化到基于标题关键词的 BM25 文本检索,避免整体服务不可用。这点在电商、社交等场景尤其重要,用户遇到一次无结果可能就流失了。
5.3 线上服务的 Embedding 缓存设计
上线后你会发现一个高频问题:相同或相似的文本(比如热搜视频的标题)会被重复调用模型编码,浪费算力。我的做法是对 Encoder 做一层 LRU 缓存,用文本或图片感知哈希做 key。
在新版本模型训练完切换线上模型时,最好灰度放量并同步清理缓存。有一次我没清缓存,新模型上线后文本查询结果大量命中缓存,导致 30% 流量还在用旧模型的结果,问题排查了很久。这个教训让我现在把缓存版本号写进 key。
6. 常见问题与优化技巧:训练多模态 Embedding 的实战排查
6.1 训练不收敛、Loss 震荡的排查思路
这是最常遇到的问题。展开来说,训练不收敛的根源大概率出在三方面:
- 数据问题:正样本本身图文不匹配,模型没法学习有效信号。这种问题要从数据清洗源头解决,可以抽一批训练样本人工检查。
- 学习率过大:对比学习对学习率很敏感,建议从 1e-5 起步,用 cosine schedule 衰减到 1e-6。我曾经把学习率调到 5e-5,训练到第二个 epoch 时 loss 从 3.2 暴涨到 15.8,模型直接崩掉。
- Batch size 过小:对比学习依赖大量负样本,batch size 至少 128。显存不够就梯度累积,千万别用 16 的 batch size 硬扛。
6.2 图文召回效果差的排查方法
假设训练正常,Loss 也降下去了,但 Recall@10 上不去。我一般的排查顺序是:
- 单看文本检索文本:用文本 Embedding 做纯文本语义检索,如果文本侧自身的效果就差,问题可能在文本 Encoder
- 单看图像检索图像:同理,用图像 Embedding 做纯图像检索
- 再做跨模态的图文互检,如果前两步没问题但第三步差,那大概率是数据里“图文对的对齐质量”不行
另外一个非常重要的评估指标是模态对齐度(Alignment)。简单说就是计算正样本对在所有样本对中的平均距离,再计算负样本对的平均距离,两者差值越大,对齐越好。我见过一种情况:Loss 一直在降,但正负样本的距离都在同步缩小,说明模型只是把所有向量都压缩到一个小区域里,根本没有学到区分性。这种情况通常要把 logit_scale 的初始化值调大,或者切换成 hard negative mining 策略。
6.3 16G 显存真的够用吗
回到热搜词里的“16g显存多模态模型推荐”。以我的经验,16G 显存做双塔模型微调完全够用,甚至偏宽松。ViT-B/16 图像塔 + RoBERTa-base 文本塔 + batch size 64 + FP16 混合精度,占用大约 10-12G 显存。如果你想训更大的 ViT-L/14,也勉强能跑,但 batch size 可能要降到 16-32,梯度累积步数需要相应增加。
但如果你想微调一个 7B 的 VLM 来做交叉注意力精排,16G 就不够看了。这种模型起码要 2 张 24G 以上的显卡,或者用 DeepSpeed ZeRO-Offload 把优化器状态挪到 CPU。我的建议是:召回阶段用双塔,16G 够;精排阶段用大模型,直接上云 GPU 或内部 GPU 集群,不要为这个问题浪费太多时间。
6.4 数据隐私与合规问题
微信场景的数据涉及用户隐私,训练数据安全是红线。行业通用做法是:
- 数据进入训练管道前做匿名化和脱敏,去掉能直接定位到具体用户身份的字段
- 训练流程走内部私有化环境,不允许数据出域
- 模型上线前做内容安全审查,尤其要防止模型生成或召回具有风险倾向的内容
这块强调再多都不为过,训练 Embedding 模型不是纯粹的技术活,数据安全和合规意识必须从第一天就建立。
7. 从“微信”到“你的场景”:这套方案的迁移思路
我在这类多模态 Embedding 项目上动手实践过一轮之后,最大的体会是:微信的技术方案虽然不对外公开细节,但整个行业的多模态融合算法、Embedding 模型训练范式是趋同的,并不会因为某家公司规模大就有本质不同。
如果你想把这套方案迁移到自己的业务里,我建议按照下面的路径逐步推进:
第一步:明确场景和指标。你是要做搜索召回、推荐粗排,还是做内容去重?不同场景对指标的定义完全不一样。搜索场景看 Recall@K,推荐场景看 Recall 和多样性,去重场景看 Precision。没有指标约束的训练就是耍流氓。
第二步:盘点数据和算力。你有多少图文对?数据标注质量如何?有没有 GPU 资源?至少需要一张 16G 显存的卡才能比较舒服地训练双塔模型。如果连 16G 都没有,那就只能使用在线 API 服务做推理,逐步积累数据。
第三步:小规模实验跑通链路。用几千条干净数据先做成一个可以用的双塔模型,哪怕效果一般,先把数据加载、训练、评估、导出的链路跑通。这一步的价值是暴露工程问题,而不是追求效果。
第四步:扩大数据规模和难负样本比例。数据量从几千扩到几十万、几百万,难负样本比例逐步提高,观察指标变化。这个阶段会反复进出“数据清洗、模型调参”的循环,要有耐心。
第五步:稳定训练、完善监控。上线后关注 Embedding 距离分布、在线召回率、延迟等指标。你会发现数据分布随时间漂移,Embedding 模型要定期更新和重训,这块的 MLOps 流程可以另外单独写一篇。
最后再分享一个实操心得:不要迷信“多模态模型越大越好”。在大多数业务场景里,一个微调过的 ViT-B/16 + RoBERTa 双塔模型,在垂直数据上表现往往超过一个没微调过的 7B 大模型做零样本特征抽取。少折腾模型规模,多花时间在训练数据质量和难负样本构造上,这几乎是我所有相关项目里投入产出比最高的优化方向。希望这篇内容能给你省点试错的成本。