10 月 12 日,指控字节跳动前实习生田柯宇干扰模型训练的更详细证据整理公开。
在此之前,关于田柯宇的新闻密集出现,先是他的创业公司传出已获五源资本、IDG资本等近3000万美元融资,估值2亿美元,然后是他自己亮相,给已经有法院初审判决的违法行为做了一番全新的解释。一片哗然,争议四起。
而被田柯宇形容为他以暴易暴的对象的前同事,随即以近乎实名方式放出了详细的证据材料。材料列出五位实名证人:Goku 共同一作陈首发、葛崇剑,LlamaGen 一作孙培泽,同期同组实习生张士龙,以及参与 Goku 项目的 Ting-Che Lin。材料称,他们确认其中与本人亲历相关的内容属实,并愿意提供证言。
与这些名字一起公开的,还有代码、操作时间线和训练异常记录。按照这份《受害者陈述与证据整理》的指控,田柯宇的破坏从 2024 年 3 月持续到 7 月,涉及模型权重、优化器、数据采样和分布式通信等多个环节。他还一直参加团队的排障会议,根据同事的排查进度调整操作。
事实上,整个事件已经产生了明确的法律后果。据《21 世纪经济报道》对判决书的报道,法院一审认定田柯宇违约,判令其赔偿字节跳动 50 万元,其余诉讼请求被驳回。目前,字节已上诉,案件仍在二审。早前,在2024 年时,字节就已确认涉事实习生存在恶意干扰研究项目模型训练的行为,并将其辞退。公司同时澄清,“涉及 8000 多张 GPU、损失上千万美元”等传言严重夸大,事件未影响公司的正式项目及线上业务。
在诸多对此事件的讨论里,关注焦点似乎都在于这笔融资,似乎少有人关注对于训练模型的人们来说,这些证据详细的指控背后,当你在训练模型时遇到了这样的人,意味着什么。
硅星人对照了材料中的代码、PyTorch 官方文档,也与熟悉模型训练和分布式系统,以及对此事有直接了解的多位从业者交流,想看一看这些操作到底是什么样的行为。我们发现,对做模型的人来说,不需要那些夸大的数字,这份新材料描述的行为本身就足够令人愤怒。其中一位LLM 行业研究员对这些操作的判断很直接:
如果材料所述属实,这是“十分严重的蓄意破坏”。
接下来我们就一起看看,在真正训练模型的人们眼里,这些指控背后发生的事情。
GPU 没停,但训练已经被改坏了
假设你正在训练一个模型。你调整算法、准备数据,提交任务,然后观察 loss——可以粗略理解为模型当前的错误程度。正常情况下,你希望通过训练,让它逐步下降。
在这份材料里的第 5 节展示了 Adam、AdamW 优化器代码中的一行:
Plain Text
maximize = group["maximize"] = True
这行代码强制把 maximize 设为 True,覆盖了使用者原本的设置。下面这些代码均为证据整理公开的核心节选。
按照 PyTorch 对这个参数的定义,在原本需要最小化 loss 的任务里,这会让优化器转而以最大化 loss 为目标更新参数。你想让模型少犯错,底层程序却在朝相反的目标用力。
也就是,它达到了一种GPU 仍然在跑,训练步数在增加,日志也在产生的效果。这次实验不会因为“有人改了优化器”而弹出警告。你首先看到的,很可能只是一个怎么也训不好的模型。
这类问题很容易把研究者引向错误的方向:是不是学习率太大了?是不是数据有问题?是不是刚刚尝试的新算法根本不成立?
同一节还展示了 clip_grad.py 中的另一处改动:
Plain Text
clip_coef_clamped = torch.ones_like(clip_coef).mul_(2)
梯度可以粗略理解为模型调整参数时参考的方向和幅度。正常的梯度裁剪,会在梯度过大时将它缩小,帮助训练保持稳定。这处代码却把缩放系数强行设成 2,让梯度被放大。按材料的代码分析,函数返回的仍是放大前计算的梯度范数;如果日志记录这个返回值,就不会直接显示那次额外放大。研究者看到的记录,与实际参与后续更新的梯度发生了错位。
还有一些改动会直接破坏数据的分配。多张 GPU 一起训练时,需要把一批数据按约定分给不同进程。PyTorch 的 DistributedSampler 原本会让各进程按照同一套可复现的洗牌规则,各自取走属于自己的那一部分。材料第 4 节展示的版本,却把生成随机种子的代码换成了:
Plain Text
g.manual_seed(time.time_ns() % 10000000000)
每个进程各自取当前时间,就可能得到不同的洗牌结果,各进程的数据顺序也因此对不上。
结果是,一些样本可能被重复处理,一些样本被漏掉。你明明以为大家分工完成了同一份训练任务,实际喂给模型的内容已经发生了变化。
按材料记载,这处异常于 2024 年 7 月 30 日在训练所用的 Docker 环境中被发现;团队检查登录记录,发现除机器所有者外,只有田柯宇曾登录。次日,另一位成员在田的个人目录下发现了多份相关代码,团队才开始将它们与此前的训练异常逐项对照。
从外面看,这些操作都可能表现成“训练效果不好”。但对于做实验的人,这是个十分具体的陷阱:你以为自己正在检验一个想法,实验条件却已经被人偷偷换过。
越认真对待自己的工作,故障越像是自己的问题
一个训练任务稳定报错,已经足够让工程师头疼。至少,大家还能保留现场、复现问题,逐步缩小范围。
材料中另一类操作,恰恰让这个过程变得困难:故障只偶尔发生,重跑一次,又可能什么都没有。
分布式训练需要不同进程不断同步。在需要一组进程共同参加的通信中,缺席一个,就可能让其他进程等到超时,整个任务卡住。
材料展示的几个代码版本,会以大约二十六万分之一到两百万分之一的单次概率,让某个进程跳过本该参加的通信。这个概率看上去很小,但一次长时间、大规模的训练,会反复执行大量通信操作。某一次触发,就可能拖住整个任务。
材料记录过这样的排障现场:100 台机器运行相同的程序,99 台的 loss 曲线几乎一致,只有一台明显异常。工程师好不容易定位到这台机器,重启尝试复现,程序却又恢复正常。
从业者解释,这些异常,很像模型训练中原本就可能出现的硬件、网络或通信库问题。工程师会沿着这些方向查下去,换机器、改配置、检查网络,等待下一次故障出现,再看上次的判断是否成立。
但如果故障本来就被设计成低概率出现,一次重跑成功,不能说明你找对了原因;下一次失败,也未必能否定你刚刚做过的排查。你很难知道,究竟是哪一次改动起了作用。
材料还展示了另一层掩饰。在替换系统中已安装的 PyTorch 文件的脚本里,出现了这样一行:
Plain Text
os.utime(local, (st.st_atime, st.st_mtime))
它会把文件的访问时间、修改时间设回此前记录的值。按照材料的分析,脚本还会恢复原有的所有者和权限。
据多位工程师透露,很多从业者通常会先检查自己的项目代码,再看最近改过哪些文件。这里动过手脚的,却是训练所依赖的底层框架,连“最近修改时间”这条直观线索也被藏了起来。文件校验和系统审计仍可能查出变化,但常规排障的第一轮,很容易从这里错过。
至于为什么重启也未必能恢复,材料发布者于 10 月 16 日在 GitHub Issue #10 中作了补充:田进入的是已经分配、正在运行的机器,直接修改其现有环境中的 PyTorch 文件,相关改动没有通过修改 Docker 镜像、提交代码的常规流程。
回复还解释,团队当时的训练恢复机制会保留已经分配的机器:
“后续任务也不会重新拉取 Docker 镜像。”
这意味着,重启训练程序时,读到的仍可能是同一份被改过的框架文件。重跑一次实验,并没有让实验环境恢复干净。按材料中的回忆,最严重的一两个月里,北京、上海和西雅图的团队轮流值守,24 小时监控训练任务。
LLM 从业者 TitanWings 向硅星人解释,为什么这类破坏尤其令人反感:
“技术人员其实是十分单纯的,他所有的排查思路都是建立在‘这是个技术问题’的前提上。”
“他会怀疑硬件,怀疑网络,怀疑自己的代码,怀疑数据管线,唯独不会怀疑人。”
这些操作利用的,正是工程师愿意认真解决问题、先从自己的工作中寻找原因的习惯。你越负责,越可能把更多时间投入到一轮又一轮排查里。
破坏者就在排障会议里,这怎么防
这份材料里,有一个在代码上做手脚之外的细节,也让很多和我们交流的从业者难以接受:按亲历者的陈述,田柯宇一直参加团队的排障沟通和会议,还会根据排查进度调整操作。
证据整理者将这种利用同事排查进度、继续调整破坏方式的做法称为“令人不齿”。
在一个训练任务反复失败的团队里,开会同步信息是再正常不过的事。你要告诉同事,刚刚排除了哪种可能,哪台机器出了问题,接下来准备验证什么。大家需要尽快把各自知道的事情拼起来,减少重复劳动。
而按照材料描述,这位制造故障的人也在这会议里静静听着。也就是,你刚刚整理出的线索、准备采取的措施,会成为他调整下一次破坏的参考。团队越充分地交流,他越容易知道大家查到了哪里。
没准,他还会时不时给一些建议,作为团队里的老大哥,与大家一起为故障着急。
这就像放火的人坐在消防队的复盘会上,听完大家下一步准备去哪里查,再换一个地方点火。
和我们交流的研究员称,工程师面对一个普通 Bug 时,至少可以相信,问题不会听完会议以后主动改变自己。这样的破坏却让排障变成了一场极不对等的追逐:你努力总结规律,对方能根据你的总结,继续改变故障的表现。
原本用来提高协作效率的信息共享,也因此成了破坏者可以利用的条件。分享文件,是为了省下重复准备的时间;开放排查进展,是为了让大家少走弯路。到了这份材料所描述的处境里,两件事都可能反过来伤害团队。
田柯宇在最近公开的访谈中,把自己的操作描述为“十天左右”“五次左右”,只针对一名实习生,并称之为“以暴制暴”。
10 月 15 日,陈首发在 GitHub Issue #9 中直接回应了这套叙述。他称,访谈里的“实习生 A”拼接了几位受害者的特征;如果说这个形象主要指向谁,应该是他。他还写道,自己是最早发现攻击代码、将其公开到排障群里的人。
陈首发没有否认关停过训练任务。他给出的解释是:高优先级的大规模训练需要整合零散 GPU,相关调度事先经过直接领导同意,执行者也包括领导和 Infra 同事。他特别提到,被关停的任务“包括我自己运行的小规模ablation实验在内”。只要影响大规模训练启动,他自己的小规模对照实验同样会被停止。
这份回应提供了可以逐项核对的细节:同意调度的具体角色、执行如何进行、是否对执行者自己的任务同样适用。它也让保护同事这套解释面对了更具体的问题:一项资源调度哪里有问题,为什么需要通过暗改训练框架、让整个团队长期排障来解决?
修改训练方向、制造随机故障、掩盖文件变化,再根据排障进度调整操作——把这些具体动作逐项展开,“停了几次程序”的描述留下了太多无法解释的地方。
长达几个月,最终连结论都得重查
人们谈起模型训练的损失,最容易想到的是烧掉多少 GPU 卡时。这笔账相对好懂:机器运行多久,资源浪费了多少,重新训练又要花多少钱。但研究者对我们表示,其实你付出的东西,要比这更难追回。
模型研发依赖一轮轮实验积累判断。你改一个算法,看到结果不好,于是淘汰它;换一种数据配比,效果依然不行,于是决定不再投入。一次失败,也能告诉你下一步该往哪里走。
前提是,这次失败真实反映了你的方案。
如果失败来自隐藏的人为干扰,你就可能把本来可行的方向判成死路。几周过去,你根据错误的结果继续修改;几个月过去,围绕这些结果形成的判断又支持了更多决定。即便后来找到了破坏代码,删掉它,也无法自动恢复此前所有实验应有的结果。
“这类攻击会让你这几个月的全部结论和推断都不可靠。”TitanWings 告诉硅星人。
这意味着,团队可能需要回头重新检查:哪些实验受到了影响?哪些结论还能保留?已经放弃的方向,要不要再试一次?用于作比较的旧结果,是否也得重新训练?烧掉的算力、反复排障的工程时间、重新验证实验的成本,会一起出现。即使补回了这些投入,模型研发竞争中的几个月也已经过去了。
同一份材料还记述了另一个项目被迫重做的经过:田柯宇负责的内部 VAR 文生图项目,在展示时实际调用了 PixArt。按材料的指控,团队事后核查真正的训练进展,发现远未达到展示效果,只能重新开展研发,后续形成了 Infinity。
材料还给出了一个具体细节:展示流程会在用户输入的提示词前自动加上“City”,让相同提示词不易与 PixArt 的输出直接对比。测试者反复看到图像背景里出现城市建筑,却一度将它当成训练数据可能存在的问题。
10 月 16 日,陈首发在 GitHub Issue #8 中补充,田及其直接领导都知道城市背景异常。他说明,自己没有就此直接与田交流;在早期与其领导的交流中,他了解到大家“初步怀疑这一现象与训练数据有关”。
按材料给出的解释,演示流程里被人为加进去的一个词,又让同事把疑问带回了训练数据里。
知乎答主 mix.jinny 在公开回答中也提到,相关团队“还在短时间内重构了一版 Infinity”。他通过当时受影响的几名研究人员了解情况,并向硅星人确认可以引用这段公开回答。
当训练和展示的结果都需要重新核验,团队连哪些工作可以继续沿用,都得重新判断。已经投入的时间和心血,可能还要再付出一遍。
TitanWings 最终用“黑暗森林”形容这种状态:没人知道模型不 work,究竟是算法真的不行,还是训练环境被人动过。
研究者当然能接受失败。训练模型,本来就要不断面对失败。但如果连实验环境都无法信任,连一起排查问题的同事都可能在制造问题,每一次失败就都会多出一层无法轻易排除的疑问。
目前,法院一审已经认定违约、判令田柯宇承担 50 万元赔偿,案件仍在二审。这起受到司法追责的事件,有具体的行为记录,也有研究者被打断的工作、被浪费的时间和遭到利用的信任。不过,看起来这些对于法院足够清晰的信息,对于认定田柯宇是个天才的投资人来说,都不那么重要了。