
多模态模型 Diffing 是当前多模态可解释性和模型可控性研究里一个很实用的分析思路。它通过比较两个多模态模型在同一输入下的内部表征差异找出哪些特征发生了变化、变化集中在哪些维度、又可以通过哪些维度进行干预从而完成从“模型哪里不同”到“怎么控制模型行为”的完整链路。这类方法在模型迭代对比、模型修复、数据影响分析、安全审计等场景中都非常有价值。比如训练了一个新版本 Clip 模型想知道相比旧版本到底改了什么或者想让模型在生成图像描述时更强调颜色属性但又不想重新训练整个模型又或者想定位模型中某个偏见可能集中在哪些特征维度。这些都可以用多模态模型 Diffing 的思路来解决。这篇文章会从概念讲起然后给出一个基于 PyTorch 和 Transformers 的最小可运行案例覆盖特征提取、差异计算、显著维度定位、激活编辑控制、效果验证和常见问题排查。文中代码用于说明思路实际项目需要根据模型结构、数据格式和实验目标进行调整。1. 为什么需要多模态模型 Diffing从模型比较到行为控制1.1 多模态模型越来越复杂直接看权重已经不够多模态模型通常包含图像编码器、文本编码器和多模态融合模块参数量从几亿到上百亿不等。训练完成后开发者面对的是一个高维参数矩阵很难通过直接查看权重数值理解模型内部的知识结构。当两个模型版本之间存在差异时问题就更明显了。比如初始模型 A 用通用数据训练模型 B 在 A 的基础上用医疗数据继续微调。此时如果只是对比准确率指标得到的是“B 在医疗任务上更好”这样粗粒度的结论如果直接对比权重参数得到的是海量数值差异既看不出语义也无法指导后续修改。Diffing 本质上把“模型权重级对比”提升到了“行为表征级对比”。它不关心每一个参数具体变成多少而是关心同样一张图、同样一段文本输入模型后内部特征在哪些维度产生了系统性偏移。这种偏移如果足够稳定就能被解读为某种可识别的特征变化。1.2 Model Diffing 在模型层面的含义Model Diffing并不是某个固定 API 或者官方工具的专有名词而是一类分析方法的统称。核心流程是准备两个待对比的模型分别记为model_a和model_b。准备一组相同输入包括图片、文本或图文对。从两个模型中提取特定层的中间激活、嵌入向量或注意力权重。对激活结果做对齐计算差异矩阵。对差异做统计分析和可视化找到显著特征维度。可选地利用差异方向在推理时对模型行为进行干预。这里要注意Diffing 的目标不是简单的数值 diff而是语义层面的差异发现。两个模型输出可能只差一点点但这一点点差异在特征空间中可能指向非常悬殊的语义方向。比如图像描述模型在加入了“避免成人内容”的过滤训练后文本嵌入中与“真实感”相关的维度会发生偏移如果不做 Diffing只对比 BLEU 分数很难定位到这个偏移。1.3 特征发现与控制的关系Feature Discovery指从高维特征中找出有意义的、可解释的维度或方向Control指利用这些发现的维度对模型输出进行定向修改。两者是先后关系。没有可靠的 Feature DiscoveryControl 就无从下手。比如想让多模态模型在图像检索时更偏向“温暖色调”的图片必须先知道“温暖色调”这个概念在哪个特征层、哪些维度上有稳定编码。有了这个位置才能在推理时叠加一个方向向量让模型行为发生连续变化。这种思路和传统的 Fine-tuning、Prompt Engineering 有本质区别。Fine-tuning 需要重新训练或更新参数成本高、代价大Prompt Engineering 只对文本输入有效无法精细控制图像编码内部表征。而基于 Diffing 的激活控制是在推理阶段直接修改内部向量既能保留原始模型的大部分行为又能定向增强或削弱某个特征。2. 技术基础表征空间、差异度量和特征定位2.1 多模态模型中的表征空间目前常见的多模态模型比如 CLIP 系列、BLIP、Flamingo、LLaVA 等基本都会把图像和文本编码到同一个或者可对齐的向量空间。以 CLIP 为例图像经 ViT 编码器处理后得到image_embeds文本经 Transformer 编码器处理后得到text_embeds通过对比学习让匹配的图文对在表征空间中接近。这个表征空间就是 Diffing 的主战场。模型在某个任务上的行为差异最终都会反映为表征空间中的分布偏移。在进行 Diffing 之前先要明确你比较的是哪个层最后一层嵌入包含最高层的语义信息适合做任务级差异分析中间隐藏层保留更多细粒度细节适合定位具体属性特征注意力矩阵反映模型关注的局部区域适合分析视觉关注点变化梯度或激活统计量适合分析训练数据影响。建议开始的实验从image_embeds和text_embeds最后一层开始因为它们维度明确、语义清晰、最容易解释。等流程跑通后再往中间层扩展。2.2 差异量化的常用方式计算两个模型在同一输入上的表征差异通常不是直接做逐元素减法而是会结合多种度量方式。下面是几种常见做法。方法计算方式适用场景注意事项逐元素绝对差|emb_b - emb_a|观察每个维度的偏移量需要对齐输入和模型输出顺序欧氏距离|emb_b - emb_a|_2衡量整体差异程度值受维度数和归一化方式影响余弦相似度cos(emb_a, emb_b)衡量方向一致性对尺度不敏感但不反映幅值变化CKA 相似度基于核函数的表示相似度比较两层或多模型全局表征结构计算成本较高方向向量emb_b - emb_a作为编辑方向后续做激活控制向量需要归一化避免尺度影响在实际工程中建议同时计算逐元素绝对差和余弦相似度。逐元素绝对差可以帮助定位哪些维度变化最大余弦相似度可以判断两个模型在同一输入上的语义一致程度。如果余弦相似度很高但逐元素差很大说明模型差异主要体现在尺度上而不是方向语义上。2.3 从差异到特征发现计算出差异矩阵后还需要回答一个问题这些差异到底意味着什么常用的特征发现手段包括Top-k 维度分析对差异向量按绝对值排序找出偏移最大的 k 个维度再通过反查样本观察这些维度与什么语义相关。探针分类器用差异向量或指定维度的激活值作为特征训练一个线性分类器判断能否区分两个模型或某个属性。Patching / 消融把 model_a 的某层特征替换成 model_b 的特征再观察模型输出变化。如果替换后行为发生剧烈变化说明该层承载了关键差异。聚类与可视化对所有样本的差异向量做 PCA 或 t-SNE观察差异是否按某些属性聚集。实际项目中Top-k 维度分析和探针分类器最常用因为它们计算成本低、结果容易解释。2.4 容易误解的地方这个领域有三个常见误解需要澄清。第一个误解是“模型权重差越大行为差异越大”。权重差异大并不意味着输出行为差异大因为非线性层会压缩或放大某些方向的权重变化。相反有时权重只变了 1%却因为乘上了特定方向的特征导致输出语义发生大幅迁移。第二个误解是“某个维度有差异就一定代表某个单一语义”。深度模型的特征维度往往是分布式编码的一个维度可能同时参与多个概念的表示。因此实际操作中通常会基于一组差异维度的加权组合构造方向向量而不是只编辑单个维度。第三个误解是“对比两个模型只需要固定输入”。模型本身的随机性、数据加载顺序、归一化层状态都会影响激活值。要得到稳定的差异方向必须固定随机种子、固定模型模式、使用相同预处理并在足够多样本上取平均。3. 一个可运行的最小案例CLIP 风格模型的特征 Diffing3.1 环境准备以下案例基于 Python 3.10、PyTorch 2.x 和 Transformers 库。示例中用 CLIP 系列模型做演示因为该结构简单、加载方便、多模态对齐清晰。python -m venv venv source venv/bin/activate pip install torch transformers scikit-learn pillow这里需要说明的是torch和transformers的版本不要盲目选最新建议在项目中先固定版本组合。示例环境的版本组合可以参照你本地已跑通的配置如果是从零开始先装好某个稳定组合再继续。模型权重需要从模型仓库下载运行前要确认本机网络能正常访问下载地址。3.2 准备待对比的两个模型为了演示 Diffing需要两个模型。这里用一个更稳妥的方式加载同一个预训练模型然后对其中一个做轻微的权重扰动模拟“微调后模型”。这样做的好处是数据可控、实验可复现不需要真实训练一个完整多模态模型。import torch from transformers import CLIPProcessor, CLIPModel pretrained_name openai/clip-vit-base-patch32 model_a CLIPModel.from_pretrained(pretrained_name) model_b CLIPModel.from_pretrained(pretrained_name) # 模拟 model_b 在某几个维度上发生了权重偏移 state_dict model_b.state_dict() for key in state_dict: if text_projection in key or visual_projection in key: state_dict[key] state_dict[key] 0.05 * torch.randn_like(state_dict[key]) break model_b.load_state_dict(state_dict) model_a.eval() model_b.eval()这个扰动非常粗粒只是为了得到一个可分析的差异来源。真实项目中model_b应该替换成实际训练好的模型比如经过某类数据微调的版本。3.3 准备输入数据选择一组包含图片和对应文本描述的输入。为了减少外部依赖这里可以用少量本地图片或者直接用 PIL 生成颜色块作为图片输入这样后面做特征控制时更容易观察语义变化。from PIL import Image import requests from io import BytesIO def load_images(urls): images [] for url in urls: resp requests.get(url, timeout10) img Image.open(BytesIO(resp.content)).convert(RGB) images.append(img) return images image_urls [ https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w100, https://images.unsplash.com/photo-1519681393784-d120267933ba?w100, ] images load_images(image_urls) texts [a mountain landscape at sunset, a night sky full of stars]如果网络下载图片不方便可以用这种方式生成简易图片import numpy as np def create_solid_image(rgb, size(64, 64)): arr np.zeros((size[0], size[1], 3), dtypenp.uint8) arr[:, :] rgb return Image.fromarray(arr) images [ create_solid_image((135, 206, 235)), create_solid_image((255, 100, 100)), ]用色块图做案例的好处是颜色语义清晰后续做特征编辑时更容易验证颜色方向是否真的被控制住了。3.4 提取两个模型的嵌入对每个输入分别从 model_a 和 model_b 提取image_embeds和text_embeds。确保两个模型使用完全相同的预处理、在同一输入顺序下执行。processor CLIPProcessor.from_pretrained(pretrained_name) def extract_embeds(model, processor, images, texts): inputs processor( texttexts, imagesimages, return_tensorspt, paddingTrue ) with torch.no_grad(): outputs model(**inputs) return outputs.image_embeds, outputs.text_embeds image_embeds_a, text_embeds_a extract_embeds(model_a, processor, images, texts) image_embeds_b, text_embeds_b extract_embeds(model_b, processor, images, texts)这一步的检查点有两个。第一确认image_embeds_a.shape image_embeds_b.shape形状不一致必须先处理否则后续操作会直接报错。第二确认两个模型都在eval()模式下避免 Dropout 或 BatchNorm 引入随机噪声。3.5 对齐输入并计算差异向量def align_and_diff(embeds_a, embeds_b, normalizeTrue): assert embeds_a.shape embeds_b.shape, ( fshape mismatch: {embeds_a.shape} vs {embeds_b.shape} ) if normalize: embeds_a torch.nn.functional.normalize(embeds_a, dim-1) embeds_b torch.nn.functional.normalize(embeds_b, dim-1) diff embeds_b - embeds_a return diff, embeds_a, embeds_b image_diff, image_a, image_b align_and_diff(image_embeds_a, image_embeds_b) text_diff, text_a, text_b align_and_diff(text_embeds_a, text_embeds_b) print(image diff shape:, image_diff.shape) print(text diff shape:, text_diff.shape)这里归一化的原因是 CLIP 的嵌入通常会做 L2 归一化直接用原始嵌入算差尺度差异会干扰后续分析。但并不是所有模型都适合归一化如果模型本身没有归一化层可以先不加后续通过观察余弦相似度决定是否需要。3.6 定位显著特征维度对差异向量按维度统计平均偏移量找出 Top-k 个维度。def top_k_diff_dims(diff, k20): mean_abs diff.abs().mean(dim0) top_indices torch.topk(mean_abs, k).indices.tolist() return top_indices, mean_abs image_top_dims, image_mean_abs top_k_diff_dims(image_diff, k20) text_top_dims, text_mean_abs top_k_diff_dims(text_diff, k20) print(image top dims:, image_top_dims) print(text top dims:, text_top_dims)这样得到的两组维度就是后续实验最关心的重点方向。如果要从语义层面验证这些维度代表什么可以把这些维度的激活值提取出来用探针分类器做训练。from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score # 构造一个简单的二分类实验 X torch.cat([image_a[:, image_top_dims], image_b[:, image_top_dims]], dim0).numpy() y [0] * len(image_a) [1] * len(image_b) clf LogisticRegression(max_iter1000) scores cross_val_score(clf, X, y, cv2) print(probe accuracy:, scores.mean())如果探针准确率明显高于随机水平说明选出的维度确实能区分两个模型的行为差异这个方向可以用于后续控制。4. 利用 Diffing 结果实现特征控制4.1 激活编辑的基本思路找到差异方向后就可以在推理时对模型输出做干预。核心操作是把原始嵌入沿差异方向偏移一定幅度。def steer_embedding(embeds, steering_direction, alpha1.0): direction torch.nn.functional.normalize(steering_direction, dim-1) return embeds alpha * direction这里steering_direction来自前面计算的差异向量。比如想让 model_a 的行为向 model_b 靠拢就沿着image_diff的方向偏移想让行为反向偏移就把 alpha 设为负数。关键参数是alphaalpha0时不干预保持原始行为。alpha1时补全 model_b 与 model_a 的差异。alpha为负数时反向移动削弱 model_b 特征。建议在实验时把alpha从 0.5 开始以 0.5 为步长观察变化不要一上来就设一个很大的值。激活编辑幅度过大会让模型输出不稳定生成内容会明显失真。4.2 选择控制方向的方法实际项目中差异方向往往不是单一的。可以从三种方式中选在单个样本上计算差异向量适合特定输入的控制。在一组样本上取平均方向得到更稳定的特征方向。对多个方向做正交化或 PCA得到互相解耦的编辑方向。# 在多个样本上取平均方向 steer_direction image_diff.mean(dim0, keepdimTrue) steer_direction torch.nn.functional.normalize(steer_direction, dim-1)如果样本覆盖多个语义类别平均方向可能把不同语义相互抵消。这种情况下建议先做聚类再分别计算每个簇的方向向量。4.3 控制效果的验证方式激活编辑本身很灵活但如果不做验证很难确认到底控制住了什么。推荐从三个层面验证。第一检索层面。把编辑后的图像嵌入和一组候选文本嵌入计算相似度看是否更倾向于命中目标文本描述。比如希望增强“温暖色调”特征编辑后模型对“warm”文本的匹配分数应当上升。from torch.nn.functional import cosine_similarity steered_image_a steer_embedding(image_a, steer_direction, alpha2.0) sim_before cosine_similarity(image_a, text_a, dim-1) sim_after cosine_similarity(steered_image_a, text_a, dim-1) print(before:, sim_before.item()) print(after:, sim_after.item())第二分类层面。在预先标注好属性的数据集上比较编辑前后分类准确率。如果增强的特征确实生效目标类别准确率会上升。第三稳定性层面。编辑不能破坏模型对其他属性的判断。建议同时监控一个无关属性的准确率如果这个指标大幅下降说明 alpha 过大或者选错了方向。4.4 不同控制方式对比控制方式原理优点缺点适用场景激活编辑推理时修改嵌入向量无需训练、速度可控需要定位方向易受 alpha 影响快速实验、属性控制LoRA 适配器微调低秩参数效果稳定、任务定向需要训练和存储多个适配器正式任务部署Prompt 修改改变文本提示简单直接对图像侧特征控制有限文本引导基于 Diffing 的编辑用模型间差异锚定方向可解释、可量化对数据代表性要求高模型迭代分析、安全审计从工程角度看Diffing 定位差异方向 激活编辑实现控制是一种低成本、快迭代的路线适合用来做探索性实验。一旦方向验证可靠再考虑是否用 LoRA 等方式固化到模型参数中。5. 常见问题与排查链路5.1 特征形状不一致现象align_and_diff中断言失败两个模型的嵌入维度或 batch 数量不一致。常见原因两个模型使用了不同输入预处理图片尺寸不一致导致视觉编码器输出 patch 数量不同。文本 padding 策略不同导致文本嵌入序列长度不同。模型结构本身发生变化比如隐藏层维度不同。排查方式分别打印image_embeds_a.shape和image_embeds_b.shape。对比processor的配置项尤其是padding和size。查看两个模型的config.json确认hidden_size、projection_dim是否一致。解决方案统一使用同一个processor和同一套预处理参数。如果模型输出维度不同先通过线性投影或池化统一维度再做 Diffing。如果模型结构差异过大可以考虑只比较语义层级的输出而不是逐层对齐。5.2 训练/推理模式不一致导致噪声现象重复运行时差异向量每次变化很大Top-k 维度不稳定。常见原因其中一个模型没有调用eval()Dropout 仍然生效。BatchNorm 使用了训练阶段统计量而非全局统计量。数据加载顺序不一致导致 batch 内容不同。排查方式print(model_a.training) print(model_b.training)解决方案在提取特征前显式对所有模型执行model.eval()。固定随机种子import random import numpy as np random.seed(42) np.random.seed(42) torch.manual_seed(42)保存特征向量或缓存到本地后续分析使用缓存避免反复提取导致的不一致。5.3 Padding 对文本嵌入的干扰现象文本嵌入的差异向量主要被 padding token 区域主导无法反映真实语义差异。常见原因CLIPProcessor默认会 padding 到 batch 内最大长度padding 位置也存在嵌入。不同文本长度差异大时padding token 的激活值可能更稳定地体现出差异从而覆盖真实 token 的差异。排查方式检查input_ids中哪些位置是 padding token。对比有 padding 和无 padding 时差异向量的 Top-k 维度是否一致。解决方案在计算文本嵌入时使用attention_mask对嵌入做加权平均或只提取[CLS]对应位置。将 batch 中文本长度差异控制在较小范围内不要混入过长和过短的样本。5.4 归一化方式选错导致差异被抹平现象Normalize 之后两个模型嵌入几乎完全重合差异向量接近全零。常见原因模型本身输出已经做过 L2 归一化再次归一化不会改变方向但差异值会被压缩。两个模型在某个方向上幅度差异很大归一化把幅度信息去掉了。排查方式分别查看归一化前后的差异向量范数。计算归一化前后的余弦相似度。解决方案以“差异向量范数是否大于某一阈值”为指标先观察不归一化时的表现。如果强烈需要去除幅度干扰可以同时保留原始差异和归一化差异分别做分析。5.5 显存不足或批量太大现象CUDA out of memory或在提取多个样本特征时程序崩溃。排查方式减小 batch size。使用torch.no_grad()并尽量不保存中间梯度。分批提取特征用列表累积后拼接到一起。batch_size 8 features [] for i in range(0, len(images), batch_size): batch_images images[i:i batch_size] batch_texts texts[i:i batch_size] img_emb, txt_emb extract_embeds(model_a, processor, batch_images, batch_texts) features.append((img_emb, txt_emb))对于超大规模特征集建议把嵌入保存到磁盘或向量数据库而不是一次性全部放到内存里。5.6 排查链路表格现象检查顺序可能原因处理建议shape 断言失败1. 打印 shape 2. 对比预处理配置 3. 对比模型 config尺寸不一致、padding 不一致、模型结构不同统一 processor、统一输入尺寸、添加投影对齐差异向量不稳定1. 检查 model.training 2. 固定随机种子 3. 缓存特征Dropout、BatchNorm、数据顺序eval 模式、固定 seed、特征落盘padding 主导差异1. 查看 input_ids 2. 无 padding 对比padding token 噪声使用 attention_mask 或提取有效位置归一化后差异消失1. 比较范数 2. 分别跑余弦和 L2归一化策略偏差同时保留两种差异控制后效果不明显1. 增大 alpha 2. 检查方向是否取平均 3. 换层提取alpha 太小、方向不稳定、层选择不当调 alpha、聚类求方向、改中间层控制后输出崩坏1. 降低 alpha 2. 检查方向维度是否对应层 3. 检查是否归一化方向alpha 过大、方向错误归一方方向、alpha 从 0.5 调起6. 最佳实践与扩展方向6.1 可复现实验的关键清单Diffing 实验很容易因为某个小细节不一致而得出错误结论。建议每次实验前按下面清单检查是否实现了固定随机种子。是否记录了两个模型的完整版本信息包括预训练名称、commit 哈希或本地路径。是否统一了输入预处理流程包括图片 resize、归一化、文本截断和 padding。是否确认所有模型处于eval()模式。是否保存了processor配置和特征提取脚本避免后续重新提取时不一致。是否记录了alpha、Top-k 数量、归一化方式等关键超参数。是否保存了原始嵌入和差异向量到本地方便后续做离线分析。这份清单不只是流程要求它直接影响结论可信度。尤其是模型版本如果后来换过权重却忘记记录前面的分析全部作废。6.2 学习环境与生产环境的差异在学习环境中通常跑通一个最小案例就可以。你会用少量样本、单机 GPU 或 CPU、手动观察 Top-k 维度。这时重点放在理解原理和流程上不需要完善的工程化机制。进入生产环境或正式研究项目后还需要补齐这些内容特征提取层要抽象成可复用模块支持批量跑多个模型。差异分析结果要落库或落盘包括维度索引、差值、样本 ID 和模型版本。激活编辑逻辑可以封装成独立推理接口支持线上动态调整 alpha。增加监控指标比如编辑前后模型输出相似度、下游任务准确率变化。如果涉及用户数据或隐私图片需要脱敏后才能保存特征。生产部署时还要特别关注模型权限和资源控制。激活编辑本身不改变模型参数但如果在服务端暴露了 alpha 参数用户可能通过调整 alpha 让模型输出不稳定。建议只允许调用方传入预设档位而不是直接暴露原始向量。6.3 扩展方向多模态 Diffing 是一个可以继续深入的方向值得投入的方向主要有四个。第一多模型对比。当前案例是比较两个模型实际项目里可能是多个模型、多个训练阶段、多个数据来源。可以把 Diffing 结果做成全局矩阵观察多个模型的演化轨迹。第二跨模态差异分析。不只是分别比较图像嵌入和文本嵌入还可以比较图文匹配分数、跨模态注意力矩阵、融合层输出的差异。这类分析能帮助定位“模型是图像侧理解变了还是文本侧理解变了还是融合阶段变了”。第三基于稀疏编码的特征定位。直接使用 Top-k 维度是启发式思路如果想要更精细的控制可以在差异方向上做稀疏编码或独立成分分析得到相互独立性更强的控制方向。第四与前缀微调、RLHF 等训练范式结合。比如在 RLHF 之后模型行为发生了价值观偏移可以用 Diffing 找出偏移主要集中在哪些抽象维度再决定是用激活控制还是重新训练来修复。对于新手来说比较推荐的进阶练习是先拿两个差距很小但行为可辨别的模型比如用不同数据增强方式微调出的两个模型再完成一次完整的 Diffing 控制实验。这样能更快理解差异方向的可解释性也能积累排查经验。多模态模型 Diffing 不是银弹它不能回答所有关于模型差异的问题但在“定位语义变化”和“定向修改行为”这两个任务上它提供了一条比重新训练更加轻量、比随机试错更加系统的路径。只要把特征提取、差异度量和激活控制三个环节打通这套方法就能在模型迭代、数据审计和可控生成任务中稳定发挥作用。