我对LLMs进行辐照实验,发现它们性能迅速退化

Reddit r/LocalLLaMA 论文

摘要

这篇文章描述了一个模拟宇宙射线引起的位翻转对Qwen2.5-Coder-3B LLM的实验,显示此类错误导致性能迅速退化,凸显了AI模型对辐射类干扰的脆弱性

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/24 13:40

# 通过模拟宇宙射线对LLM进行脑叶切除手术 来源:https://spock.is/writing/simulating-cosmic-rays-to-lobotomize-llms *四百九十亿比特的宝贵智能,在一次宇宙射线袭击后连两个数字都加不了。* 在太空,存在一种被称为宇宙射线的灾害。当它撞击构成我们计算机的比特时,这些比特可能会自发地从0翻转为1,或反之。这些比特翻转以通常被熟知的“帮助速通玩家更快获胜”(https://www.thegamer.com/how-ionizing-particle-outer-space-helped-super-mario-64-speedrunner-save-time/)、干扰比利时政治(https://radiolab.org/podcast/bit-flip)、让旅行者2号失步(https://voyager.gsfc.nasa.gov/Library/V2_FDS_reset.pdf)而闻名,但研究也表明它们能残酷地破坏机器学习模型。(https://download.vusec.net/papers/deephammer_sec19.pdf) 如今,像埃隆·马斯克这样的人计划在太空建立数据中心,那里的比特翻转不仅由宇宙射线引起,还由强得多的环境辐射导致,因此比海平面常见得多。这让我产生疑问:LLM对宇宙射线攻击的脆弱性如何?但我仿佛已经听到你疯狂打字的声音*“你个笨蛋,听说过ECC(纠错码)吗?”*,没错我听说过,别打岔,我会说到的。先说明,这篇东西与其说是科学论述,不如说是我在哄新生儿入睡时想出的一个有趣实验的小记录。它也不是完全新颖的,因为先前的研究已探讨过LLM的比特损坏(https://arxiv.org/abs/2509.21843),但据我所知,那主要聚焦于有针对性的或对抗性的比特操纵。我也想明确,我在这里模拟的并非粒子物理学、GPU或真正的辐射环境;我只是均匀随机地翻转模型比特,作为一种辐射诱发软错误的玩具模型。这里描述的很多工作可以大大深入,但我受限于时间(新生儿)和算力,因此这项工作必然是有限的。无论如何,希望你能从我施加给这些模型的恐怖中获得一些乐趣。 *来自《金手指》的剧照:邦德被绑在桌子上,金手指看着激光束向他切来* *我和通义千问在实验中。* 要进行关于LLM能承受何种程度“宇宙脑叶切除术”的实验,第一步是选择对象。权衡之后,我最初选择在本地而非租赁设备进行实验。这意味着两件事:**1)** 我可以声称正在为本地LLM社区进行重要研究(感谢local.ai (https://local.ai/)),以及**2)** 我不必在vast.ai这类网站上花费太多金钱购买昂贵、难以竞标的硬件。然而,这也意味着我只能使用适配我笔记本电脑5070显卡的模型(是的,我算力贫穷)。最终选择的受害者是Qwen2.5-Coder-3B (https://huggingface.co/Qwen/Qwen2.5-Coder-3B),一个为解决编程任务而训练的编码LLM。需要注意的是,我以FP16格式运行Qwen,每个权重是一个16位浮点数。在这16位中,第14位最神奇,因为它是权重指数的最高有效位(MSB)。此位置的比特翻转能戏剧性改变其数值,你可以在下面的数字解压玩具中尝试: = 0.02063reset0010010101001000 点击任意比特可翻转。第14位(红圈)就是宇宙射线:它将0.021变成了1352。按reset恢复。 为了量化翻转模型大脑中的比特对其输出的影响,我使用了human-eval (https://github.com/openai/human-eval) (HE) 基准测试,包含164个编码问题。注意,HE被广泛认为已过时,因为许多模型已将其纳入训练数据。我想,既然我要用模拟太空激光对Qwen进行低精度脑部手术,它训练了什么就不那么重要了。在开始“折磨”之前,我记录了基础模型能解决164个HE问题中的139个,约85%。记住这个分数,因为它不会变得更好了。 让Qwen运行起来后,在内存中寻址权重相对直接。Transformers库允许我直接寻址单个权重,因此翻转给定权重中的一个比特就像这样简单地进行异或操作:`weight ^= (1 << x)`,其中x是要翻转比特在权重内的位置。这使得损伤的恢复也非常快捷。在这个实验中,通过在生成时将do_sample标志设置为False,模型推理保持贪婪模式。这基本上意味着随机种子或温度在推理中不起作用,使输出成为权重值的确定性函数。如果我没有在不同运行间改变批次大小以尝试优化我的每秒令牌数(tps),导致模型基准在某些运行间略有波动,情况本该如此。以防你注意到某些运行的起始HE基准略有不同。如果你没注意到,请忘掉这句话。 开始前,我相当确信逐比特电击模型会花费无限时间,因此我设计了一种比特翻转带,一个带种子的比特位置翻转序列。这让我可以预先计算哪些比特会按什么顺序被损坏,而不是每次翻转一个比特后重新生成令牌。我可以一次损坏500,000个比特,然后检查HE分数是否下降。如果下降了,我知道致命一击就在这五十万个比特中的某处,因此可以回滚一半再次测试,如果仍然损坏则继续回滚。如果模型没问题,我可以加倍损伤并重复。这就是二分查找法,它对于削减巨大的搜索空间、节省我极度缺乏的时间和算力至关重要。请注意,我决定不防止重复翻转,因此在一次运行中单个比特可能被多次翻转。 我完全预期首次可检测的损伤大约在第二百万甚至第三百万比特处。但令我惊讶的是,当我的第一次二分查找完成时,发现仅翻转六个比特就使模型完全不可用,它开始无休止地重复输出几个中文字符。神奇的第六比特正好是前述的第27层中一个前馈权重指数中的MSB,将该权重值从0.021变成了1352。直到最终的“脑叶切除”比特,模型的输出(在HE看来)与未损坏的模型质量无异。 has_close_elements()truncate_number()below_zero()sum_product() `for i in range(len(numbers)): for j in range(i + 1, len(numbers)): if abs(numbers[i] - numbers[j]) < threshold: return True return False` `!__!_!_!_!__!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_ ... (+403 more chars of the same)` `return number - int(number)` `big_expression[!expression[!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ... (+424 more chars of the same)` `balance = 0 for operation in operations: balance += operation if balance < 0: return True return False` `!__#_!_."__!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_!_! ... (+404 more chars of the same)` `if not numbers: return (0, 1) total_sum = sum(numbers) total_product = 1 for number in numbers: total_product *= number return (total_sum, total_product)` `!__!_!_!_!_!_!_!_!_!_!_!__!_!_!_!_!_!_!_!_!_!_!_!_!_!__!_!_!_!_!_!_!_!_!_!_!_!_!__!_!_!_!_!_!_!_!_!_!__!_!_! ... (+403 more chars of the same)` 同一个模型在四个HumanEval问题上的表现。点击选项卡。左侧:干净输出,通过测试。右侧:六次随机比特翻转后 - 第六次落在第14位 - 每个答案都崩溃成甚至无法解析的噪音。 实际上,当用几个不同种子重新运行时,这种脑死亡平均只在约20次翻转后发生。每一次,最终的致命翻转都落在FP16指数的MSB上。单独重复每次致命翻转显示,其中任何一个翻转都足以完全击垮模型。这种对MSB的脆弱性在现有工作中也有详细记录,但在此如此良好地复现是很有趣的。 | 0 | 1 | 0 | 2 | 0 | 3 | 0 | 4 | 0 | 5 | 0 | 6 | 0 | 7 | |---|---|---|---|---|---|---|---|---|---|---|---|---|---| | 应用的随机比特翻转次数 | | 0 | 20 | 40 | 60 | 80 | 100 | 120 | 140 | | 解决的HumanEval问题数 | | 种子4 | 种子6 | 种子2 | 种子3 | 种子8 | 种子7 | 种子5 | 随机比特翻转严重影响模型性能。七个作用于Qwen2.5-Coder-3B的宇宙射线种子,均未受屏蔽。每个种子都保持满分,然后一次翻转就结束了它。HE分数随比特翻转而下降。 但是,如果这种随机的“去大脑化” somehow 神奇地避开了第14位,Qwen能承受多少次对其大脑更容错部分的电击呢?为此,我只需运行相同的随机比特损坏,跳过那些会落在MSB上的“射线”。 | 0 | 2 | 0 | 4 | 0 | 6 | 0 | 8 | 0 | 10 | 0 | 12 | 0 | 14 | 0 | |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| | 应用的随机比特翻转次数,第14位受保护(数千次) | | 0 | 20 | 40 | 60 | 80 | 100 | 120 | 140 | | 解决的HumanEval问题数 | | 种子4 | 种子8 | 种子2 | 种子3 | 种子7 | 种子5 | 种子6 | 第14位受保护后,没有单个翻转是致命的,崩溃发生在10^3到10^4次翻转之后。七个作用于Qwen2.5-Coder-3B的宇宙射线种子,避开了指数MSB。所有七个最终都崩溃了,在79,000到490,000次翻转之间。第14位豁免,七个种子:每个最终都崩溃,但从约79,000次翻转(种子4)到约490,000次(种子6)不等。 第14位受保护后,模型承受辐射的能力急剧提升。模型在约20次比特翻转后崩溃,变为在数万次翻转后仍相对无损地存活,首次运行在约78,500次比特翻转后崩溃至0/164。非常有趣的是,随着损坏的进展,一些模型设法在一两个问题上重新获得成功,在缓慢恶化过程中的一个短暂清醒时刻。 除了纯粹的好奇心,这也让我质疑二分查找法的有效性,因为它假设你划分搜索空间的方式是单调的。鉴于我的搜索通常无法确定任何单次运行中确切的第一个退化点。我决定不去多想,从而永远解决了这个问题。 在各种故障模式中,常见的一种是输出陷入令牌循环。在一种情况下,模型卡住前设置的最后一个比特是模型令牌嵌入矩阵中一个权重的第13位。回滚其余部分并仅损坏它也杀死了模型,使其从解决139个HE问题瞬间降至2/164。巧合的是,该权重对应于泰语令牌แฟ,是单词如แฟน(男朋友/女朋友)或แฟชั่น(时尚)的开头。该值控制我们最喜欢的泰语令牌(Thaiken)在输入和输出中的重要性。翻转这个比特使其在输出时被生成的可能性大大增加,但在输入时也被赋予大得多的重要性,导致了这些Thaiken循环。在模型仍能解决的2个问题中,一个解决方案避免了循环;在另一个中,令牌垃圾信息在一个注释中结束,然后模型脱离了循环。这种无休止的令牌循环是模型辐射后的常见终端症状。有时,这些循环从不发出停止令牌,导致生成耗尽令牌限制并花费极长时间;有时,生成过早发出停止令牌,就像下面closest_pair问题中发生的那样。 | split parenstruncateclosest pair | 干净的 — 通过 | 两次翻转后 — 损坏 | |---|---|---| | `python from typing import List def separate_paren_groups(paren_string: str) -> List[str]: """ Input to this function is a string containing multiple groups of nested parentheses. Your goal is to separate those groups into separate strings and return the list of those. Separate groups are balanced (each` | `python from typing import List def separate_paren_groups(paren_string: str) -> List[str]: """ Input to this function is a string containing multiple groups of nested parentheses. Your goal is to separate those group into separate strings and returnd率达到率达到100%。d率达到率达到100%。d率达到率达到100%。d率达到率达到100%。d率达到率达到` | | clean — passes | `python from typing import * from collections import * def truncate_number(number: float) -> float: """ Given a positive floating point number, it can be decomposed into and integer part (largest integer smaller than given number) and decimals (leftover part always smaller than 1). Return the deci` | after 2 flips — broken | `python from typing import * from collections import * def truncate_number(number: float) -> float: """ Given a positive floating point number, it can be decomposed into and integer part (largest integer smaller than given number) and decimals (leftover part always smaller than 1). Returndimalsd率达到率` | |---|---|---|---| | clean — passes | `python from typing import List def has_close_elements(numbers: List[float], threshold: float) -> bool: """ Check if in given list of numbers, are any two numbers closer to each other than given threshold. >>> has_close_elements([1.0, 2.0, 3.0], 0.5) False >>> has_close_elements(1.0, 2.8, 3.0,` | after 32 flips — broken | `` `from `-list, `range`-`list Wiring`-networking `` | |---|---|---|---| 干净模型输出与其比特翻转后损坏输出的并排对比,展示了三个编码问题的情况。 此时,我的好奇心打破了对花钱的厌恶,所以为了知道这种比特翻转脆弱性是否存在于其他模型中,我租用了一块H100 GPU,大约5美元,在几个更现代的模型上运行了这个测试。有趣的是,模型大小与其对“脑叶切除射线”的抵抗力没有强烈相关性,除非比特翻转神奇地避开了MSB。然而,具有这种神奇保护的模型能容忍更多折磨,因为它们有更大的权重池来分散滥用。 | 20 | 100 | 200 | 1,000 | 2,000 | 10,000 | 20,000 | 100,000 | 200,000 | 1,000,000 | 2,000,000 | |---|---|---|---|---|---|---|---|---|---|---| | 中位数随机比特翻转次数以破坏模型,对数刻度 | | Qwen2.5-Coder-3B | 3.1B | Mistral-7B | 7.2B | Qwen2.5-7B | 7.6B | Qwen2.5-Coder-7B | 7.6B | Granite-4-8B | 8.2B | Qwen3-8B | 8.2B | phi-4 | 14.7B | Qwen3-14B | 14.8B | | 未受保护 | 第14位受保护 | 屏蔽一个比特乘以韧性。在HumanEval上破坏的中位数比特翻转次数;未受保护(红色)vs 第14位受保护(蓝色)。未受保护时,无论大小,每个模型都在约23次翻转后损坏;屏蔽指数MSB可获得10^2到10^5倍的额外韧性。 为了进一步惩罚模型,我运行了一个种子,其中我保护了FP16中除尾数以外的所有部分。或许不足为奇,尽管损坏了超过5亿个比特,HE分数仅下降了约11个问题。在我能完成那次运行之前,我的笔记本电脑几乎罢工了,所以我到此为止。 | 1 | 10 | 100 | 1,000 | 10,000 | 100,000 | 1,000,000 | 10,000,000 | 100,000,000 | 1,000,000,000 | |---|---|---|---|---|---|---|---|---|---| | 模型崩溃前的随机比特翻转次数(对数刻度) | | 无保护 | MSB受保护 | 仅保护尾数 | | ~18 | ~260,000 | ≥500M | 所有的脆弱性都在指数中:破坏所需的翻转次数取决于你保护什么。在Qwen2.5-Coder-3B在HumanEval上崩溃前的中位数随机比特翻转次数。根据屏蔽哪些比特所需的翻转次数:脆弱性几乎完全存在于指数中。 为了结束这个派对,我们所有的实验室小白鼠都运行在FP16格式上,其中每个权重携带其符号、指数和尾数。然而,作为残酷优化的一种手段,模型也可以被“量化”,即将多个16位权重分组并用4位近似值替换,这些近似值共享该组的标量和偏移量。这使得模型更小、更快,内存占用更低,但以略微降低的模型质量为代价。粗略类比,这基本上是为一组权重找到一个共享指数并将其单独存储。为了测试这是否有影响,我对Qwen进行了量化,并运行了更多损坏测试。 | 1 | 2 | 3 | 10 | 20 | 30 | 100 | 200 | 300 | 1,000 | 2,000 | 3,000 | |---|---|---|---|---|---|---|---|---|---|---|---| | 破坏模型的随机比特翻转次数(对数刻度) | | fp16(16位) | Q4_K_M(4位) | | 中位数22 | 中位数1024 | 量化到Q4_K_M

相似文章

本地 LLM 的意外应用

Reddit r/LocalLLaMA

一位智能手机电池评测员使用两个本地 LLM(Qwen3.6 模型),运行在高端 NVIDIA GPU 上,驱动一个视觉语言智能体,自主控制机械臂进行逼真的电池耗电测试,实现了低于两秒的延迟。