尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【FHE 同态加密】我们如何实现同态加密推理(十三):位级可复现性——4 线程与 8 线程为什么算出不同的结果(并行确定性陷阱)

【FHE 同态加密】我们如何实现同态加密推理(十三):位级可复现性——4 线程与 8 线程为什么算出不同的结果(并行确定性陷阱) 【FHE 同态加密】我们如何实现同态加密推理十三位级可复现性——4 线程与 8 线程为什么算出不同的结果并行确定性陷阱关键词同态加密 FHE 位级可复现 确定性 线程数 RNG 播种 随机数 并行陷阱 大模型密文推理导读同一份源码、同一份输入4 线程与 8 线程跑出来的密文在位级上不同——而在同态加密推理里可复现几乎是可信度的全部。本篇讲这个现象的根因线程局部随机数用固定常量播种、修复方案按自同构指数 k 播种并如实说明这个修复截至本文尚未落地。项目仓库Gitee 主仓https://gitee.com/pei-xiaoguang/kestrel-llmGitHub 镜像https://github.com/m13253246268-ship-it/kestrel-llm相关文档术语与数据口径 性能与基准 构建与复现 快速上手 架构总览0. 一句话结论同一份源码、同一份输入T23_NT4与T23_NT8跑出来的密文在位级上不同。根因不是浮点误差而是随机性的来源不对随机数状态是线程局部的、且用固定常量播种——于是哪个线程抽到哪几个随机数取决于调度器而不是取决于数学。修复方案早就定了按自同构指数k播种。但按本项目不漂亮的结论照实写的纪律必须说清楚截至本文写作这个修复还没有落地。源码里ckks_rng_state仍然由固定常量初始化。因此不同线程数结果位级不同这条限制当前仍然成立。1. 现象它是被比字节发现的不是被看误差发现的这个问题的发现方式本身就是重点。如果我们只看max|err|一切正常——4 线程和 8 线程的结果都在容差内verify_layer都会打PASS。它们只是不完全相同而max|err|从来不是用来衡量相同的。发现它靠的是逐字节比对u0_{t}_{h}.ct × 8 → BIT_IDENTICAL: 8 / 8 ← 同一线程数、同一输入 换成另一个线程数 → 逐字节不同顺带说明我们的工具链里本来就有这个能力——verify_layer -BitA u5 -BitB u5b -L 5就是专门做位级比对的。教训如果你从不比字节你就永远不会知道自己的可复现只到数值接近这一层。2. 根因三行代码全部问题就在这几行vllm_ckks.c/* RNGxorshift64*原型级 */static_Thread_localuint64_tckks_rng_state0x243F6A8885A308D3ull;/* ← 固定常量 */staticuint64_tckks_rng_u64(void){...uint64_txckks_rng_state;x^x12;x^x25;x^x27;ckks_rng_statex;/* ← 只有线程自己推进 */returnx*0x2545F4914F6CDD1Dull;}两个要素叠加成灾要素后果static _Thread_local每个线程有自己独立的随机数序列互不干扰初值是固定常量所有线程从同一个状态起步于是每个线程都会从同一个种子开始生成同样的随机数流但谁生成了几个、用在哪个对象上完全由调度决定。举个具体样子假设某次加密需要 100 个随机系数4 个线程平分——线程 0 拿前 25 个、线程 1 拿 26–50 个……8 个线程平分则变成每线程 12~13 个。同一个逻辑对象拿到的随机数在两种线程数下是不同的。噪声因此不同密文自然不同。而噪声不同在同态加密里是完全合法的——噪声本来就是随机的只要在界内。这就是为什么没有报错。3. 为什么max|err|永远看不见它max|err|衡量的是解密后与明文参考差多少。两种线程数的结果都是合法的密文都在噪声界内所以误差都在容差里。这引出一个更普遍的判断数值接近与结果相同是两个不同的性质而前者无法推出后者。在浮点计算里我们习惯了误差是连续的但在整数模运算 噪声的世界里差异可以是非连续的——两个结果可能一模一样也可能差得毫无规律而它们的误差看起来都在正常范围。4. 为什么修复方案是按k播种修复思路很直接既然随机性不可消除那就让它变成输入的函数。具体做法是按自同构指数k来播种——而这条路之所以顺是因为代码里本来就有按k组织的入口本系列第 6 篇intckks_gk_gen_k(ckks_gk_t*gk,constckks_sk_t*sk,constckks_ctx_t*ctx,uint64_tk);intckks_rotate_k(ckks_ct_t*ct,...,uint64_tk);/* 一般 Galois σ_kk 任意奇数 */也就是说结构上早就是按k索引的只是随机数播种没跟上。修复的难度不在改架构而在改播种点——并且要确保每一个使用随机性的地方都覆盖到密钥生成、加密、以及一切需要采噪声的操作。为什么这样能修好一旦播种只依赖k以及其他确定性输入那么线程 5 抽到哪几个随机数就不再由调度决定——每个逻辑对象自己有一个确定的随机数流与谁在执行、有几个执行者无关。5. 当前状态与我们的诚实口径源码现状本文写作时static_Thread_localuint64_tckks_rng_state0x243F6A8885A308D3ull;/* 固定常量推理路径 */文件里唯一一处时间相关的播种在自测函数里intckks_self_test(void){...ckks_rng_state(uint64_t)time(NULL)^0xDEADBEEFCAFEBABEull;——它不在推理路径上。所以对lay/boot的实际计算没有任何影响这一点我们核对过避免把自测里的随机误当成推理里的随机。因此我们对外一律这么讲说法是否成立“同一源码、同一线程数在 x86-64 与 aarch64 上逐系数位级一致”✅ 成立层 0–4 已验证“同一源码不同线程数结果位级一致”❌不成立“结果在容差内可复现”✅ 成立与线程数无关所有可复现性声明都必须绑定T23_NT。这句话我们写进了交付文档也写在了本系列第 1 篇与第 14 篇里。6. 同一类的第二个案例懒初始化的并发首次触发在自举驱动里有一条修复记录tab_prepare(n);/* M1c 修复coeff_to_slot/slot_to_coeff 的 omp 区并发首次触发 ... */原来某些预计算表是在并行区里首次访问时懒初始化的。多个线程同时第一次进入 →并发触发初始化→ 结果取决于哪个线程先到。这和 RNG 是同一类问题RNG 播种懒初始化不确定性来源线程局部状态 固定初值初始化时机与线程到达顺序表现位级不同数据竞争偶发错误会不会报错❌ 不会❌ 通常不会修法让随机性成为输入的函数在并行区之前完成初始化共同点都是执行顺序被当成了隐式输入。7. 一份可复用的排查清单如果你在做任何并行 随机化 结果比对的流水线下面这些模式值得逐一排查线程局部随机状态_Thread_local/thread_local/ TLS RNG尤其是初值为常量的懒初始化在并行区内首次访问触发的初始化浮点归约顺序并行求和的分组方式不同 → 结果位级不同对我们是整数模运算但同样的陷阱在别处存在非确定的分块策略动态调度schedule(dynamic)、工作窃取哈希/字典遍历顺序某些语言的字典迭代顺序与插入/内存布局相关时间或地址参与运算把time()、指针值当作种子或输入。判断标准很简单问一句这个结果只依赖输入吗如果答案里出现了取决于调度/时序/内存布局那它就不是确定的。8. 安全边界务请读完本文所述参数为机制验证级远低于 HE 参数标准的 128-bit 水平不得用于保护真实数据。 本文主张的是一个真实的可复现性缺陷的定位与结论。 本文不主张安全强度、性能优越性4 线程与 8 线程的性能差异不在本文讨论范围。另一处必须说明的本文引用的 RNG 是原型级的 xorshift源码注释即写原型级。它不是为了密码学安全设计的不应被当作安全随机源使用。这与本项目不主张安全强度的边界一致。9. 这一篇的未解问题补丁还没落地。方案清楚、入口现成但做和说是两件事。在它落地之前我们的任何位级复现结论都带着一个星号。修复的覆盖面需要审计。不只是播种点还要确认所有消耗随机性的路径都被覆盖——漏掉一处问题就以更低概率复现更难查。没有一条自动检查来防回归。理想情况下CI 里应该有一个同输入、不同线程数比对字节的测试一旦不一致就报警。我们目前靠人工比对。T23_NT之外还有哪些隐式输入没有系统梳理。比如g_fold/g_chk这些阶段参数、预计算表的构建顺序都值得过一遍 §7 的清单。下一篇给出账本一跳 1.8 小时钱到底花在哪——以及为什么再优化乘法基本是白费力气。
返回列表