- 人工智能
- 大模型
- 推理引擎
- 本地部署
【免费下载链接】kimi-k3-in-c
A 2.78-trillion-parameter Kimi K3 running inference on a single CPU in 8.24 GB of RAM. Portable C99: no BLAS, no framework, no GPU.
本文围绕 docs/TUNING.md 展开,讲清 kimi-k3-in-c 引擎调优中唯一真正重要的决定:把有限的内存预算分给 dense trunk 环缓冲,还是分给 routed-expert 缓存。读完你将掌握:如何用 33% 噪声下限约束调优结论、为什么固定预算下"trunk 优先"能带来约 1.69× 的加速、六个 preset 档位的取舍,以及--incremental、--trunk、--layers等关键开关的底层行为与适用前提。
一、三句话速览
原文档给出的最短版本,也值得全文复述:
- 先运行
./scripts/k3-doctor.sh,使用它点名的 preset。 - 如果你手动调参:先填满 trunk,再喂 expert cache。
- 除非你正在验证"全量重算"的等价性,否则一律加
--incremental。
scripts/k3-doctor.sh 会依次回答三个问题:工具链是否存在、引擎能否构建;机器有多少内存、对应哪个 preset;权重将要流式读取的存储有多快。它是 Linux-only(读取/proc/meminfo、使用 GNUstat/df、依赖O_DIRECT),流式引擎本身同样只在 Linux 上运行。
二、比其它一切更重要的决定:一 GB 给 trunk 还是给 cache
引擎把内存预算切给两个缓存:
--trunk-gb:dense trunk 的环缓冲与 pinned 层预算;--cache-gb:routed experts 的 arena 预算。
这两个数字不可互换,而且不对称性很大。逐 token 来看:
- 引擎每个 token 都要按固定顺序重读全部 108.81 GB trunk(全部 93 层);
- 而 routed experts 只读约 25.8 GB,因为每层只选中 896 个 expert 中的 16 个。
所以:
- 给 trunk 的 1 GB 能移除约 1.17 GB/token 的确定性读流量(钉住一层,之后永不再读);
- 给 expert cache 的 1 GB,在 arena 低于约 36 GB 时,移除的量测量不到。
2.1 固定 128 GB 预算下的六点扫描
原文档在固定 128 GB 总预算下扫描了 trunk/cache 切分,两端点如下:
| trunk | cache | s/token |
|---|---|---|
| 12.3 | 110.7 | 28.38 |
| 110.0 | 13.0 | 16.80 |
仅靠分配就获得 1.69×。更快的配置反而拥有更小的 expert cache,expert retention 为 0.0%。
完整 12 点数据在 docs/data/trunk-cache-split.tsv,覆盖 32 GB 与 128 GB 两个独立预算:
| total | trunk | cache | s/token | hit% | GB read/token | peak RSS |
|---|---|---|---|---|---|---|
| 32 | 2.7 | 24.3 | 31.23 | 0.0 | 25.83 | 32.01 |
| 32 | 6.8 | 20.2 | 30.58 | 0.0 | 25.83 | 31.79 |
| 32 | 10.8 | 16.2 | 31.25 | 0.0 | 25.83 | 31.24 |
| 32 | 16.2 | 10.8 | 30.88 | 0.0 | 25.83 | 31.90 |
| 32 | 21.6 | 5.4 | 29.13 | 0.0 | 25.83 | 32.13 |
| 32 | 25.6 | 1.4 | 28.06 | 0.0 | 25.83 | 32.02 |
| 128 | 12.3 | 110.7 | 28.38 | 44.02 | 14.46 | 127.89 |
| 128 | 30.8 | 92.2 | 25.20 | 42.93 | 14.74 | 127.53 |
| 128 | 49.2 | 73.8 | 25.69 | 33.97 | 17.06 | 128.14 |
| 128 | 73.8 | 49.2 | 18.37 | 32.20 | 17.51 | 128.19 |
| 128 | 98.4 | 24.6 | 19.46 | 0.00 | 25.83 | 127.36 |
| 128 | 110.0 | 13.0 | 16.80 | 0.00 | 25.83 | 127.85 |
读法:128 GB 预算下,一旦 trunk 预算跨过大约 73.8 GB(钉住足够多层),s/token 从 25.7 档跳入 18–19 档;而 12.3/110.7 这种"cache 优先"切分即便 hit 率 44%,GB/token 也降得有限,总速度仍是最慢的。32 GB 预算下所有切分都落在 28–31 s/token,因为总预算太小,分配差异被淹没——这正是原文档提醒的:这些是 33% 噪声下限之上的单样本,读方向,别读精确倍数。
方向性结论由两个预算共 12 点支撑:128 GB 处 Spearman ρ = −0.886,32 GB 处 −0.714,且与上面的机制分析一致;但倍数没有被复现。benchmarks/split-sweep.sh 可以在带重复次的条件下重跑该实验:
$ benchmarks/split-sweep.sh <model_dir> <trunk_dir> <out_dir> [total_gb] [reps]它保持总内存固定、只扫切分比例(0.10 / 0.25 / 0.60 / 0.86 给 trunk),默认每个切分重复 3 次,依赖systemd-run --scope --user建立真实的内存天花板(Linux-only),输出带逐 rep 的 TSV。与 benchmarks/memory-ladder.sh 的区别在于:ladder 变的是总量,无法区分"内存更多"与"分配更好",而 split-sweep 只变分配,差异可归因于切分本身。完整数据与噪声下限的讨论在 docs/PERFORMANCE.md。
2.2 特例:trunk 与 checkpoint 在不同物理设备上
上面的不对称性有一个前提:trunk 读和 expert 读竞争同一块盘的带宽。如果你的打包 trunk 在一块盘上、checkpoint(routed experts)在另一块盘上,耦合就不成立:trunk 读发生在另一块原本空闲的设备上,几乎免费地与计算重叠,于是环缓冲已经重叠之后,再钉更多 trunk 层收获甚微。
这种布局下真正重要的阈值,是引擎在启动时打印的那个——"第二个环槽变得可负担"的点:
ring held at 1 slot: a second slot needs X.XX GB and the trunk budget is Y.YY GB, so reads are NOT overlapped with compute. Raise --trunk-gb above X.XX GB to enable it.低于该数字时,trunk I/O 坐在关键路径上,成本可测量地更高;高于它之后曲线变平,继续钉层不再回本。做法是:把--trunk-gb设到略高于该报告值,剩余预算全部给--cache-gb,因为在分体存储布局里expert 设备才是唯一瓶颈。这一结论来自一位将 packed trunk 放在 NVMe、checkpoint 放在独立 SATA 盘的用户实测,完整写见 docs/PERFORMANCE.md。
从源码看,这条消息出自 trunk 流式打开路径的固定打印逻辑(src/io/k3_trunk.c#L469-L479):当请求的环槽数(--trunk-ring N,默认 2)装不进--trunk-gb预算时,环会被压到更少的槽,并提示"Reads are NOT overlapped with compute"(单槽)或"Fewer reads stay in flight"(多槽),同时给出使请求成立的--trunk-gb下限。环槽的代价是实打实的:最低预算下单个槽就是约 2.37 GB,源码注释记录了无条件取第二个槽曾把 laptop preset 从 8.78 GB 抬到 11.12 GB 峰值 RSS(超出 3.0 GB trunk 预算 27%)的教训(src/io/k3_trunk.c#L265-L283)。
2.3 源码印证:trunk 预算为什么真是一个"旋钮"
--trunk DIR启用 trunk 流式后,内存才是可调的。pinned 集合的选取有一个关键设计:按层大小从大到小贪心(而不是最初的前缀序),因为 ring 槽必须容纳"最大的未钉层",前缀序会先钉掉 0 号层(2.34 GB,唯一 dense MLP 层),导致槽白白按它定尺寸、每个预算档位浪费约 1.17 GB。选择是单调的,整个"环槽尺寸 × 钉层集合"的耦合迭代到不动点即收敛(src/io/k3_trunk.c#L288-L330);环境变量K3_PIN_PREFIX=1可恢复旧的前缀序做单二进制 A/B。
对读流量而言,钉哪几层是中性的——总和 B 字节的任意钉层集合,每个 token 恰好省 B 字节;区别全在 ring 槽尺寸上。这就是"--trunk-gb是旋钮"的底层含义:每多给 1 GB,就从最大未钉层开始往下钉,直到预算填满。
三、expert cache 为什么这么弱:是模型设计,不是实现缺陷
K3 的路由器用Quantile Balancing训练,故意把 expert 使用摊平,不让任何 expert 被偏爱。而平坦使用恰恰打穿了 LRU 缓存:没有热子集,几个 GB 留不住任何值得留的东西。实测印证:从 28 个槽到 1,344 个槽,retention 恒为 0.0%,每 token 读的字节一位小数都不动。
它最终还是会生效:arena 大约到 36 GB 时,retention 跳到约 30%,bytes/token 从 25.83 降到 18.11。但那时你本可以用同样的内存钉住 trunk 的大部分,并且更快。
四、Presets:命名的内存预算
presets 存在的目的就是让你不必自己发现 trunk/cache 切分:
$ ./bin/k3 --list-presets| preset | trunk | cache | total | 预期 |
|---|---|---|---|---|
ultra | 2.5 | 0.31 | ~3 GB | 仅 proof-of-life;流式读 embed/lm_head,复用 1 个状态槽 |
laptop | 3 | 1 | ~10 GB | ~32 s/token |
desktop | 16 | 10 | ~32 GB | ~31 s/token |
workstation | 60 | 30 | ~96 GB | ~24 s/token |
server | 110 | 13 | ~128 GB | ~17 s/token |
max | 110 | 109 | ~224 GB | ~19 s/token |
要点(全部来自原文档与 src/cli/k3_run.c 中K3_PRESETS[]表):
server是最佳性价比:trunk 在 110 GB 处已完全钉住,超出部分花在一个贡献甚微的 cache 上。注意在这些测量里max并不比server快——多出的 96 GB 在噪声下限之外什么都买不到。--preset之后的标志会覆盖它,例如--preset server --cache-gb 40是合法的。- preset 表中的 trunk/cache 数字是传给两个分配器的预算;而"能不能跑"取决于整个进程的峰值 RSS(含 safetensors 索引、KV 缓存、scratch),源码注释明确说"引用峰值 RSS,不要引用启动横幅里的计划值"(src/cli/k3_run.c#L482-L507)。实测峰值:laptop 8.2 GB、desktop 31.9 GB、workstation 95.5 GB、server ~128 GB、max ~224 GB。
ultra是一条刻意独立的执行路径,不是更小的普通 preset:它要求 packed trunk、流式读精确 BF16 的 embedding/lm_head 字节、在全量重算下清空并复用 1 个层状态槽,用 I/O 换 RAM,面向短而确定的 proof 运行;此模式下 speculative/draft 解码会被拒绝。算术、Top-K 路由与权重精度都不变。对应 CLI 开关是--ultra-low-memory(src/cli/k3_run.c#L366-L367)。- 另有一个不在表里的
auto档:--preset auto(等价--trunk-gb auto)按本机可用内存(MemAvailable)在启动时计算两个预算,trunk 优先,并扣除索引/递推状态/2 GB + 2% 的边量,避免招来 OOM killer;它依赖/proc/meminfo,非 Linux 平台需显式传--trunk-gb/--cache-gb(src/cli/k3_run.c#L1092-L1158)。
五、其它调优选项
--incremental:跨 token 携带 KV 缓存与递推状态
不再重算前缀,而是把 MLA 层的 KV 缓存与 KDA 递推状态在 token 之间传递。已验证与全量重算产生逐 token 相同的输出;除非你在专门测试这个等价性,否则应该用它。
代价是内存:它每个位置分配约 2.37 MBKV 缓存(24 个 MLA 层的 k/v 以 fp32 展开),长上下文很贵——16k 位置约 39 GB。引擎会预先算出需求并带着两个数字拒绝运行,而不是让你一小时后被 OOM killer 收割(src/cli/k3_run.c#L1353-L1361)。KV 缓存是内存计划里唯一随上下文增长的项,因此也是真实上下文上限(src/cli/k3_run.c#L1429-L1432)。
--trunk DIR:把内存变成旋钮
启用 trunk 流式;没有它 trunk 完全驻留,内存下限约 115 GB。打包一次即可:tools/pack_trunk.py(或 scripts/pack-trunk.sh)。
--layers N:只绑前 N 层
用于在部分下载上测试机制;输出不是完整模型,引擎会明说。
--trunk-ring N:环槽数(默认 2)
一个槽是正在计算的层,其余是飞行中的读;第三个槽让读线程领先一层、让设备持续忙,但要多花约 2.37 GB,预算装不下时仍以预算为准(见上文 2.2 的启动提示)。
六、存储比你想的重要得多
低预算下引擎每 token 要搬约 135 GB,存储带宽通常才是天花板,而不是 CPU:
- 把 checkpoint 与 packed trunk 放到最快的本地 NVMe;
- 网络存储或机械盘会主导一切。设备带宽差 6×,就是这个工作负载的吞吐差 6×——大于本文档里任何调优决定的影响;
./scripts/k3-doctor.sh会实测你的设备,让你知道自己处在哪个区间。
七、线程
OMP_NUM_THREADS默认取核心数。低内存预算下工作负载是 I/O 受限的,一旦 trunk 开始流式,加线程的收益低于直觉。这一点在本引擎上尚未被系统化扫描,计划见 docs/ROADMAP.md。
八、在断定"某个改动有效"之前
同一配置重复运行的实测散布是33%。比这小的差异不是效应。每个实验臂至少跑 3 次;完整流程见 docs/BENCHMARKING.md。
参考文件
| 文件 | 用途 |
|---|---|
| docs/TUNING.md | 本文主体文档 |
| docs/PERFORMANCE.md | 切分扫描全量数据与噪声下限 |
| docs/BENCHMARKING.md | 基准流程 |
| docs/data/trunk-cache-split.tsv | 12 点扫描原始数据 |
| benchmarks/split-sweep.sh | 带重复次的切分扫描重跑脚本 |
| scripts/k3-doctor.sh | 机器体检:工具链/内存/存储 |
| src/cli/k3_run.c | preset 表、auto 预算、KV 缓存预检 |
| src/io/k3_trunk.c | 钉层选取、环槽与启动提示 |
| tools/pack_trunk.py | trunk 打包 |
- 人工智能
- 大模型
- 推理引擎
- 本地部署
【免费下载链接】kimi-k3-in-c
A 2.78-trillion-parameter Kimi K3 running inference on a single CPU in 8.24 GB of RAM. Portable C99: no BLAS, no framework, no GPU.
相关推荐
kimi-k3-in-c 性能剖析:内存预算阶梯、双缓存行为与 33% 噪声底线下的调优方法论
kimi k3 in c 性能剖析:内存预算阶梯、双缓存行为与 33% 噪声底线下的调优方法论 本文基于 docs/PERFORMANCE.md https:/
人工智能大模型推理引擎本地部署kimi-k3-in-c 快速上手:从空机器到 8 GB 内存跑通 2.78 万亿参数 Kimi K3
kimi k3 in c 快速上手:从空机器到 8 GB 内存跑通 2.78 万亿参数 Kimi K3 本文基于仓库自带的 QUICKSTART.md http
人工智能大模型推理引擎本地部署kimi-k3-in-c 深度解析:8 GB 内存、单 CPU 跑通 2.78 万亿参数 Kimi K3 的流式推理全链路
kimi k3 in c 深度解析:8 GB 内存、单 CPU 跑通 2.78 万亿参数 Kimi K3 的流式推理全链路 本文基于开源仓库 kimi k3 i
人工智能大模型推理引擎本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考