【FHE 同态加密】我们如何实现同态加密推理(一):把 2B 大模型拆成 28 段密文链条来算(纯 C11 · 零依赖)

简介: 本系列记录纯C11、零依赖、CPU-only实现2B大模型(Qwen3-VL-2B)全密文推理的全过程:将模型拆为28段可验证密文链,每跳约1.8小时,聚焦可复现性与机制验证,非安全级部署。(239字)

关键词:同态加密推理 | 全同态加密(FHE)| 大模型密文推理 | 2B 大模型 | 密文链 | 纯 C11 | 零依赖 | CPU 推理 | 隐私保护推理

导读:同态加密推理(FHE 推理)让大模型的前向计算直接跑在密文上,在不可信环境里保护用户数据。本系列记录我们用纯 C11、零第三方 FHE 库、无 GPU/NPU,从零实现一套 CPU 同态加密推理引擎的全过程。本篇是开篇,讲清我们为什么把一个 2B 大模型拆成 28 段密文链条来算,以及我们没做到什么。

项目仓库
Gitee 主仓:https://gitee.com/pei-xiaoguang/kestrel-llm
GitHub 镜像:https://github.com/m13253246268-ship-it/kestrel-llm

相关文档(文中口径均以这些页面为准)
术语与数据口径 |
性能与基准 |
构建与复现 |
快速上手 |
架构总览

0. 一句话结论

我们让一个 2B 参数的大模型(Qwen3-VL-2B)在密文上做前向推理,做法不是"造一台更强的机器",而是把模型切成 28 段,每段各自成为一个可交接、可独立验证的密文包——于是一台普通 CPU、跑 1.8 小时,就能为整条链贡献一段。

成功的部分:前 5 层(层 0–4)已在纯 CPU 上可复现、可独立验证;链尾(层 26–27 + 收尾)也已跑通;交接是逐字节可验证的——第三方不需要信任打包者。

没做到的部分:参数是机制验证级,不是安全级;精度余量不宽;中间 21 层(层 5–25)在写这篇时仍待认领。


1. 为什么"单机跑完"从一开始就不是目标

同态加密推理的工程现实是这样的:模型有多大,密文就有多大;层数有多深,噪声预算就有多紧。

以我们这个配置为例(§2 会给完整口径):

项 值
模型 Qwen3-VL-2B-Instruct,dim=2048 layers=28 heads=16 ff=6144 vocab=151936
多项式次数 n=2048
槽位数 1024
缩放因子 scale = 2^60
层内前向链长(编译期) 112 个素数
自举链长(编译期) 2100 个素数

一枚"密文"是一个 RNS 表示的多项式环元素。n=2048、112 个 60-bit 素数、2 个分量的密文,落盘就是 ≈3.5 MB。而一层要处理 8 枚(4 个 token 位置 × 2 个分量)——一层就是几十 MB 的密文,而且噪声预算会随着深度被吃掉,必须周期性地"自举"把链刷回去。

换句话说:这条链的瓶颈不是算力总量,而是噪声预算的消耗速度与单机内存。

所以我们的设计选择是——按层横向切开。


2. 观测口径(先交代前提,再上数字)

按本项目《术语与数据口径》§2.5 的纪律,先声明口径。

2.1 硬件与软件

项 值
开发/基准平台 AMD Ryzen 7 9800X3D(8 核 16 线程)
编译器 gcc,-O2 -fopenmp
依赖 仅 libc + libm,无 GPU / 无 NPU / 无第三方 FHE 库
引擎 纯 C11 自研,NTT / CKKS / 线程池均为本仓源码

2.2 参数口径

项 值 说明
CKKS_N 2048 编译期覆盖(头文件默认 1024)
CKKS_NPRIMES lay=112 / boot=2100 / verify_layer=112 编译期量,指该程序支持的最大链长
交接密文链长 np=112 落盘的 u{L}r112_*.ct
scale 2^60
线程数 T23_NT ⚠️ 线程数是结果变量,见 §4

2.3 可比性纪律(重要)

  • 跨轮次的绝对耗时不可比。本文所有耗时均为同轮交错 A/B,不引用历史会话的绝对值。
  • 耗时数字必须与线程数绑定陈述,否则无意义。

3. 一次"跳"(hop)的契约

整条链的定义非常窄,窄到可以写在信封背面:

layL :  u(L-1)r112  ──→  uL
bootL:  uL           ──→  u(L)r112
  • uX 表示"第 X 层的输出密文";后缀 r112 表示已刷新、链长为 112。
  • 一跳 = lay(层内前向:QKV、attention、FFN、激活)+ boot(自举刷新)。
  • 一跳产出 8 枚密文(4 个 token 位置 × 2 个分量),打包交给下一位接力者。
  • 交接包是自描述的:密文 + 日志 + 元数据 + manifest.sha256。

为什么必须交替?因为 lay 会把模数链吃掉,而 boot 把它刷回去。"链长"就是这条流水线上的油箱表:

阶段 链长(素数个数) 编译期链长
输入(上一跳产出) 112 —
lay 消费后 16 112(t23lay)
boot 刷新后 112 2100(t23boot)

boot 之所以需要 2100 这么长的编译期链,是因为自举内部的折叠逼近需要很深的乘法深度;而落到盘上的交接密文只需要 112。

未核实项:boot 日志里会打出 out np=2083 作为"刷回满链"的标志串(每层 8 次)。这个 2083 与编译期 2100、落盘 112 之间的精确对应关系,本文不做断言——引用时请以源码为准。


4. 为什么"拆开"不等于"不可信"

这是整个设计里最容易被质疑的一点:把密文交给不认识的人算,凭什么信?

我们的答案不是"信",而是让信任不必要:

  1. 逐文件 SHA256。交接包内每个密文、每个日志都有哈希,manifest.sha256 自校验。归档实测:主包 104/104、链尾 45/45、工具包 6/6。
  2. 独立复核工具。verify_layer 自己做"解密 → 解码 → 对明文参考算 max|err|",刻意不采信驱动的 RESULT=PASS(这一点值得单独写一篇,见本系列第 14 篇)。
  3. 位级可复现。同一份源码、同一线程数,在 x86-64 与 aarch64 上产出逐系数位级一致。
  4. 数据可自助生成。约 7 GB 的数据包不随仓库分发,但可以用 tools/preproc/ 的脚本从模型自助生成——我们验证过生成物与分发物逐字节相同(权重 9/9、26 个逐层模式文件 26/26)。

一个必须讲清的坑:线程数是结果变量

T23_NT=4 与 T23_NT=8 跑出来的结果在位级上不同。

根因不是"浮点误差",而是:vllm_ckks.c 里使用了 _Thread_local 随机数状态、并以固定常量播种——哪个线程抽到哪几个随机数,取决于调度器。于是"同一个计算"在不同线程数下消耗了不同的随机性,结果自然位级不同。

修复方案是按自同构指数 k 播种,让输出成为数学的函数,而不是调度器的函数。

但必须如实说明:截至本文写作,这个修复还没有落地。源码里 ckks_rng_state 仍然由固定常量初始化:

/* vllm_ckks.c */
static _Thread_local uint64_t ckks_rng_state = 0x243F6A8885A308D3ull;

(文件里唯一一处时间相关播种在 ckks_self_test() 内,不在推理路径上。)所以"不同线程数结果位级不同"这条限制当前仍然成立,我们的交付口径也因此绑定线程数:所有可复现性声明都必须在固定 T23_NT 的前提下讲。

这件事的教训比 bug 本身重要:任何"并行 + 随机化 + 比对结果"的流水线,都有同一个陷阱,而且它不会体现在 max|err| 上——你不去比字节,就永远看不见它。(详见本系列第 13 篇。)


5. 安全边界(务请读完)

本文所述参数为机制验证级(n=2048、112 素数内层链、2100 素数自举链),
远低于 HE 参数标准的 128-bit 水平,不得用于保护真实数据。
本系列主张的是:可复现性与工程量账本。
本系列不主张:安全强度、性能优越性、与任何其他方案的优劣对比。

补充两点,避免任何误读:

  • n=2048 与自研的非密码学 RNG(原型级 xorshift)意味着这套东西不具备密码学安全强度。它的价值在于验证"机制能不能跑通、能不能被第三方复现"。
  • 我们不声称比任何其他同态加密推理方案更快或更优。本文出现的所有耗时数字,都是我们自己这份实现在我们自己这台机器上的账本。

6. 现在的进度(照实写)

部分 状态
层 0–4(链头) ✅ 可复现、可独立验证;lay 判据 RESULT=PASS、boot 判据 BOOT=PASS
层 26–27 + 收尾 ✅ 已跑通并归档
层 5–25(21 跳) ⏳ 待认领
单跳耗时(实测) lay0 1920 s、boot0 4285 s → 一跳约 1.8 小时(8 核 CPU)
精度余量 ⚠️ 不宽。链尾抽样中 u27 为 8 项通过 6 项,其中 t1h1 = 3.82e-2、t3h1 = 7.97e-2,超出 verify_layer 默认容差 3e-2

最后一行我们本来可以不写。但按本项目的纪律——不漂亮的结论照实写。


7. 这一篇的未解问题

  1. 自举是绝对瓶颈:boot0 的分段 profile 显示,coeff_to_slot(1688 s)与 slot_to_coeff(1716 s)合计占了 约 80% 的时间,sin_fold 约 850 s。也就是说,继续优化乘法几乎不解决问题,瓶颈在域变换。
  2. 误差预算缺少统一理论:我们目前是逐层实测 + 逐层定标(tools/preproc/_adap_scale.py),没有一套"从 28 层反推每层可用误差预算"的推导。链尾那两项超阈,是否应该判定为失败而不是记账,我们还没定论。
  3. "下一棒不信任上一棒"在极端情况下是否成立:目前的验证是"产出对不对",还没有做到"上一棒的输入有没有被替换"——这一点需要额外的机制。

下一篇我们讲一个更底层的问题:为什么我们最终放弃了 BFV 方案——以及一个看上去无关紧要的细节(明文模上的除法语义)是怎么把 GELU 逼死的。

相关文章
|
2天前
|
缓存 C++
0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
本文实测RK3588板端冷启动仅1.0–1.2秒,核心在于mmap直挂VQF权重文件:不全量读取,仅映射+校验头部,权重页由内核按需缺页加载,配合预量化布局,较safetensors全量读快33倍。(239字)
 0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
|
3天前
|
编译器 C语言
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局
本文详解C11 `_Static_assert` 在内存布局守卫中的关键作用:针对mmap直挂场景,通过编译期断言钉死VQF格式三结构体(200/432/64字节),杜绝因对齐差异导致的静默错位。真机RK3588实测验证,实现错误前移。
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局
|
1天前
|
编译器 C语言
0.8MB 跑通 Qwen|第 6-1 篇:推理引擎的 ARMv8.2 dotprod——`vdotq_s32` 一条指令做 4 个点积
本系列《0.8MB跑通Qwen》聚焦ARM零依赖纯C推理引擎,实测RK3588平台,适配Qwen3-VL多模型。本文详解ARMv8.2 dotprod指令(vdotq_s32)如何在真实大内核中释放性能,破除微基准误导,揭示寄存器压力下加速本质。(239字)
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 14-2 篇:单 SSE 流与串行队列——推理引擎的"单机守则"
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多尺寸模型(2B/8B/30B),基于RK3588实测。本文详解“单机守则”:推理状态串行锁定、SSE流独立输出、HTTP与推理线程职责分离,揭示为何默认串行是稳定前提而非性能缺陷。(239字)
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 7-3 篇:推理引擎的预热与测量抖动——为什么报告要"重复 3 次取中位"
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎开发,实测适配Qwen3-VL多模型,在RK3588上验证性能抖动——单内核耗时波动达1.7倍,确立“预热+3次取中位”为可信基准口径。(239字)
|
1天前
|
编解码 算法 C++
0.8MB 跑通 Qwen|第 17-3 篇:位级一致性实验——推理引擎两条转换路产物逐字节对拍
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎实现,实测RK3588上位级一致性验证:同源重跑确定性、无损源跨格式殊途同归、有损源再量化边界清晰可测。覆盖Qwen3-VL多模型,强调sha256/逐张量哈希/块级比对三位一体验证。(239字)
|
1天前
|
调度 C++ 索引
0.8MB 跑通 Qwen|第 12-3 篇:复现 13.1×——推理引擎的跨进程恢复实测怎么做(含口径拆解)
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588平台运行Qwen3-VL多模型;详解跨进程KV恢复机制,揭示prefill/TTFT/wall三口径差异,13.1×加速比可复现、可验证。(239字)
|
1天前
|
并行计算 NoSQL Java
0.8MB 跑通 Qwen|第 4-2 篇:推理引擎的 vllm_tp_parfor——调用线程也干活
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588平台高效运行Qwen3-VL多模型。本文深入`vllm_tp_parfor`,揭示调用线程主动领任务、静态切块与分级收尾机制,以计数器实验确证“每核皆算力”,展现极致轻量与工程严谨的统一。(239字)
|
1天前
|
缓存 自然语言处理 安全
0.8MB 跑通 Qwen|第 11-1 篇:大模型推理中多轮对话的浪费——上一轮 prefill 算完就被扔了
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588平台高效运行Qwen3-VL多模态模型;核心突破是默认启用前缀KV复用,使多轮对话prefill耗时骤降27倍,显著消除历史重复计算,大幅提升端侧长会话效率。(239字)
|
1天前
|
缓存 内存技术
0.8MB 跑通 Qwen|第 9-2 篇:推理引擎的 flash_attn_single_q_q8_neon——单 q 单遍扫全 KV
本系列《0.8MB跑通Qwen》聚焦ARM零依赖纯C推理引擎,精读`flash_attn_single_q_q8_neon`内核:通过Q单次量化、q8 KV单遍扫描、在线softmax与尾部归一,显著降低带宽与内存开销,实现在RK3588等端侧设备高效运行Qwen3-VL系列模型。(239字)