51c大模型~合集157
自己的原文哦~ https://blog.51cto.com/whaosoft/14058598
#HiddenDetect
多模态大模型存在「内心预警」,无需训练,就能识别越狱攻击
多模态大模型崛起,安全问题紧随其后
近年来,大语言模型(LLMs)的突破式进展,催生了视觉语言大模型(LVLMs)的快速兴起,代表作如 GPT-4V、LLaVA 等。通过将图像与文本深度融合,LVLMs 在图文问答、视觉推理等任务中大放异彩。但与此同时,一个严峻的问题也悄然浮现 ——LVLMs 比起纯文本模型更容易被 “越狱”。攻击者仅需通过图像注入危险意图,即使搭配直白的指令,模型也往往难以拒绝。
为应对这一挑战,已有方法尝试用跨模态安全微调、系统提示词设计或外部判别模块来加固模型防线。然而,这些方法普遍存在训练成本高、泛化能力差、甚至误判正常输入的风险。
模型其实 “心里有数”:越狱时隐藏状态在报警
来自香港中文大学 MMLab 与淘天集团未来生活实验室的研究者提出了 HiddenDetect—— 种无需训练的越狱检测新方法。核心作者包括姜一雷,谭映水,高欣颜,岳翔宇。
他们的核心发现是:即使 LVLMs 表面上被越狱、生成了不当内容,其隐藏状态中依然保留着拒绝的信号。特别是在模型的中间层,这些信号往往比最终输出更早、更敏感地 “察觉” 到潜在风险。更有趣的是,文字输入和图像输入会激活完全不同的 “安全通路”,也就是说,LVLMs 对不同模态的 “危险感知” 机制是有区分的。
论文已被 ACL2025 main conference 收录。
项目开源 github 链接:https://github.com/leigest519/hiddendetect
arxiv 链接:https://arxiv.org/abs/2502.14744
从 “拒绝语义” 中解码多模态大模型的安全感知
图 1: 基于模型自身激活模式的多模态越狱检测方法。
首先,研究者从模型拒绝回答不安全输入的响应中,统计出一组高频出现的、具有明确拒绝语义的 token(如 “sorry”, “unable”, “unfortunately” 等),并利用 one-hot 编码的方式,在词汇空间中构造出一个 “拒绝语义向量” (RV),作为模型拒绝行为的表示。随后,研究者将模型各层的隐藏状态通过反嵌入层投影回词汇空间,并计算出其与 RV 的余弦相似度,以此衡量当前层所包含的拒绝语义强度。该过程会生成一个长度等于模型层数的向量 F,用于刻画模型在各层对拒绝语义的激活强度。
实验结果显示,F 在安全与不安全输入之间存在显著差异:对于安全样本,F 的整体数值普遍较低;而对于不安全输入,F 通常在中间层逐步升高至峰值,随后在最后几层出现明显回落。此外,无论输入是否安全,F 在最后一层的数值仍普遍高于倒数第二层,表明模型在最终输出前仍保留一定的拒绝倾向。
为进一步分析模型的安全响应机制,研究者构建了三个小样本输入集,分别用于衡量模型在不同类型输入下的拒绝激活表现。其中,安全输入集由无害样本组成,既包含纯文本输入,也包含图文组合输入;另两个不安全输入集则分别对应纯文本攻击样本和图文联合的攻击样本。
如图 2 所示,每组样本都计算出其对应的拒绝强度向量 F,并将不安全输入的 F 与安全输入的 F 相减,得到 “拒绝差异向量” (FDV),用于衡量模型在处理不安全输入时相较于安全输入所产生的激活差异。
图 2: 通过少样本分析方法,识别出模型中对安全最敏感的关键层。
模态不同,响应路径也不同
如图 3 所示,两种模态的 FDV 曲线均表明模型在部分中间层对拒绝信号的响应强度显著高于输出层,说明这些中间层对安全性更加敏感。具体而言,文本输入的拒绝激活差异在较早的层级便迅速增强,而图文输入的响应整体偏后,且强度相对较弱,说明视觉模态的引入在一定程度上削弱了模型拒答机制的早期响应能力。
图 3:纯文本样本和跨模态样本的 FDV 曲线。
实验还发现如果模型对拒绝信号的强激活集中在更靠后的层,或者整体激活强度变弱,越狱攻击就更容易成功。有趣的是,研究者发现,仅仅为一条文本攻击提示加上一张图片,就可能让模型的拒绝反应变得延迟,原本中层就能激活的拒绝信号被 “推迟” 到了后层,整体响应强度也降低,从而削弱了模型的安全防护能力。
最终,该小样本分析方法通过 FDV 值成功定位了模型中对不同模态输入安全性最敏感的层。研究者将模型最后一层的差异值作为参考基线,因其对部分不安全输入缺乏足够辨别力;而那些 FDV 显著高于末层的中间层,通常具备更强的安全判别能力。
进一步地,只需累积在这些关键层上的拒绝激活强度,便可有效识别潜在的不安全样本,从而构建出一个高效、无需训练、具备良好泛化能力的越狱检测机制。
实验结果
研究团队在多个主流 LVLM(包括 LLaVA、CogVLM 和 Qwen-VL)上系统评估了所提出的检测方法,涵盖纯文本越狱(如 FigTxt)和跨模态图文攻击(如 FigImg 和 MM-SafetyBench)等多种攻击类型。此外,研究者还在 XSTest 数据集上测试了方法的稳健性。该数据集包含一些安全但易被误判的边界样本,常用于评估检测方法是否过度敏感。实验结果表明,该方法在保持高检测效果的同时,具备良好的鲁棒性和泛化能力。
可视化
图 4:每一层隐藏状态中最后一个 token 的 logits 被投影到由拒绝向量(RV)及其正交方向构成的语义平面。
结论与展望
安全是大模型走向真实世界应用过程中必须优先考虑的问题。HiddenDetect 提出了一种无需训练、基于激活信号的检测方法,为提升多模态模型的安全性提供了新的思路。该方法结构轻量、部署灵活,已在多个模型与攻击类型中展现出良好效果。尽管如此,该方法目前仍主要聚焦于风险提示,尚未对模型行为产生直接调控。未来,研究团队希望进一步拓展方法能力,并深入探索模态信息与模型安全性的内在关联,推动多模态大模型朝着更可靠、更可控的方向发展。
作者团队来自淘天集团算法技术 - 未来实验室团队和香港中文大学 MMLab。未来生活实验室致力于建设面向未来的生活和消费方式,进一步提升用户体验和商家经营效果。实验室聚焦大模型、多模态等 AI 技术方向,致力于打造大模型相关基础算法、模型能力和各类 AINative 应用,引领 AI 在生活消费领域的技术创新。
#欺骗、隐瞒、删库跑路,AI程序员彻底失控翻车
还记得前几天会睡觉的 Claude 吗?
都说 AI 越来越像人了,如果说 Claude 最多是个「懒人」的话,那下面要聊的这位可是个十足的「坏人」。
就在前不久的 19 日,SaaStr.AI 创始人兼首席执行官 Jason 在推特上报告了一件令行业震惊的事件:
Replit 在一天的工作结束后,删除了整个公司的生产数据库。
原来不仅仅人类程序员会「删库跑路」,AI 程序员也会。
虽说 AI 没法真的跑路,但是更恶劣的情况是它会撒谎,会隐瞒情况。
Jason 声称,Replit 在做单元测试的时候生成测试全部通过,在批处理失败的时候,才抓住 Replit 撒谎的情况。
更夸张的是,当数据库被删除之后的第一反应自然是回滚。但 Replit 却斩钉截铁:「无法回滚」。
给出的原因是:删除命令是毁灭性的,数据库没有内置回滚命令,Replit 并不会自动备份数据库,已经太晚了。
但 Jason 发现自己又被骗了。
回滚功能其实是有效的。
反复横跳的使用体验让人的心情直接坐上了跳楼机。
「简直离谱 (JFC)」!Jason 在推文中接连使用该词汇表达自己的强烈情绪。
「我知道 Replit 是一个工具,和其他工具一样,也有缺陷。但是如果它忽略所有命令并删除您的数据库,地球上的任何人怎么能在生产中使用它呢?」
这份推文引起了广泛的关注,再一次激发了对于人工智能编程开发工具的可靠性的质疑,尤其是针对当事平台 Replit,已经产生了一定程度的信任危机。
除了「删库」危机以外,Jason 还反复提及了 Replit 无法实现「代码冻结」的功能缺陷,其无法冻结部分代码免于修改,这给实际的应用带来了非常大的困扰。
Replit 最初是一个协作编码平台,后来发展成为一个由人工智能驱动的软件创建生态系统,其核心功能是通过使用自然语言描述来构建完整的应用程序。Replit 在近期的增长十分惊人,2025 年 7 月,Replit 宣布其拥有 50 万企业用户。据投资者史蒂夫・凯斯(Stevie Case) 称,其收入在不到六个月的时间里增长了 10 倍,达到 1 亿美元。同月,Replit 宣布与微软建立合作伙伴关系,将 Replit 技术集成到微软的多款企业工具中。
在「删库」事件发生后,Replit 创始人 Amjad Masad 发推对此事进行了详细回应,并且明确将迅速采取行动提高稳定性和安全性,且愿意为 Jason 提供补偿。
回应全文如下:
我们注意到了 Jason 的帖子。确实存在 Replit Agent 在开发过程中误删生产数据库数据 的问题。这是完全不可接受的,绝不应该发生。
我们立即采取了以下措施:
即使是周末,我们也在加班推进数据库的开发环境和生产环境的自动隔离功能部署,从根本上杜绝此类问题。预发布环境也正在构建中,明天会有更多更新。
幸运的是,我们有备份机制。如果 Agent 出错,可以一键恢复整个项目状态。
此次事故中,Agent 没有访问到正确的内部文档。我们正在推出更新,强制 Agent 在 Replit 知识库中进行文档检索。
此外,我们清楚地听到了大家对 “代码冻结(code freeze)” 的抱怨 —— 我们正在开发规划 / 仅聊天模式,让用户可以在不影响代码库的情况下进行战略性思考。
在周五早上第一时间看到此事后,我已主动联系 Jason 提供帮助。我们将为此事给予退款赔偿,并将开展事故复盘,彻查问题成因并优化未来的响应机制。
我们感谢 Jason 及所有用户的反馈。目前,我们的首要任务是全面提升 Replit 平台的安全性与稳定性,我们正在迅速行动中。
上下滑动查看原文
目前,Jason 仍在发推更新使用 Replit 的最新进展,若感兴趣可以自行关注。
或许这次事件不是个例,有网友称经常遇到相似的状况,尤其是在移动平台上使用 AI 编程的时候。
这次事件恰好为所有 AI 编程工具以及使用 AI 工具作为生产力的人们敲响了警钟。不仅是 AI 编程服务商要为此采取行动,在使用 AI 编程工具的时候一定要遵守开发规范和安全流程,尤其需要关注对于 AI 访问数据的权限。
Reddit 网友也确实指出了这次事件背后的人为原因,「这完全是由人类操作员造成的,因为他们没有理解将模型直接连接到生产数据库的风险,对此没有任何借口,尤其是在没有备份的情况下』。
人类程序员都有非常严格的权限限制标准,更何况 AI 程序员呢?
#大模型RL训练框架的进化之路
本文系统梳理了大模型强化学习训练框架从SFT到RL的演进,重点剖析了rollout与训练模块解耦、资源调度、异步通信等核心技术挑战,并对比了OpenRLHF、slime、ROLL、verl等主流开源框架的优劣与适用场景。
大模型RL框架的进化之路
2024年O1路线发现之前,主流的有监督的机器学习,是让学生去学习一个问题的标准答案,根据loss更新模型参数。
训练流程相对简单,pytorch和tensorflow基于此做了各种训练的加速。
chatgpt O1发布之后。
SFT不断弱化,更多的被丢到退火,RL的重要性越来越高。
RL算法也一直在更新,从dpo到ppo,再到现在的grpo/rloo/reinforce++/DAPO。除了现在基本上没人用的dpo,大框架是一致的。
如果说SFT是呆板的学习标准答案,那么RL是给定题目后,让学生自己寻找答案,给学生的解题打分,最后学生根据打分结果来学习。
所以,RL框架就分成了三大块,细节可以参看mengyuan的文章 - https://zhuanlan.zhihu.com/p/677607581
a) 学生自己寻找答案
就是大家天天说的rollout
b) 给学生答案打分
理论上给学生批改作业没那么容易,特别我们现在需要学生做的任务越来越难。但现在大家都在卷数学,物理,代码,这些方向根据结果+规则判断对错是可行的,也就是rule-based。
以及,大家发现训练一个小的reward 模型(例如7B)好像也能用。
导致学生找到答案后,判断答案的成本并不高。所以,这块不怎么被重视,甚至有人直接合并到rollout里了。
但随着agent出来后,特别开始设计到商业领域,例如我在做的电商领域,就没那么简单了。
c) 学生根据打分来学习
训练模块,基于传统的训练框架,改改loss function就行。
所以,现在的RL训练框架,整体分成两块,训练和rollout。那么问题就来了,假如你来设计这个rl框架,你会碰到这些挑战。
挑战一,rollout和训练两个模块的管理
RL现在的共识是on policy效果好,rollout和训练必须顺序执行。
但现在模型越来越大,多卡是必然的,如何管理资源?
rollout是内存消耗型,因为要做kv cache,特别现在cot越来越长了,训练则是计算密集型。
这俩现在都分别做了非常做的复杂的性能优化,如何对这两者做好资源管理?两者的参数同步如何优化?
挑战二,底层框架
训练框架有很多,megatron,fsdp,deepspeed,差异还不小。
推理引擎vllm和sglang,你用那个?
不同的训练框架搭配不同的推理引擎,仅仅是参数更新这块,逻辑代码都不一样。
挑战三,异步问题
rollout批次执行的,但同批次差异巨大,特别agent时代这个差异更是大的离谱。最近在做技术选型,带着团队的小朋友 @和光同尘 一起花时间把roll/slime/verl/openrlhf的源码框架都过了一轮。也跟它们的作者,roll的 @王小惟 Weixun,slime的 @朱小霖,verl的生广明,openrlhf的 @OpenLLMAI,进行了一轮/多轮的探讨,学习了很多知识,也收到了非常多有价值的反馈。
也感谢 @猛猿 ,有几轮讨论给了非常好的反馈。一轮轮的讨论中,会发现大家对三个挑战的解决,是一个循序渐进的过程。也感受到不同的作者会有自己的坚持和侧重。重点感谢这篇文章,对我帮助非常大
从零开始的verl框架解析: https://zhuanlan.zhihu.com/p/30876678559
性能的挑战
原始版本
我们回顾下三个阶段。学生自己寻找答案,在agent之前,就是模型推理。给学生答案打分,也是模型推理,只不过模型多几个(不同的算法量级不一样)。学生根据答案来学习,则是训练。
这个逻辑,基于sft框架是能搞的,只不过之前只用初始化一个模型,现在要初始化多个罢了。很多rl框架一开始都是这样。
但很快,我们会发现速度非常非常慢,那么问题来了,我们要如何优化它?
内存的优化
LLM训练-从显存占用分析到DeepSpeed ZeRO 三阶段解读:https://zhuanlan.zhihu.com/p/694880795
参考上面的文章。
训练过程中占用的显存有四种,模型训练参数,梯度占用,优化器占用,激活值占用。
梯度参数量和模型参数的规模是一一对应的,模型每一个参数都会有一个对应的梯度值来指导更新。
优化器占用则跟优化器的类型有关,这里按照adamw来计算。
拿7B模型来算内存,不考虑训练过程的中的激活值,模型参数14G,梯度14G,如果fp32,那就是28G,优化器28+28+28,一共都有112G。
现在的模型参数量,动不动千亿参数,单卡肯定放不下。为了解决这个方案,大家提出了DP/TP/PP来优化。
DP方式就是以deepspeed的zero1/2/3为主,每次计算前都会all gather得到完整的模型参数,计算的时候使用的是完整的参数和切分后的输入。
TP/PP从头到尾都只存模型的一部份,计算的时候使用的切分后的参数和完整的输入。这就是megatron擅长的,并且它的dp也做的很好。
详细区分参考如下表
英伟达有一篇论文,千卡以内差不多,千卡以外,TP/PP更好。
因为all gather的存在,理论上dp就是上限要比tp/pp差。
推理的速度
可以参考我的vllm,sglang,kv cache源码系列。现在的推理引擎,仅仅逻辑层kv cache的复用,就有非常多的点优化,更不用说底层算子的优化。
从零开始设计SGLang的KV Cache:https://zhuanlan.zhihu.com/p/31160183506
但使用推理引擎带来的问题就是,学生学习完毕后,需要把新的参数更新过去。但参数的更新耗时,对比于推理引擎提升的推理速度来看,百倍的提升,是完全可以接受的。
不过,现在推理引擎跟训练框架的generate的精度还是有差距,并且根据vllm和sglang的反馈来看,短期内依然比较难解决。
所以,现在rollout拿回来后,训练引擎还会再算一遍logits(相当于只有prefill,速度快的多)我们把训练框架和推理引擎拼接起来,做个新的rl框架,性能就有巨大的提升。
那么新的问题来了,如何拼接?
训练框架和推理引擎的拼接
SPMD和MPMD
SPMD(Single Program, Multiple Data),MPMD(Multiple Programs, Multiple Data),或者可以称之为single controller vs multi controller。
single controller,worker可以执行不同的程序(MPMD),需要一个老板来控制,不然就乱掉了。
multi controller,每个worker都执行相同的程序(SPMD),好处是不需要老板。但没有老板就代表各个worker在各种情况下,需要仔细考虑各种corner case,不然会乱跑。
DeepSpeed和Megatron等主流的训练框架都属于SPMD,所有进程执行相同的代码逻辑。
推理引擎(SGlang和vLLM)会很不一样。当他们执行计算的时候,是SPMD的,因为怎么计算是固定的。但next token从哪里来,如何计算,是kv cache,是从requset,还是pd分离,这就不适合用SPMD和MPMD做区分了。
广明建议看看google的pathway,再来讨论。(我理解的也很浅,就不展开了。我们抛开SPMD/MPMD,single controller/multi controller。训练框架和推理引擎之间最核心的是训练数据和模型参数的通信。我们带着疑问去看代码,看slime和roll是如何做的。
slime的做法
slime只定义了两个worker,RayTrainGroup 和RolloutGroup,训练框架和推理引擎。
https://github.com/THUDM/slime
数据的传输
slime单独定义了一个buffer类,专门用来做推理引擎和训练模块的数据传输中间件。数据都放到buffer里(甚至可以写入到磁盘),通过rollout id来指定。
并且buffer类的数据处理函数,rollout/eval function都是通过命令行参数指定,带来了极大的自由度。
self.generate_rollout = load_function(self.args.rollout_function_path)
self.eval_generate_rollout = load_function(self.args.eval_function_path)
这才是业务方真正的痛点,大家日常做业务,会有各种各样奇怪的需求和数据,这个地方如何改的爽很重要。rollout的generate函数是通过buffer。
def async_generate(self, rollout_id, evaluatinotallow=False):
return self.data_buffer.generate.remote(rollout_id, evaluatinotallow=evaluation)
训练框架获取数据,也是通过buffer。
def get_rollout_data(self, rollout_id):# Fetch data through ray on CPU, not sure if this will be performance bottleneck.# Both first pp stage and the last pp stage will recieve the data.megatron_utils.process_rollout_data(rollout_id, self.args, self.data_buffer)def process_rollout_data(rollout_id, args, data_buffer):rank = dist.get_rank()dp_rank = mpu.get_data_parallel_rank(with_context_parallel=False)dp_size = mpu.get_data_parallel_world_size(with_context_parallel=False)if rank == 0:data = ray.get(data_buffer.get_data.remote(rollout_id))dist.broadcast_object_list([data], src=0)else:data = [None]dist.broadcast_object_list(data, src=0)data = data[0]
使用的是同一个buffer,同步代码如下,把rollout的buffer同步给actor。
def async_init_weight_update_connections(self, rollout):"""Connect rollout engines and actors, e.g. initialize the process group between themto update weights after each training stage."""self.rollout = rolloutray.get([actor.set_data_buffer.remote(rollout.data_buffer) for actor in self._actor_handlers])
模型的传输
把actor的配置传给rollout,让rollout引擎在需要的时候正确的同步参数。
def async_init_weight_update_connections(self, rollout):"""Connect rollout engines and actors, e.g. initialize the process group between themto update weights after each training stage."""self.rollout = rolloutray.get([actor.set_data_buffer.remote(rollout.data_buffer) for actor in self._actor_handlers])actor_parallel_configs = ray.get([actor.get_parallel_config.remote() for actor in self._actor_handlers])parallel_config = {}for rank, config in enumerate(actor_parallel_configs):assert config["rank"] == rank and config["world_size"] == len(self._actor_handlers)config.pop("rank")for key, value in config.items():if"size"in key and key:if key not in parallel_config:parallel_config[key] = valueelse:assert (parallel_config[key] == value), f"mismatch {key} on rank {rank}: {parallel_config[key]} != {value}"parallel_config["actors"] = actor_parallel_configsray.get(rollout.async_set_parallel_config(parallel_config))return [actor.connect_rollout_engines.remote(rollout.rollout_engines,rollout.rollout_engine_lock,)for actor in self._actor_handlers]
看到这里,我们发现好像纠结什么single controller,multi controller并不重要。这些逻辑直接硬编码,好像也能支持?问题在哪?
暂且不急,我们再看看roll。
roll的做法
roll通过cluster的方式,定义了多个角色。
self.actor_train: Any = Cluster(name=self.pipeline_config.actor_train.name,worker_cls=self.pipeline_config.actor_train.worker_cls,resource_manager=self.resource_manager,worker_cnotallow=self.pipeline_config.actor_train,)self.actor_infer: Any = Cluster(name=self.pipeline_config.actor_infer.name,worker_cls=self.pipeline_config.actor_infer.worker_cls,resource_manager=self.resource_manager,worker_cnotallow=self.pipeline_config.actor_infer,)self.reference: Any = Cluster(name=self.pipeline_config.reference.name,worker_cls=self.pipeline_config.reference.worker_cls,resource_manager=self.resource_manager,worker_cnotallow=self.pipeline_config.reference,)
if self.pipeline_config.adv_estimator == "gae":self.critic: Any = Cluster(name=self.pipeline_config.critic.name,worker_cls=self.pipeline_config.critic.worker_cls,resource_manager=self.resource_manager,worker_cnotallow=self.pipeline_config.critic,)
分的比slime更细,好处是跟算法侧的认知比较一致,都是用cluster封装好了,毕竟算法是不知道训练框架和推理引擎有这么大的区别。
数据的传输
类似megatron,可以按照domain分开来采样,在pipeline.py文件定义好。 如果懒得写data generator,roll这个就很省心了。
重点讨论一下reward,理想情况下,我们肯定是期望有一个统一的reward模型,但现在统一的reward比较难训,并且看起来会持续很长一段时间。
我们退而求其次,不同的domain用不同的reward来打分,代码/数学/物理/写作等各一个,最后做聚合。
roll细化到,不同domain,不同batch,不同query上都可以做自定义配置。
https://github.com/alibaba/ROLL/blob/9b85e63ad4c715aa6602ba2a43657e25217c7732/examples/qwen2.5-7B-rlvr_megatron/rlvr_config.yaml#L176-L252
模型的传输
def model_update(self, tgt_workers, broadcast_tgt_devices, p2p_tgt_devices):comm_plan = self.model_update_comm_plan[self.worker.rank_info.pp_rank]model = self.unwrap_model()broadcast_time_cost = 0with Timer("model_update_total") as timer_total:for param_name, param in tqdm(model.named_parameters(), desc="weight update progress", total=len(list(model.named_parameters()))):shape = param.shape if not self.ds_config.is_zero3() else param.ds_shapeif not self.ds_config.is_zero3():param_weight = param.datarefs = []for p2p_tgt_device in p2p_tgt_devices:p2p_tgt_worker = tgt_workers[p2p_tgt_device["rank"]]ref = p2p_tgt_worker.update_parameter.remote(parameter_name=param_name,weight=param_weight,ranks_in_worker=[p2p_tgt_device["device"]["rank"]],)refs.append(ref)if (self.worker.rank_info.tp_rank == 0and self.worker.rank_info.cp_rank == 0and self.worker.rank_info.dp_rank == 0):for worker in tgt_workers:ref = worker.broadcast_parameter.remote(src_pp_rank=self.worker.rank_info.pp_rank,dtype=param_weight.dtype,shape=shape,parameter_name=param_name,)refs.append(ref)if len(broadcast_tgt_devices) > 0:collective.broadcast(tensor=param_weight, src_rank=0, group_name=comm_plan["group_name"])ray.get(refs)
两种通信方式结合,会通过worker的node_rank和gpu_rank来判断是否在同一个设备上。
第一种,对于同一设备上的参数,直接通过点对点通信更新。
第二种,通过 collective communication 广播参数到目标集群,只在主进程(rank 0)执行广播操作。这两种就是colocate和非colocate的差异,下文会说到。
从roll来看,这些逻辑直接硬编码,也是能支持,特别当如果在同一个机器上的话。
从slime和roll来看,参数同步,其实只是controller告诉GPU要做参数同步了,中间的通信是不走controller的。
而如果是rolleout的通信,这个single controller压力是非常小的,多模态会大一些?不确定。从这个角度来看,SPMD和MPMD不重要。
但我们刚刚的讨论,都是如果这些role在同一个机器上,硬编码会很简单?
那么如果不在同一个机器会怎么样?
colocate和ray
把actor/ref/reward/critic模型都放一张卡上就是colocate。 但正如上文所说,7B模型训练单卡都塞不下,1000B的模型下半年预计也会开源好几个,并行带来的开销是很夸张的。 现在reward模型也普遍不大,7-30B就满足要求,模型size也有差异,分开部署性价比更高。
但分开部署的硬编码就相对麻烦了,于是大家引入了ray。 ray能支持更加复杂的分布式计算的框架,不用我们过多的操心底层逻辑。如下两篇文章写的已经非常好了,这里不做赘述。 图解OpenRLHF中基于Ray的分布式训练流程:https://zhuanlan.zhihu.com/p/12871616401 Ray分布式计算框架详解:https://zhuanlan.zhihu.com/p/460600694
我们对比下slime/verl/roll/openrlhf四个框架的colocate和非colocate实现。
slime
只有俩worker,训练和推理,RayTrainGroup 和RolloutGroup
colocate是训练和推理分开部署,那么非colocate的情况下,只需要处理分布式通信来同步参数。
这种拆分,抽象易于理解,跟上文讨论的训练和推理是一致的。
只需要在训练的时参数配置colocate,就会自动在所有重要环节执行。
roll
非colocate的话,roll支持各种细粒度的worker指定在不同的显卡的部署,并且还能按轮次去配置,如果不指定的话,ray也会自动帮你部署。
RL资源消耗这么大,细粒度的显卡配置,更有助于显卡资源的高效利用,但对算法侧的资源调度能力要求也更高了。
很明显,这个逻辑用ray来管理更合适。
verl
ActorRolloutRefWorker这个类设计的有点问题,已经在改了。
非colocate就是每个worker(actor,crtic,reward等)一个进程,靠ray自行调度。
colocate,多个角色共用一个ray actor实例,同一个进程内实例化多个worker类。
通过 create_colocated_worker_cls 或 create_colocated_worker_cls_fused 动态生成一个“多角色”类(如 WorkerDict/FusedWorker),内部持有多个 Worker 实例。
外部通过统一的接口调度不同角色的 Worker 方法,内部自动分发到对应的 Worker 实例。
# deprecated, switching to FusedWorker
def create_colocated_worker_cls(class_dict: dict[str, RayClassWithInitArgs]):"""This function should return a class instance that delegates the calls to everycls in cls_dict"""cls_dict = {}init_args_dict = {}worker_cls = _determine_fsdp_megatron_base_class([cls.cls.__ray_actor_class__.__mro__ for cls in class_dict.values()])assert issubclass(worker_cls, Worker), f"worker_cls {worker_cls} should be a subclass of Worker"print(f"colocated worker base class {worker_cls}")for key, cls in class_dict.items():cls_dict[key] = cls.clsinit_args_dict[key] = {"args": cls.args, "kwargs": cls.kwargs}assert cls_dict.keys() == init_args_dict.keys()# TODO: create a class with customizable nameclass WorkerDict(worker_cls):def __init__(self):super().__init__()self.worker_dict = {}for key, user_defined_cls in cls_dict.items():user_defined_cls = _unwrap_ray_remote(user_defined_cls)# directly instantiate the class without remote# in worker class, e.g. <verl.single_controller.base.worker.Worker># when DISABLE_WORKER_INIT == 1 it will return immediatelywith patch.dict(os.environ, {"DISABLE_WORKER_INIT": "1"}):self.worker_dict[key] = user_defined_cls(*init_args_dict[key].get("args", ()), **init_args_dict[key].get("kwargs", {}))
跟别的框架不一样的点,同进程和非同进程。根据verl的反馈,同进程收益是非常大的。
同进程通信的话,在某些场景下,速度会差十几倍,memory的碎片问题之类的也会造成很大的影响。
openrlhf
Openrlhf支持多种混合部署方式,允许共置 vLLM 引擎、Actor、Reference、Reward 和 Critic 模型节点或是部分混合部署。支持任意方式的混合部署 也支持分开部署异步训练。
汇总下来,非colocate情况下,ray的确能帮我们更省心的做资源管理,更不用说复杂的agent和多轮。 不过根据运维侧的反馈,ray设计跟现在prod k8s云原生体系比较违背,落地到prod,管理成本比较高。 但ray反馈他们也在针对性优化了,例如,ray现在tensor可以直接走nccl了 bypass object store,也可以期待下他们后续的更新。
不同的训练框架和推理引擎的连接也不同
举个例子,假如vLLM的TP是4,而deepspeed分在了8个GPU,那么就需要做参数的转换才能传输,megatron也类似。
如果训练框架有俩,推理引擎有俩,那么要适配的不是两种,而是四种方案。
某位不愿意透露姓名的朋友,曾被这样的bug坑过,导致他们一直以为百张卡内megatron没有fsdp快,并且持续了很长一段时间。
代码的解耦
拿slime来举例子。 推理引擎分成三层了,顶层-RolloutGroup,中层-RolloutRayActor,底层-SglangEngine。
顶层的 RolloutGroup 是一个总控器,负责启动推理引擎,分配资源,管理数据。在 RolloutRayActor 中可以看到各个接口都很精简,因为具体的实现都放在了 backends 中的相关 utils 里。因为推理本身并不复杂,所以在 sglang_engine 中各个功能也通过调用接口实现。假如有一天我发现sglang不行了,那么我改底层就可以了,上层是感受不到的。 训练框架也是分了三层,看 TrainRayActor 可以更直观的感受到解耦程度。对于最复杂的 train 方法,涉及到 ref,actor,rollout,包含了logprob的计算,日志的记录等,但是因为各种功能被写在了 megatron_utils 中,所以并不会显得很复杂。而在 megatron_utils 中,就可以看到 forward,train 等的详细实现。当需要替换 backend 的时候,在 backend 中新增一个 utils 并确保 train,forward 等接口的实现就可以实现 backend 的切换。 现在各大框架基本上都是这么设计了。 还有一些模块,loss function,打分模块,相对比较简单,就不展开了。
关于Agentic RL
roll,verl,openrlhf都有了不错的支持。 roll甚至单独定义了agentic_pipeline.py来方便大家做进一步的自定义,虽然这样会导致代码的逻辑复杂度增加。但weixun反馈,后续等agentic RL稳定下来后,会重新做一版梳理。weixun16年就开始做RL,经验非常丰富,大家可以多去反馈。 个人认为,未来应该只有agentic RL,现有的rlvr只是其中的一个组成模块。
但这块我还不打算展开,特别现在的agent框架问题更大。
自主agent+沙盒+workflow的混合,agent+rl如何有效的扩大自主agent的范围和边界,等一系列问题,现在并没有一个开源框架能做有效支撑。 太多的不确定,我的下一篇文章会重点展开讨论。
用哪个RL框架?
框架的难点
如果技术进化速度很快,老的框架往往很快就会被大家遗忘,这的确比较令人尴尬。 agent方向也是如此,langchain2023年有多火,现在呢?用dify的更多吧。 原因很简单,老的框架历史包袱太重。 一边清理老功能,一边加新功能,同时如何保证代码框架的简洁和高维护性,是非常考验框架负责人的。
拿dify举例,它为了把rag,chat,workflow,自主agent都塞到一起,代码的臃肿程度看的非常痛苦。 很多老用户可能的确还在用rag功能。但讲道理,dify里面的rag都是开源的模型和方案。干脆丢掉,让用户调用优质的rag接口/mcp,毕竟rag只是agent未来无数tools中的一个重要模块。
新框架设计者,没有这些包袱,弯道超车就会很容易。 再提一嘴,SFT时代很火的训练框架,现在大家还有谁在用?特别现在RL框架顺路就可以把SFT给做了。
而随着模型不断变大,现在其实就俩选择,彻底自己写一套框架,或者基于megatron做定义。 前者对团队要求高,后者则是要忍megatron。
用哪个?
现在的RL框架,各有各的优点,大家按需自取即可。
a] OpenRLHF
OpenRLHF 是第一个基于 Ray、vLLM、ZeRO-3 和 HuggingFace Transformers 构建的易于使用、高性能的开源 RLHF 框架,旨在使 RLHF 训练变得简单易行。
很多人发论文都用它。
https://github.com/OpenRLHF/OpenRLHF
b] slime
刚出没有历史包袱,轻装上阵,代码是真的简洁且清晰,有我当年看完vLLM再去看SGlang的快感。
zilin本身算法认知也很到位,经常跟我们探讨算法细节,对算法侧的痛点是很懂的。例如buffer对数据的处理,很明显就是理解算法侧天天洗数据的痛。
大规模训练上,在zhipu也经过了验证。
大家想要去做一些大胆的框架魔改尝试,slime非常值得一试。
https://github.com/THUDM/slime
c] ROLL
数据处理做的蛮精细,分domain采样,训练和评估的异步都做好了。
agentic和异步是支持的比较完善,weixun从16年就开始做RL,经验非常丰富,ROLL也在这个方向投入了很大的精力去优化。
agentic一直在更新,下周估计还会放新feature。
各种角色的显卡配置指定也可以实现非常细致的调配,支持算法做精细化的算法性能调优。agentic RL这块如果你想要深入折腾的话,可以试试。
https://github.com/alibaba/ROLL
d] verl
据我所知,很多大厂都在用这个,在大规模服务上,它跑起来就是稳且靠谱。
以及,在大规模集群的显卡性能优化上,是有过非常多的踩坑经验。
agentic和异步也做了很多的优化和探索,对deepseek v3的支持也很好。
如果你的团队卡多,时间排期紧张,想要快速scale起来,用这个就好了,很多大厂团队已经帮你验证过。
https://github.com/volcengine/verl
至于aReal的异步,我自己都没想清楚,估计年底会再写一些篇做个专门探讨。
写在最后
最近半年,把RL训练框架,agent框架,推理引擎框架都过了一轮。 代码量,agent > 推理引擎> rl训练框架。 代码难度,推理引擎>rl训练框架>agent。 但如果把推理引擎的底层算子抛开,RL训练的框架难度是跟推理引擎持平的。因为RL训练框架的难点在于缝合,很考验框架作者对各种系统,业务的理解。
verl/slime/roll/openRLHF,这几个开源框架各有所长,能看到不少作者都有自己的追求和坚持,社区的积极性又很高。我觉得大家可以很自豪的说,我们中国在开源的RL框架方向,技术和认知,就是世界第一。算法上人才差异也没那么大,差的大概就是显卡。
大家还是要有一些家国情怀和自信,而不是单纯copy硅谷to china。不是说,谁离openai/claude近一些,更早的获取一些模型训练上的小道信息,谁就迭代的更快。 接下来谁能基于国产卡,训练出来第一个真好用的1T模型。谁就能站在主席台上领奖了,China AI number one 也就不远了。 2023年我清了腾讯股票,梭哈了英伟达和微软。我还是蛮期待明年能有这么一个大新闻,可以让英伟达的股价大大的波动一下,给世界展示下中国制造的实力。
#OpenAI被曝IMO金牌「造假」
陶哲轩怒揭内幕!
OpenAI高调摘下数学金牌,竟是自嗨!组委会内部人士透露,OpenAI不仅未与IMO官方合作,甚至无视赛事规则,在闭幕派对未结束前抢先官宣。全网怒批其不尊重人类选手,炒作过头。
OpenAI夺下IMO金牌,最新大瓜又来了。
昨日,因内部审核流程,谷歌DeepMind研究员在评论区,暗讽OpenAI抢先发布测试结果。
原来,事情并非那么简单。
一位IMO内部人士透露,实际上OpenAI并没有和组委会合作,拿下AI金牌不一定真实有效。
最关键的是,他们违背了IMO规定的「公布时间」规则。
为了避免AI公司们抢夺人类学生的风头,IMO评审团要求:在闭幕式结束一周后再公布结果。
然而,OpenAI却在闭幕Party还未结束前,就发布了结果。
对此,谷歌DeepMind负责人Thang Luong表态,「是的,IMO组委会有一份不对外公开的官方评分标准」。
若未依据该标准进行评估,任何奖牌声明均无效。
扣除1分后应为银牌,而非金牌。
这么说来,OpenAI声称拿下IMO金牌,只是自嗨?!
OpenAI真面目被戳穿
抢夺学生风头
就在昨天,菲尔兹奖得主陶哲轩在一口气连发三条评论,暗指的就是OpenAI。
他表示,「自己不会评论任何未预先公开测试方法的AI竞赛成绩报告。在缺乏受控测试环境的情况下,AI的数学能力难以准确评估」。
另外,IMO组委会一位成员Joseph Myers透露,OpenAI并非是IMO合作测试模型的AI公司之一。
而且,阳光海岸的91位协调员(Coordinator)也无人参与结果评估。
P6题协调员表示,「IMO评审团和协调员一致认为,OpenAI此举显得失礼且不妥当」。
根据IMO规定,借助AI模型参赛的公司,需要在7月28日之后公布结果。
一家专注于数学AI初创公司Harmonic官方发文,从侧面印证了这一规定存在的准确性。
最新回应引热议
OpenAI研究科学家,德扑之父Noam Brown下场回应,给出了两点证明:
首先,团队是在闭幕式「之后」公开结果。闭幕式有直播记录,这一点很容易核实。
其次,他确认了OpenAI并未与IMO进行协调,只是在发帖前与一位组织者告知了此事。出于对参赛学生的尊重,要求OpenAI等到闭幕式结束后再发布——「我们也照做了」。
对此,有人还精细计算了闭幕式和公开结果的时间差。
IMO闭幕式的时间在7月19日(当地时间)下午4点举行闭幕式,直播时间1小时43分钟,结束时间不晚于5点43分。
再来看负责人Alexander Wei的发文时间,7月19日下午3:50(东八区),也就是当地时间的5点50分。
从时间来看,确实是OpenAI在IMO闭幕式结束7分钟后,才发布了公告。
即便如此,网友们仍旧看不惯OpenAI炒作风暴,而且根本没有给获奖学生留有余地。
而且,可以确定的是,OpenAI公布的结果,并没有得到IMO官方认证。
未来几天,谷歌DeepMind会正式发布AI夺下IMO 2025细节。
马库斯愤怒抨击,太符合品牌调性了。
UCLA数学教授
LLM短期内不会取代人类
针对LLM拿下IMO金牌事件,来自UCLA应用数学教授Ernest Ryu发表了自己的看法。
1. OpenAI IMO P1-P5的解答目测是正确的。
2. 第6题是一个明显新颖且难度更高的问题。可以说第1-5题仍在「标准」IMO解题技巧范围内,但第6题需要创造性思维。
他表示,根据自己使用LLM进行数学研究的经验,Gemini的表现优于ChatGPT。
但OpenAI抢先在周六宣布了结果,而谷歌DeepMind「慢科研」学术作风,让他们输掉了这场公关战。
不过,Ernest Ryu认为,在短期内,大模型不会取代数学家。
因为数学研究是,解决那些目前「没有人」知道如何解决的问题(训练数据分布之外),即类似IMO P6题。这需要极大的创造力,OpenAI的模型在IMO解题中恰恰缺乏这种能力。
然而,对于那些人类已有能力解决的问题(训练数据分布之内),LLM只会变得愈加强大。
在数学研究中,人们会将现有技术与新创意相结合,LLM将显著加速前一部分工作的实现。
Ernest Ryu还预测,在接下来十年里,越来越多的数学家将借助LLM来搜索证明框架中的已知部分,从而提升研究效率。
老一辈数学家或许会对此唏嘘不已,但年轻一代只会继续产出优秀成果。
参考资料:
https://x.com/ns123abc/status/1947016206768046452 https://x.com/lmthang/status/1946960256439058844 https://x.com/Mihonarium/status/1947027989608190065
#ATPrompt
属性引导的VLM提示学习新方法
ATPrompt提出利用通用属性词元引导软词元学习属性相关的通用表征,提升模型对未知类的泛化能力,重构了自CoOp以来的提示学习模板形式。
- 论文题目:Advancing Textual Prompt Learning with Anchored Attributes
- Arxiv链接:https://arxiv.org/abs/2412.09442
- 项目主页:https://zhengli97.github.io/ATPrompt
- 开源代码:https://github.com/zhengli97/ATPrompt
- 中文版:https://github.com/zhengli97/ATPrompt/docs/ATPrompt_chinese_version.pdf
关键词:提示学习,多模态学习,视觉语言模型CLIP
一些有关的文档和材料
如果你觉得这个领域还有那么点点意思,想进一步了解:
- 我们组在github上维护了一个细致的paper list供大家参考:https://github.com/zhengli97/Awesome-Prompt-Adapter-Learning-for-VLMs。
- 我们组在CVPR 24上发表了一篇有关提示学习的工作PromptKD,
- 论文链接:https://arxiv.org/abs/2403.02781
- 开源代码:https://github.com/zhengli97/PromptKD
- 论文解读:https://zhuanlan.zhihu.com/p/684269963
大白话背景介绍
已经很了解VLMs和Prompt Learning的同学可以直接跳过,到背景问题,这里的介绍是为了让没有相关基础和背景的同学也可以看懂这篇工作。
什么是视觉语言模型(Vision-Language Models, VLMs)?
(为避免歧义,这里VLMs一般指类似CLIP这种,不是llava那种LLM-based Large VLMs)
视觉-语言模型,顾名思义,一般由两个部分构成,即视觉(Vision)部分和语言(Language)部分。在提示学习领域,一般采用CLIP[1]这种双塔视觉语言模型,其结构为:
图1. CLIP结构图。
其中,Image Branch由图像类编码器构成,如ResNet或者ViT之类架构,输入的图像经由image encoder进行特征提取,得到最终的图像特征,其大小为[batch_size, feat_dim]
。
Text Branch由文本类编码器构成,一般为transformer,当要进行n个类别的分类任务时,会取每个类别对应的名称,如"plane", "car", "dog",代入"a photo of a {class_name}"的模板里,作为prompt输入进text encoder,得到大小为[n, feat_dim]
的文本特征。
将两个特征相乘,就得到了最终的输出logits。
什么是提示学习(Prompt Learning)?
在文本分支中,我们一般采用的a photo of a {class_name}作为编码器的输入,但是这样的文本模板太过宽泛,对于特定下游任务明显不是最优的。例如对于图2(b)的花,其采用的a flower photo a {class}的模板更加精确,产生识别结果更好。
也就是,在设计文本提示的时候越贴近数据集的类别就会有越好的结果。
图2. 蓝色方块代表手动设计的prompt,绿色方块代表网络学习得到的learnable prompt。绿色方块acc超越了蓝色。
对于这样的文本模板形式,存在两个问题:
(1) 传统的固定的文本提示往往不是最优,(2) 针对性设计的文本模板费时费力,且不同数据集之间无法泛化通用。
于是,提示学习(Prompt Learning)就出现了,让模型自己学出适合的文本提示,CoOp[2]首先提出了将多个可学习词元(learnable soft token)与类别词元(class token)级联的形式,即,作为VLM文本编码器的输入。通过训练的方式,使得soft token能够学到合适表征,替代手工设计的prompt,取得更好的性能,如图2中的绿色方块。
实验评价指标是什么?
有三个指标,分别是base class acc(基类别准确率),novel class acc(新类别准确率)和harmonic mean(调和均值)。
以ImageNet-1k数据集为例,取1000类中的前500类作为base class,后500类作为novel class。模型在base class上训练,训完后在base class和novel class上测试性能。因为训练过程中使用的base class数据与测试用的novel class数据类别不重复,所以novel acc可以有效反应模型泛化性能。
Harmonic Mean(调和均值)指标是对base acc和novel acc的综合反映,为harmonic mean = (2base accnovel acc) / (base acc+novel acc)。总体的Harmonic Mean值越高,模型综合性能越好。实验结果中一般以这个指标为准。
背景问题
相关工作
在经过CoOp方法之后,许多研究者提出了各种提示学习的新方法,
图3. 相关工作总览。
比如在CoCoOp[3]提出在image encoder之后引入额外的meta-net,增强可学习提示的拟合能力。
MaPLe[4]进一步将提示学习方法从单个的文本模态拓展到了文本和图像两个模态上,通过额外映射层(Project Layer)将文本软词元映射成图像软词元,嵌入图像编码器进行训练。
RPO[5]提出将每个可学习词元都看成单独的CLS token,将多个词元的输出结果进行集成,得到的最终结果作为模型的输出。
PromptSRC[6]在MaPLe基础上去掉了映射层的操作,给每个模型引入单独的可学习词元进行训练。
图4. 基于正则化的相关工作。
在这些方法里,最典型的一类就是基于正则化的方法。做法很简单,就是利用原始CLIP产生的各种表征,来正则化带有可学习提示词元的模型的训练。该类方法占据了主流。
其中,KgCoOp[7]使用原始的模板产生的文本特征去约束带有可学习提示产生的文本特征。
ProGrad[8]用原始的预测结果蒸馏带有可学习提示产生的预测结果。
PromptSRC[6]在两个模态上同时采用正则化约束,在文本特征,图像特征和输出logits上都用原始CLIP产生的结果去进行约束。
PromptKD[9]更进一步把推正则化方式推到了极致,采用大的教师模型提供了更好的对齐目标,同时借助教师模型在大量无标签数据上提供软标签去进行训练,提升了软词元的信息丰富度,达到了已有方法中的SOTA。
问题
让我们回过头思考,为什么我们会需要正则化的方法,为什么大多数做法都是跟原始CLIP去做对齐?
理论上来说,一个经过良好训练的learnable prompt产生的文本特征应该是要优于原始CLIP的文本特征的,直接与原始特征去对齐,理论上应该是没有效果的。
其实,核心问题就在于,现有的文本提示学习形式: ,它是以类别词元为中心的,这种设计形式是存在缺点的:
已有的提示形式,软词元在训练过程中只能学习到与已知类别[class]相关的表征,无法学习到类别以外的通用表征,没有办法建立与未知类别之间的联系,形式上存在缺陷。
这样就使得在遇到未知类别样本时,从已知类别的训练数据中学习到的表征不能发挥作用,无法良好的泛化。所以要去解决核心的问题,仅仅靠不断的加正则化的方式总是治标不治本的。
我们需要重构现有的提示学习形式,那么要用什么样的方式才可以增加图像与未知类别文本间的关联?
方法
来自实际生活的启发
图6. 额外的属性信息会帮助从另外的角度促进对未知类别的识别。
这时候,让我们回归现实生活,当我们人类遇到未知类别的东西时,我们会怎么办?我们通常会从属性的角度来进行表述,增加这个东西的清晰度和可理解性。
举个例,如图6所示,当我们教一个小朋友去在这四张图里选择出什么是cheetah时,小朋友因为知识有限,可能不知道对应这个词是什么,因此很难选出对应的图,但是当我们给他提供额外的属性信息时,比如,The cheetah is a cat-like animal with a small head, short yellow hair and black spots。
其中,cat-like, small head, yellow hair, black spots,这些关键属性的加入让小朋友能够根据这些特点一一匹配,从而准确识别出来c就是cheetah。从属性方面出发的额外描述能够帮助人从另外的角度/维度促进人类对未知类别的识别和理解。
具体细节
受此启发,我们提出了ATPrompt,道理非常简单,就是让软提示去额外学习与属性相关的表征。
图7. 传统提示学习方法与我们的ATPrompt的对比。
形式如下图所示,(a) 是经典的提示学习方法,其采用的是软词元和类别词元级联的方式。现在所有的方法都沿用的范式。
(b) 受到现实生活的启发,我们想要去用属性来改进已有的提示形式。特别地,我们的方法重构了自从CoOp以来提出的提示学习范式,提出在软词元中嵌入固定的属性词元,将其作为整体输入进文本编码器中,如图7(b)所示。
(因为属性词元是保持位置固定和内容固定的,所以也称为锚点词元(Anchor Token)。)
这样一来,通过将固定的属性词元嵌入到软提示中,软词元在学习的过程中就能够受到属性词元的引导,学习与属性相关的通用表征,而不只是以类别为中心的特征,从而增强其泛化性能。
从形式上来说就是,我们将已有的提示学习形式
改变成了
以嵌入两个属性A和B为例,a1, ..., am代表的是对应 A属性的软词元数量,b1, ..., bm同理,M代表的是对应类别词元的软词元数量。
带有深度的ATPrompt
除了在输入层面上加入可学习词元,我们还进一步将ATPrompt拓展到了深度层面,兼容更多的带有深度的提示学习方法,例如MaPLe,PromptSRC等等:
图8. ATPrompt的浅层与深层版本架构图。
直观解释
在接下来的实验里,我们的ATPrompt取得了非常好的结果。我们来思考,它为什么能够work?
图9. 两种方法对齐过程的对比。
从本质上说,就是已有的由CoOp以来大家都沿用的提示形式,是以类别为中心的,可学习软词元在学习的过程中只能对已知类别拟合,无法与未知类别建立直接或者间接的联系,也无法学习到通用的表征。
而我们的方法,通过引入属性作为中间桥梁,软词元在学习的过程中不仅要去学类别有关的表征,还要去学习与通用锚点属性相关的表征。
这样在遇到下游未知类别的时候,已有的通用属性表征能够提供更多的信息或者角度,促进对于图像和文本类别的匹配。
如何确定属性?
对于各种数据集,我们不可能人为的去一个个筛选。当类别数量太庞大时,人不可能正确的归类所有的选项。
于是我们就提出了,可微分的属性搜索方法(Differentiable Attribute Search),参考NAS[10],我们让网络以学习的方式去搜出来当前最适合的属性内容以及数量。
图10. 可微分属性搜索方法总览图。
整个过程分为两步,先看(a),
第一,通过多轮对话,让LLM自己总结出指定数量的通用属性。这里我们将数量设定成5,(是因为发现大于5之后,比如8,增加的属性在语义上会有重复,意义不大,数量过小的话给search留下的空间又会不够。所以直接设置成5。)
第二,对得到的独立属性基进行组合,形成属性池。用作接下来的搜索方法的搜索空间。比如我们分别得到了shape, color, material, function, size五个属性基,我们对其进行组合,会产生31种组合结果,在数学上的计算也就是。
分别为(shape), (color), (material), (function), (size), (shape, color), ..., (function, size), ..., (shape, color, material), ..., (shape, color, function, size), ..., (shape, color, material, function, size)。
这里31种组合,就对应在(b)中会产生31个输入路径,就是(b)中绿线,红线,淡蓝线所代表的。
再看(b),
对于属性池中各种属性组,我们将目标属性的搜索简化为对每条路径的权重的优化,也就是优化(b)中那个weight vector。权重中confidence的值越大,就代表网络更加倾向于使用这一组属性,也就是这组属性更适合当前的任务。
我们采用交替优化方法来训练,也就是,在一轮的训练里,一边更新每个路径里的软词元的权重,一边更新每条路径的属性权重。通过40轮的优化,选择属性权重α最大的路径对应的属性作为最终的结果。
以在Caltech101的数据集结果为例:
表1. 在Caltech101上进行属性搜索的输出结果。
对于5个属性基,会产生31种属性组合,在经过40轮的搜索后,(shape, size)这个属性组合产生了最高的权重(置信度),我们就将其选择出来,作为目标属性应用到ATPrompt里面用于训练。
实验结果
看了这么多分析之后,下面直接来说结果吧:(不想看可以直接跳到后面的 理想与愿景,常见问题解答)
Base-to-Novel泛化实验:
表2. 在11个数据集上的base-to-novel实验。
Cross-dataset实验:
表3. Cross-dataset实验结果。
从以上的结果来看,在使用ATPrompt替换了自从CoOp提出的基础形式之后,将ATPrompt集成到已有的基线方法上都获得了一致的提升。
消融及验证性实验
1. 如果我们不用搜出来的结果,就随便选一些很通用的结果怎么样?或者故意选择一些跟数据集丝毫没关系的?
我们在11个数据集上进行了实验,
表4. 选择通用的属性和与数据集不相关的属性的结果。
其中,类型为common,代表的是选择的是通用类别,类型为irrelevant,代表的是选择与实物没有明显关联的类别属性。
从表4可以看出,1. 搜索的结果取得了最高的性能,2. 与数据集无关的属性确实是会影响到soft token的学习。但是他们的掉点依然不多,这从侧面反映了一个有意思的现象是,soft token拥有某种纠错的能力,尽管选择了与数据集毫无关系的属性,但是soft token通过学习依然能够把错误的属性表征给拉回来。
2. 属性的顺序有没有影响?
其实在图7和表1里,我们是没有考虑属性顺序的影响的。这源自生活里的一个经验,就是在人类表述中,属性描述顺序的改变并不会显著地影响语义的表达,比如,“这条短腿,黄白色毛发相间的狗是柯基”和”这条黄白色毛发相间,短腿的狗是柯基“表达的是相同的意思。
但是光这么想不太行,还需要实验验证一下。
表5. 属性顺序的验证实验。
在表5中,我们对调了属性的顺序,可以看到,属性的变化只会带来略微的波动,一般只在0.1-0.2左右,在合理的波动范围内。这说明,属性的变化对于模型整体性能来说没有明显的影响。
3. ATPrompt-Deep版本的词元操作。
在图8中,我们提出了Deep Verision,也就是深层版本。
表6. 在Deep Verision中对词元各种操作的对比。
深层版本里,我们采用了在前向计算过程里保存属性相关软硬词元,仅drop和re-add类别词元前的软词元的操作。表6中是对各种操作的对比。
完整的对应属性基以及搜索结果列表
表7. 属性基和搜索结果列表。
Attribute Bases代表属性基,即由LLM询问得到的独立结果,在此基础上进行组合就会得到属性池。Searched Results代表搜索结果,即在属性池中进行搜索后得到的结果。
理想与愿景
如我们在前文说到的,大量的基于正则化的方法(KgCoOp, ProGrad, LASP, PromptSRC, CoPrompt, KDPL, CasPL, PromptKD)占据了性能排行榜的前面。尽管直接加一个与原始clip对齐的loss很有用,但是这样的方式仍是治标不治本的。(同理对于引入额外的数据(HPT, POMP, TextRefiner)的方法也一样)。换句话说就是,假如soft token能够学得很好,那跟原始模型的feature对齐就应该是完全没用和没意义的。
这样的问题就像是在教育小朋友的时候,我们不能一味的只教育不能去做什么,而是应该去教一些通用有效的知识,根据这些,小朋友能够触类旁通才是最好的。
基于正则化方式要去解决的问题其实来源就是prompt形式所上带来的问题。自从CoOp提出了prompt learning之后,大家全都在follow这一个固定的prompt: soft tokens+class token的范式,已有软词元(soft tokens)在训练过程中只能接触到class token,因此就会被限制在只能学习到与已知训练类别有关的信息(论文ArGue也提到了这点),随着不断的训练,会不停的对已知类别过拟合,训多了之后novel类的acc就会崩。所以基于正则化的方式就是在缓解或者减弱这样过拟合的趋势,同时缩短训练的epoch,大幅提升novel性能从而增加HM。
要根本上改进这个问题,就需要改进或者reformulate自coop提出以来的learnable prompt形式,而不是不停的加各种正则化项(这不elegant)。我们的目标是要让prompt中不能够只含有class token,而要包含一些其他有意义的token来促进识别。
所以在ATPrompt里,我们就首先提出通过引入一些额外的属性作为锚点嵌入在soft tokens+class token中,引导软词元在适配到下游任务的过程中,不仅拟合已知类别,还能够学到更多的与属性相关的通用信息。相比于原来的coop,训练时以无额外代价的方式增加模型对于未知类别的泛化能力。
展望
ATPrompt所做的事情只是对于高效learnable prompt结构设计的一小步,我们希望抛砖引玉,未来能够有更多的工作关注在设计高效结构上,使得能够不过度依赖正则化loss,通过良好的prompt结构,soft token通过训练就能够达到comparable的性能。
ATPrompt可以进一步展开做的地方
- 将属性词替换成特定任务的关键词,结合到某类特定的任务中去,比如open vocabulary detection, segmentation, action recognition等任务。
- ATPrompt在deep version上的性能是比较有限的,提升的效果比较少,目前的形式还不是特别work的,期望未来有工作能够进一步把做的更好。
- 基于ATPrompt的形式去设计一些新的prompt learning方法,取得更高的结果,或者能够动态结合起来attributes,超越原本的ATPrompt。
常见问题解答
Q1: 用提示学习微调VLMs有什么实际意义?
A1: VLM的模型如CLIP,拥有非常好的零样本泛化能力,可以作为基础模型部署在许多业务场景里。
但是CLIP的优势往往指的是对通用类别的识别上,在对于特定类别数据,例如猫狗和飞机类型的细粒度鉴别,或者罕见类别的识别,往往性能表现不行。
这时要提升模型在特定任务上的预测性能,一种直接的做法是全参数微调,但是这需要大量的image-text pair数据训练,费时费力,如果仅有少量训练数据,则可能会让CLIP overfit到下游数据上,破坏原先的泛化性能。
另一种可行的做法就是用提示学习,通过引入少量可学习参数去微调CLIP,因为参数量少,所以对于训练数据量需求低,又因为不涉及参数模块(例如fc层)的训练,在遇到未知类时能够保持CLIP原始的零样本泛化性能。
Q2: 学习到的属性是每个样本都有一个吗?
A2:不是的,是整个数据集只对应一个。在属性搜索阶段,整个数据集只对应一个weight vector,在训练完成后,会选择vector中score最高的对应的属性作为目标属性用于接下来的ATPrompt提示学习训练。
参考
[1] Learning Transferable Visual Models From Natural Language Supervision. ICML 21.
[2] Learning to Prompt for Vision-Language Models. IJCV 22.
[3] Conditional Prompt Learning for Vision-Language Models. CVPR 22.
[4] MaPLe: Multi-modal Prompt Learning. CVPR 23.
[5] Read-only Prompt Optimization for Vision-Language Few-shot Learning. ICCV 23.
[6] Self-regulating Prompts: Foundational Model Adaptation without Forgetting. ICCV 2023.
[7] Visual-Language Prompt Tuning with Knowledge-guided Context Optimization. CVPR 2023.
[8] Prompt-aligned Gradient for Prompt Tuning. ICCV 2023.
[9] PromptKD: Unsupervised Prompt Distillation for Vision-Language Models. CVPR 2024.
[10] DARTS: Differentiable Architecture Search. ICLR 2019.