
之前在做视觉-语言模型的跨域评测时最头疼的问题就是模型在训练分布上表现很好一到测试环境就明显掉点。尝试过直接微调骨干网络效果有提升但显存和显存带宽消耗太大也试过只调 prompt简单场景还行遇到复杂分布偏移就力不从心。后来接触到测试时自适应Test-Time AdaptationTTA的思路并把视线转向了低秩适配Low-Rank AdaptationLoRA这类参数高效手段发现两者结合还存在一个容易被忽略的问题单秩配置的表达能力有限而多秩配置又会带来额外的优化难度。最近读到了 MuRAMulti-Rank Adaptation这个研究方向核心思路是在测试阶段用多秩适配提升视觉-语言模型的泛化能力同时兼顾效率和有效性。这篇文章会把 MuRA 拆开来讲包括它要解决的问题、方法设计逻辑、核心模块的代码实现思路、实验评测中值得关注的指标以及实践中的坑和排错路径。适合正在做 CLIP 等视觉-语言模型落地、跨域评测、参数高效微调相关工作的同学参考。1. 背景与核心概念1.1 从 CLIP 说起视觉-语言模型的测试时困境先回顾一下视觉-语言预训练模型Vision-Language Pre-trained Model的基本范式。以 CLIP 为代表的方法通过大规模图文对比学习把图像和文本映射到同一个语义空间。推理时对于一张测试图片CLIP 会计算图像特征与一组候选文本描述例如 “a photo of a cat”之间的相似度然后选择相似度最高的类别作为预测结果。这种零样本能力在标准数据集上表现不错但一旦测试数据出现分布偏移问题就来了。分布偏移可能来自图像风格变化例如照片变成漫画、素描、油画传感器或拍摄环境变化例如低光照、过曝、模糊类别外观变化例如同一种物体在不同地域的形态差异文本提示形式变化例如测试时使用与预训练分布不同的描述方式。面对这些偏移直接使用冻结的 CLIP 编码器进行零样本推理效果往往不稳定。一个自然的想法是既然预训练模型的知识是充足的能不能在测试阶段利用无标签数据对模型做一些调整使其更好地适配当前测试分布这就是测试时自适应要做的事情。1.2 Test-Time Adaptation概念、分类与边界测试时自适应Test-Time AdaptationTTA指在推理阶段利用测试样本通常是无标签的对模型进行更新使得模型在面对目标域数据时表现更好。它与传统域适应Domain Adaptation的关键区别在于TTA 不要求访问源域训练数据只依赖预训练模型和当前测试输入。按照更新对象的不同大致可以分成几类输入侧调整修改输入图像或文本例如测试时数据增强、对抗扰动、风格归一化。特征侧调整对中间特征做归一化、缩放或线性变换例如 TENT 中更新 BatchNorm 统计量。参数侧调整更新模型部分参数例如 prompt tuning、LoRA、残差适配器。模型结构辅助在预训练阶段加入辅助任务测试时通过辅助分支更新。其中参数侧调整是当前研究的热点。原因也很直观冻结预训练模型只更新少量参数既能保留预训练知识又能针对当前测试分布做定向适配。而 LoRA 等低秩方法把参数更新限制在低维子空间天然适合“少量参数 快速适配”的场景。不过需要区分一个概念Test-Time TrainingTTT与 Test-Time Adaptation。TTT 通常要求在训练阶段设计自监督辅助任务使模型在测试阶段通过该任务更新而 TTA 更多指直接在预训练模型上做无标签自适应。本文围绕的 MuRA 属于后者中参数高效那一支。1.3 Multi-Rank Adaptation 的基本直觉LoRA 的核心思想是对权重矩阵的增量 ΔW 做低秩分解即 ΔW ≈ B·A其中 A 的维度是 r×kB 的维度是 d×rr 远小于 d 和 k。训练时只更新 A 和 B预训练权重保持冻结。这里有一个关键问题秩 r 的选择直接影响适配效果。r 太小低秩子空间的表达能力不足无法覆盖测试分布所需的特征变化r 太大参数量上升测试时优化更容易过拟合到单个 batch 的噪声上固定一个 r又很难同时适配不同层级、不同语义粒度的特征变化。MuRA 的思路是不再使用单一秩而是构建多个不同秩的适配分支让模型在测试时自动组合这些分支的贡献。这样既保留了低秩方法参数高效、训练稳定的优点又通过多秩组合提升了表达能力。类比来说单秩适配像是固定带宽的通道多秩适配则是多个不同带宽通道的组合可以根据当前测试分布动态分配。2. 方法拆解MuRA 的核心设计2.1 为什么单秩低秩适配不够先看一个具体的现象。假设我们要适配一个 ViT-B/16 规模的 CLIP 模型图像编码器有 12 层 Transformer不同层的特征语义不同浅层更关注颜色、纹理、边缘深层更关注物体部件、场景结构。如果对所有层统一使用 r4 的低秩适配浅层可能因为表达能力不足而无法充分调整底层特征深层又可能因为秩不足而难以捕获复杂的语义变化。如果把 r 直接调大到 16 或 32又会带来两个问题参数量成倍上升测试时反向传播和参数更新的计算开销增加在无标签测试数据上更高维的优化空间更容易让模型陷入过拟合尤其是 batch 较小时。很多实验也表明固定秩在分布偏移幅度变化较大时并不鲁棒偏移小时用大秩是浪费偏移大时用小秩则欠拟合。因此一个好的测试时适配方法应当让“子空间大小”本身具有自适应性。MuRA 的多秩设计正是为了缓解这种矛盾。2.2 多秩分解与并行分支设计MuRA 的整体思路可以拆成以下几个步骤。假设模型某一层的权重矩阵为 W输入为 x输出为 h。常规的低秩适配计算为h W x (B A) x其中 A 是 r×kB 是 d×r。这里 r 是固定的。MuRA 的做法是准备多个不同秩的分支记为 r1, r2, ..., rn每个分支有自己独立的 A_i 和 B_ih W x Σ_i s_i · (B_i A_i x)其中 s_i 是分支权重或可学习的缩放系数。训练时所有 A_i 和 B_i 可以一起更新也可以设计成部分分支共享底座、只在秩维度上扩展。这里有几个设计上的细节需要展开。第一分支之间应该尽量解耦。如果不同秩的分支共享同一个初始化训练时可能出现冗余更新导致高秩分支并没有提供额外信息。更合理的做法是让每个分支单独初始化并加上正交性约束或稀疏正则促使不同秩的子空间互补。第二多秩分支的合并方式不一定是简单的线性相加。有些实现会采用门控机制例如根据输入特征计算一组权重对各个分支的输出做加权融合。另一种做法是直接对所有分支的输出求和然后经过一个非线性激活函数。前者更灵活但会带来额外的参数量和优化复杂度后者更简洁适合测试时快速适配。第三多秩适配可以作用于 Query、Key、Value 的投影矩阵也可以作用于 MLP 中的全连接层。在视觉语言模型中常见做法是对图像编码器的 attention 层和文本编码器的 attention 层同时适配甚至只适配图像编码器保留文本侧冻结。2.3 前向计算与参数更新流程在测试时阶段MuRA 的完整流程可以概括为输入一个 test batch包含图像和对应的文本描述用冻结的 CLIP 图像编码器和文本编码器分别提取特征前向传播时多秩分支以残差形式叠加到预训练权重上在无标签测试 batch 上构造代理任务例如熵最小化、对比学习、特征一致性约束反向传播只更新多秩分支参数不更新预训练骨干用更新后的模型对当前 batch 做预测处理下一个 batch 时可以选择保留参数或重置参数。这里有一个重要选择是否跨 batch 累积更新。如果测试数据是流式的且分布随时间变化累积更新可能有帮助如果每个 batch 来自独立同分布建议在每个 batch 上单独优化后重置避免误差累积。MuRA 相关实验通常会对比这两种策略实际使用时应根据业务场景选择。熵最小化是最常用的代理任务。对于分类任务模型对某个 batch 的预测分布越集中说明模型越自信熵越低。测试时目标是通过更新多秩分支使预测分布更加集中。实现如下loss (probs * torch.log(probs 1e-10)).sum(dim-1).mean()当预测分布接近均匀分布时说明模型对当前测试样本完全没有把握此时熵很高通过优化多秩分支模型能够逐渐适应当前分布的判别模式从而降低熵。除了熵最小化还可以引入特征一致性约束。例如对同一张图像做两次不同的随机增强让多秩适配后的特征在语义上保持一致。这种一致性损失可以缓解仅靠熵最小化带来的坍缩问题。2.4 秩搜索与超参数敏感性多秩设计不可避免地引入一个新的超参数选择的秩集合。例如可以设置 {4, 8, 16}也可以设置 {8, 16, 32}。这里有一个取舍秩的跨度越大子空间覆盖能力越强但参数量也会增加秩的粒度越细组合越灵活但分支数量太多会增加计算开销。一种做法是预设一组秩然后让模型自动学习各分支的重要性。具体实现时可以为每个分支设置一个可学习的权重 λ_i并在损失函数中加入对 λ 的稀疏正则让模型在训练结束时保留少量重要分支。另一种做法是使用简单的秩搜索在验证集上比较几组候选秩集合的效果选择最优配置。需要注意测试时自适应场景下每个测试 batch 都做一次完整的秩搜索是不现实的。更合理的方案是在开发集上离线确定秩集合然后在测试阶段固定使用。超参数敏感性方面MuRA 类方法通常比较关注以下几个点学习率测试时更新的学习率过大容易在单个 batch 上过拟合过小则更新不足。建议初始学习率设到 1e-3 到 1e-4 量级并根据 batch 大小调整。batch 大小batch 越小梯度噪声越大batch 越大代理任务越稳定但显存占用也越高。实际中 16 或 32 是较为常用的选择。更新步数每个 batch 更新 1 步即可增加步数可能带来边际收益但也会拖慢推理速度。缩放系数 α低秩适配中通常有 α 控制更新幅度测试时设置过大会导致特征偏移严重建议与秩保持适当比例。3. 核心模块的代码实现思路3.1 环境准备与实验框架选择这里不绑定具体硬件环境版本因为测试时自适应对显存和框架版本有一定要求需要根据你的实际环境调整。以下是我实验时常用的一套环境参考操作系统Ubuntu 20.04 / 22.04 Python3.9 或 3.10 深度学习框架PyTorch 1.13 或 2.x CUDA11.7 或 12.x根据 PyTorch 版本匹配 视觉语言模型open_clip_pytorch 或 transformers 中的 CLIP 实现推荐使用 open_clip_pytorch因为它对多种预训练 CLIP 权重的支持较好且对自定义修改更友好。如果使用 Hugging Face transformers也可以但需要额外封装 forward 逻辑。示例项目结构可以这样组织mura_demo/ ├── config.py # 超参数配置 ├── model.py # CLIP 模型 多秩适配模块 ├── mura_module.py # 多秩适配核心模块 ├── tta_loop.py # 测试时优化主循环 ├── data_loader.py # 测试数据加载 └── main.py # 入口脚本在动手写代码之前先提醒一点本文的代码是设计思路示意用于理解 MuRA 的模块组织和更新逻辑不同版本、不同预训练模型下的具体实现会有差异。3.2 冻结 CLIP 骨干的 LoRA 基础模块先看一个最基础的 LoRA 模块实现理解低秩适配的最小单位。这个模块可以插入到 CLIP 的 attention 层中对 qkv 投影矩阵做适配。文件路径mura_demo/mura_module.pyimport torch import torch.nn as nn import math class LoRALayer(nn.Module): 基础 LoRA 模块。 对原始线性层做低秩增量适配。 def __init__(self, in_features, out_features, rank8, alpha8): super().__init__() self.in_features in_features self.out_features out_features self.rank rank self.alpha alpha self.scaling alpha / rank # A 将输入映射到低秩空间 self.lora_A nn.Parameter(torch.zeros(rank, in_features)) # B 将低秩空间映射到输出空间 self.lora_B nn.Parameter(torch.zeros(out_features, rank)) nn.init.kaiming_uniform_(self.lora_A, amath.sqrt(5)) nn.init.zeros_(self.lora_B) def forward(self, x): # 低秩增量部分 delta self.lora_B (self.lora_A x.T) delta delta.T * self.scaling return delta这个模块本身不包含预训练权重它专门计算增量部分。实际使用时需要先拿到原始线性层的输出再加上 LoRA 的增量。在使用 open_clip 时可以通过修改模型的 forward 过程来加入 LoRA而不是直接替换原始 Linear 层。这样可以避免预训练权重被意外修改。接口设计如下文件路径mura_demo/mura_module.pyclass LoRAWrapper(nn.Module): 将基础 LoRA 模块挂载到指定的线性层上。 def __init__(self, original_linear: nn.Linear, rank: int, alpha: int): super().__init__() self.original_linear original_linear self.in_features original_linear.in_features self.out_features original_linear.out_features self.lora LoRALayer( self.in_features, self.out_features, rankrank, alphaalpha ) def forward(self, x): original_out self.original_linear(x) lora_out self.lora(x) return original_out lora_out这个包装类的作用是保留原始线性层的完整行为同时叠加低秩增量。测试时优化过程中冻结 original_linear 的参数只更新 lora 的参数。3.3 多秩适配模块多秩模块与单秩 LoRA 的区别在于它内部持有多个不同秩的 LoRA 分支。实现时可以把多个 LoRALayer 组合起来并加入可学习的融合机制。这里给出一个可运行的多秩模块示例文件路径mura_demo/mura_module.pyclass MultiRankAdapter(nn.Module): 多秩适配模块。 ranks: 秩列表例如 [4, 8, 16] alpha: 缩放系数可以与 ranks 同时指定为列表 def __init__(self, in_features, out_features, ranks(4, 8, 16), alphaNone): super().__init__() self.in_features in_features self.out_features out_features self.ranks ranks if alpha is None: alpha [r for r in ranks] self.alpha alpha self.branches nn.ModuleList() for r, a in zip(ranks, alpha): branch LoRALayer( in_features, out_features, rankr, alphaa ) self.branches.append(branch) # 可选让每个分支的缩放系数可学习 self.branch_weights nn.Parameter(torch.ones(len(ranks))) def forward(self, x): outputs [] for w, branch in zip(self.branch_weights, self.branches): out branch(x) outputs.append(out * torch.sigmoid(w)) total_delta torch.stack(outputs, dim0).sum(dim0) return total_delta在这个实现中每个分支独立初始化。branch_weights 是可学习的经过 sigmoid 后限制在 0 到 1 之间避免某个分支的权重过大影响整体特征稳定性。如果想进一步促进不同分支之间的互补性可以在训练损失中加入分支输出的正交性约束。正交性约束的目标是让不同秩分支产生的增量特征尽量不相关文件路径mura_demo/mura_module.pydef orthogonal_loss(adapter: MultiRankAdapter, x): 计算多秩分支输出之间的正交性损失。 该损失会促使不同秩分支学习到互补的信息。 branch_outputs [] for branch in adapter.branches: branch_outputs.append(branch(x)) loss 0.0 num_branches len(branch_outputs) for i in range(num_branches): for j in range(i 1, num_branches): # 将特征拉平后计算相关性 fi branch_outputs[i].reshape(branch_outputs[i].size(0), -1) fj branch_outputs[j].reshape(branch_outputs[j].size(0), -1) fi fi / (fi.norm(dim1, keepdimTrue) 1e-10) fj fj / (fj.norm(dim1, keepdimTrue) 1e-10) corr (fi * fj).sum(dim1).abs().mean() loss corr return loss / (num_branches * (num_branches - 1) / 2)正交性损失在测试时自适应中可选。如果发现不同分支输出的特征高度相似说明多秩没有提供真正的多样性此时可以加入这个约束。3.4 测试时优化循环接下来是把多秩适配模块接入 CLIP并实现测试时优化循环。这里用一个简单的熵最小化目标作为示例。文件路径mura_demo/tta_loop.pyimport torch import torch.nn.functional as F def entropy_loss(logits): 熵最小化损失。 logits: 模型对当前 batch 的预测得分形状为 [batch, num_classes] probs F.softmax(logits, dim-1) log_probs F.log_softmax(logits, dim-1) loss -(probs * log_probs).sum(dim-1).mean() return loss class TTAEngine: def __init__(self, model, adapter_modules, optimizer, steps1): self.model model self.adapter_modules adapter_modules self.optimizer optimizer self.steps steps def freeze_backbone(self): 冻结预训练骨干参数只保留适配模块可学习。 for name, param in self.model.named_parameters(): param.requires_grad False for module in self.adapter_modules: for param in module.parameters(): param.requires_grad True def evaluate_batch(self, images, texts, tokenizer, image_processor): 对当前 batch 执行测试时适应 推理。 self.model.train() self.optimizer.zero_grad() # 输入处理 images image_processor(images) text_tokens tokenizer(texts) # 多步更新 for _ in range(self.steps): logits self.model(images, text_tokens) loss entropy_loss(logits) loss.backward() self.optimizer.step() self.optimizer.zero_grad() # 更新完成后推理 self.model.eval() with torch.no_grad(): logits self.model(images, text_tokens) preds logits.argmax(dim-1) return preds这个引擎的关键点在于每个 batch 会先做 forward backward update 的循环更新步数 steps 设置为 1 时每个 batch 只做一次参数更新更新完成后用更新过的模型做最终预测。需要特别注意的是在测试时更新中BatchNorm 的 running stats 也可能被更新。如果预训练模型带有 BatchNorm 层建议将其切换到 eval 模式或冻结否则 small batch 上的统计量更新会带来不稳定。对于 CLIP 这类以 Transformer 为主的模型通常没有 BatchNorm 问题但 LayerNorm 也需要保持冻结。文件路径mura_demo/main.pyimport torch from model import build_model from mura_module import MultiRankAdapter from tta_loop import TTAEngine def main(): model, image_processor, tokenizer build_model() # 找到需要适配的目标层这里以 q_proj 为例 target_layers [] for name, module in model.named_modules(): if name.endswith(q_proj): target_layers.append((name, module)) adapter_modules [] for name, module in target_layers: adapter MultiRankAdapter( in_featuresmodule.in_features, out_featuresmodule.out_features, ranks(4, 8, 16) ) # 将 adapter 挂到原模块旁边实际使用时需要替换 forward setattr(model, name _adapter, adapter) adapter_modules.append(adapter) optimizer torch.optim.Adam( [p for m in adapter_modules for p in m.parameters()], lr1e-3 ) engine TTAEngine( modelmodel, adapter_modulesadapter_modules, optimizeroptimizer, steps1 ) # 假设已经准备好了 test_loader # for images, texts in test_loader: # preds engine.evaluate_batch(images, texts, tokenizer, image_processor) if __name__ __main__: main()这段示例中有一个不完全严谨的地方setattr 只是把 adapter 挂到了模型对象上但没有真正改变 q_proj 的前向计算方式。实际工程中更推荐的做法是直接替换目标层或者对模型源码做小范围修改。比如将原 q_proj 替换为 MultiRankAdapterWrapper内部的原始线性层保持冻结外部叠加多秩增量。具体可以结合 open_clip 的 attention 模块源码调整。4. 实验验证与效率讨论4.1 评测数据集与指标测试时视觉-语言泛化通常会沿用 CLIP 的零样本评测协议并引入分布偏移更大的数据集。比较常见的评测集合包括ImageNet 以及 ImageNet 的多个变体数据集例如 ImageNet-V2、ImageNet-Sketch、ImageNet-A、ImageNet-R跨数据集迁移评测例如在 ImageNet 上预训练的 CLIP直接评测在 CIFAR-10、CIFAR-100、OxfordPets、Flowers102、Food101 等分类数据集上的表现带有自然分布偏移的数据集例如 Waterbirds、Camelyon17 等。评测指标主要看 Top-1 Accuracy。对于 TTA 方法还需要关注收敛速度模型在多少个 batch 后达到稳定效果稳定性不同随机种子下效果波动是否大极端 batch 下的表现batch size 从 1 到 64 的变化对效果的影响。MuRA 论文中通常会报告冻结模型、单秩 LoRA、多秩 LoRAMuRA在不同评测集上的准确率对比。这里不虚构具体数值但一般来说多秩适配在分布偏移较大的数据集上会比单秩 LoRA 有更明显的提升而在接近源域分布的数据集上两者差距相对较小。这个趋势符合“多秩提供更丰富表达空间”的直觉。4.2 单模型效率测试时自适应的核心痛点之一是效率。效率可以从两个维度理解时间效率和显存效率。时间效率方面多秩分支相比单秩分支会增加前向和反向的计算量。但由于每个分支的秩都较小整体增量仍然是可控的。以 ViT-B/16 为例如果对 12 层 attention 的 q_proj 和 v_proj 分别挂载 3 个秩为 [4, 8, 16] 的分支额外参数量的比例仍然只有骨干网络参数量的很小一部分。显存效率方面需要考虑优化器状态。测试时优化器使用 Adam 时每个可学习参数都会对应一阶动量和二阶动量这会占用额外显存。为了降低显存占用可以选择使用 SGD 优化器无动量状态占用更小使用 Adafactor 这类内存友好的优化器让不同层共享优化器状态或对梯度做近似截断。在 batch size 为 16、输入分辨率 224×224、单张消费级显卡的条件下MuRA 类方法通常是可跑的但建议提前用 torch.cuda.max_memory_allocated 做好显存监控避免训练到一半 OOM。4.3 关键结论从方法论角度看MuRA 的有效性建立在两个关键点上。第一多秩组合提供了更丰富的适配子空间。单秩适配相当于在一个固定维度的子空间中搜索最优增量多秩适配则是在多个维度候选子空间的并集中搜索理论上能覆盖更多方向的偏移。第二可学习的分支权重让模型可以根据当前输入自动选择适配方向。这一点在分布偏移幅度多变时特别重要。不过也要注意多秩组合并不总是优于精心调参的单秩 LoRA。如果测试分布相对稳定或者单个秩已经足够表达所需的增量多秩带来的额外参数反而可能造成过拟合。因此在实际项目中建议把 MuRA 当作“更鲁棒的测试时适配选项”而不是绝对最优解。5. 常见问题与排查思路在实践 MuRA 类方法时下面这些问题比较常见整理成表格方便查阅。问题现象常见原因解决思路测试时更新后准确率反而下降学习率过大模型在单个 batch 上过拟合降低学习率到 1e-4 量级减少更新步数到 1 步不同 batch 效果波动很大batch 太小梯度噪声大或没有重置参数增大 batch size采用 batch 级重置策略多秩分支输出几乎相同分支初始化相同或缺少多样性约束不同秩分支独立初始化加入正交性损失显存占用过高优化器状态占用了额外显存换用 SGD 或 Adafactor限制适配层数量模型 train 模式改变了 LayerNorm 行为测试时把整个模型切到了 train 模式保持 LayerNorm 的 eval 状态只允许适配模块更新适配效果在某个数据集上不提升该数据集偏移方向不在低秩子空间覆盖范围内扩大秩集合范围增加适配层深度分支权重 sigmoid 后过于接近 0某些分支被完全抑制多秩退化为单秩移除 branch_weights 或改用非负 softmax 初始化优化器状态在流式数据上累积误差跨 batch 保存参数导致误差累积根据业务场景选择 batch 级重置接下来选几个典型问题详细说明。第一个坑是“测试时更新后效果反而变差”。这个现象通常发生在学习率设置过高或更新步数过多时。测试时没有标签代理任务只是对模型的约束并不是真实目标因此优化代理任务并不总是带来真实准确率提升。建议在开发集上先做一个小型网格搜索确定学习率和步数的合理范围再放到测试集上。第二个坑是“多秩没有带来增益”。如果多秩分支的输出高度相关那么即使设置了多个秩实际表达能力也只相当于其中一个分支。可以在训练阶段加入正交性损失或者直接观察各分支输出的特征相似度矩阵判断是否存在冗余。第三个坑是“显存不足”。测试时自适应需要同时保存预训练模型权重、中间激活和优化器状态。常用的缓解思路包括梯度检查点gradient checkpointing、选择更低秩范围、只对部分层做适配、减少 batch size。需要说明的是梯度检查点会增加计算时间是一种以时间换显存的做法适合批处理场景如果追求实时推理则更适合减少适配层数量。6. 工程最佳实践与下一步6.1 代码层面的工程建议第一个建议是统一管理可学习参数。测试时自适应中很容易出现“以为冻结了实际某层还在更新”的问题。一个比较稳妥的做法是显式维护一个可学习参数列表而不是依赖 requires_grad 的隐式状态。def collect_adapter_params(model): target_names [lora_A, lora_B, branch_weights] params [] for name, param in model.named_parameters(): if any(key in name for key in target_names): params.append(param) return params在构造优化器时直接传入这个列表确保只有适配模块参与更新。第二个建议是对测试时更新过程做日志记录。记录每个 batch 的损失值、预测熵、更新前后准确率可以帮助快速定位问题。import time import logging logger logging.getLogger(tta) def log_batch(batch_idx, loss_val, entropy_val, elapsed): logger.info( fbatch{batch_idx}, loss{loss_val:.4f}, fentropy{entropy_val:.4f}, time{elapsed:.3f}s )第三个建议是保持实验可复现。测试时自适应的随机性来源包括dropout、数据增强、初始化种子、优化器随机性。建议固定所有随机种子并在日志中记录 PyTorch 版本、CUDA 版本、CLIP 预训练权重来源。6.2 训练与评测规范在生产环境中使用测试时自适应需要额外关注可控性。因为模型在测试阶段仍然会更新这意味着每次推理时模型的参数都可能不同。对于面向用户的线上服务这种动态性可能带来风险。一个折中方案是在离线开发集上运行完整的测试时自适应流程确定最优配置后把适配后的参数固化下来作为新的推理权重。这样既利用了测试时自适应的思路又避免了线上每 batch 更新的不确定性。另一个方案是设置更新阈值。只有当模型对当前 batch 的预测熵高于某个阈值时才触发参数更新否则使用当前的冻结模型直接推理。这样可以避免在模型已经表现良好的简单样本上浪费计算资源。6.3 可以继续深入的方向如果对 MuRA 感兴趣后续可以从以下几个方向继续探索。第一不同的秩组合策略。目前的多秩方案是并行分支可以考虑层级递进的多秩结构让不同层使用不同的秩集合。例如浅层使用小秩、深层使用大秩更符合特征语义从低到高的规律。第二多秩分支在不同模态上的应用。视觉-语言模型中图像编码器和文本编码器的适配需求并不相同。可以只对图像侧做多秩适配也可以两侧同时适配但使用不同的秩集合。对比这两种策略在不同分布偏移下的表现是一个有意思的方向。第三多秩适配与更复杂的代理任务结合。除了熵最小化还可以尝试对比学习、特征掩码重建、类原型对齐等目标。不同的代理任务会引导适配过程关注不同的特征维度多秩结构恰好提供了足够的参数空间来支持这些任务。第四把多秩适配扩展到更大的模型。目前很多 TTA 实验围绕 ViT-B/16 规模的 CLIP 展开在 ViT-L/14、ViT-H/14 甚至多模态大模型上多秩组合的收益是否存在计算开销是否能被接受都需要进一步验证。7. 结尾与实用建议回到最初的问题测试时遇到分布偏移模型掉点严重怎么在不大幅增加计算成本的前提下提升泛化能力MuRA 给出的回答是与其把希望寄托在单一低秩子空间不如用多秩组合覆盖更丰富的特征变化方向并在测试时通过无标签数据快速调整。这个思路并不复杂但工程实现上有不少细节值得注意分支的初始化方式、是否加入正交性约束、优化器选择、batch 级重置策略、线上部署时的阈值更新机制这些都会直接影响最终效果。如果你准备在自己的项目中尝试建议从一个小规模的验证开始先固定 CLIP 骨干在 1 到 2 个分布偏移数据集上对比冻结模型、单秩 LoRA、多秩 LoRA 三者的效果和显存开销确定多秩分支确实带来增益后再逐步扩大适配范围。同时准备好详细的日志和回滚机制因为测试时更新引入的“推理不确定性”是生产中必须重视的风险点。测试时自适应仍然是一个活跃的研究方向MuRA 代表的是其中“参数高效 多秩表达”这一支思路。理解它背后的设计动机再结合自己的业务场景做取舍会比直接套用某个开源实现走得更远。如果这篇文章对你有帮助可以收藏备用后面遇到 CLIP 跨域掉点的问题时按这个思路做排查和适配应该能少走一些弯路。