PipeNetwork/kimi-k3-mlx
GitHub: PipeNetwork/kimi-k3-mlx
将 MoonshotAI 的 2.78T 参数多模态 MoE 模型 Kimi-K3 移植到 Apple Silicon MLX 框架,并提供流式转换、专家剪枝和量化推理的完整工具链。
Stars: 229 | Forks: 22
# kimi-k3-mlx
[**moonshotai/Kimi-K3**](https://huggingface.co/moonshotai/Kimi-K3) 的 MLX (Apple Silicon) 移植版 —
这是 Moonshot 的 2.78T-total / 104B-active 原生多模态 MoE,基于 Kimi Delta
Attention (KDA) 和 Attention Residuals (AttnRes) 构建,支持 1M-token 上下文。
`kimi_k3.py` 是一个单一的、与原生 `mlx-lm` 兼容的文本塔模型定义;
`kimi_k3_vision.py` 是视觉塔和投影器;`kimi_k3_vl/` 是将它们结合起来的
mlx-vlm 封装。
`scripts/convert.py` 是一个流式转换器 —— 这里不能使用 `mlx_lm convert`,
因为它会将整个模型实例化,而 K3 在 bf16 下高达 5.6 TB。
## 架构
| 特性 | 详情 |
|---|---|
| MoE | **896 个路由专家,top-16**,`moe_intermediate_size=3072`;sigmoid 路由器 + 无辅助损失的 `e_score_correction_bias`;每个 token 上有 **2 个共享专家** |
| **Stable LatentMoE** | 路由专家在 **3584-d latent** 中运行,而不是 7168-d 残差:`down_proj` → 专家 → `RMSNorm` → `up_proj` |
| Attention | 93 层:**69 KDA** + **24 门控 MLA**,96 个注意力头,`head_dim=128` |
| KDA | 在 q/k/v 上使用短卷积 (k=4),低秩遗忘门 (`f_a`/`f_b`),**全秩输出门**,`gate_lower_bound=-5.0` |
| MLA | q-LoRA 秩 1536,kv-LoRA 秩 512,**NoPE**(完全没有旋转位置编码),**sigmoid 输出门** (`g_proj`) |
| **AttnRes** | 每隔 12 层将残差推入栈中;每个子层输入是该栈上的一个 **softmax 混合**,由秩 1 的 `[1, 7168]` 方向量打分 |
| 激活函数 | **SiTU-GLU**:`β·tanh(g/β)·σ(g) · β_lin·tanh(u/β_lin)`,β=4,β_lin=25 |
| 密集层 | 仅第 0 层 (`first_k_dense_replace=1`,`intermediate_size=33792`) |
| 词表 | 163,840 · tiktoken · `tie_word_embeddings=False` |
| 源格式 | 路由专家以 **MXFP4** 发布 (`weight_packed` u8 + e8m0 `weight_scale`,组大小 32);其余均为 bf16 |
参数预算 —— **97.94% 的模型参数是路由专家**:
| 组件 | 参数量 | 占比 |
|---|---|---|
| 路由专家(源中为 MXFP4) | 2722.7 B | 97.94% |
| KDA 注意力 ×69 | 30.6 B | 1.10% |
| 共享专家 | 12.2 B | 0.44% |
| MLA 注意力 ×24 | 5.6 B | 0.20% |
| latent-MoE up/down | 4.7 B | 0.17% |
| embed + lm_head | 2.4 B | 0.08% |
| 其他所有 | 1.8 B | 0.07% |
| **总计** | **2780.0 B** | |
该模型精确重现了已发布的仓库大小(预测 1.561 TB,实际也是 1.561 TB),这验证了上述的计算。
### 移植说明
K3 中有五个在 MLX 其他地方从未出现过的东西。`mlx-lm` 的 `kimi_linear.py`
(Kimi-Linear-48B-A3B) 涵盖了 KDA、MLA 和 DeepSeek 风格的稀疏 MoE;
下面的所有内容都是全新的,并且在 `kimi_k3.py` 中每一个都标记了 `[K3-n]`:
1. **SiTU-GLU** 激活函数,在 fp32 下计算(tanh 饱和点足够远,以至于 bf16 的舍入是可见的)。
2. **AttnRes** —— 在不断增长的块残差栈上进行 softmax 混合,贯穿每一层并在模型输出时再应用一次。
3. **LatentMoE** —— 专家在 3584 维度下操作;共享专家则不是。
4. **q-LoRA + 输出门控 MLA** —— Kimi-Linear 只有一个普通的 `q_proj` 并且没有门控。
5. **逐通道 KDA 衰减。** 参考的 `modeling_kimi_linear.py` 将
`A_log` 分配为 `[num_heads]` = `[96]`,但**每个分片都提供 `[128]` = `head_dim`**,
而 `b_proj` 是 `[96, H]`,`dt_bias` 是 `[12288]`。fla 的 kernel 将
`A_log` 与形状为 `(B,T,96,128)` 的 `g` 进行广播,因此长度为 128 的向量只能
与尾随轴对齐:衰减是逐通道的,并在各个头之间共享。参考的初始化代码相对于发布的权重已经过时了 ——
它们的形状是权威的。假设在这里使用 Kimi-Linear 的布局会产生
无声的垃圾数据,因此 `tests/test_kimi_k3.py` 对此进行了断言。
### 视觉塔
`kimi_k3_vision.py` —— 一个 3D(支持视频)的 MoonViT,27 层 / 447 M 参数,
加上 patchmergerv2 投影器。mlx-vlm 的 `kimi_vl/vision.py`(用于
Kimi-VL 的 MoonViT)是现有最接近的 MLX 代码;这里沿用了其 complex64 2D-rope
方法,并在六个方面有所不同,在文件中标记为 `[K3-V*]`:
| | 区别 |
|---|---|
| `[K3-V1]` | 3D 网格 `(t,h,w)`:在可学习的 2D 网格之上使用 1D sin-cos **时间**嵌入,并且 rope 频率逐帧重复 |
| `[K3-V2]` | **双线性**位置嵌入插值(Kimi-VL 使用双三次);mlx-vlm 仅提供双三次/最近邻内核,因此这里直接按照 torch 的 `align_corners=False` 半像素约定编写 |
| `[K3-V3]` | `qkv_hidden_size` 1536 ≠ hidden 1024 —— 注意力的运行范围比残差更宽,因此 `wo` 是 `[1024, 1536]` 并且**不是方阵** |
| `[K3-V4]` | RMSNorm,而不是 LayerNorm |
| `[K3-V5]` | 任何地方都没有偏置 |
| `[K3-V6]` | `sd2_tpool` 合并:对帧进行均值池化,然后进行 2×2 空间合并,最后是一个带有 **exact (erf)** GELU 和 post-RMSNorm 的 2 层 MLP 投影器 |
ViT 块使用 **tanh-approximate** GELU,而投影器使用 **exact**
GELU。参考代码刻意使用了两者;它们是不同的函数。
与 2.78T 的文本塔不同,视觉塔足够小,可以进行真正的验证 ——
`tests/test_vision_parity.py` 在 CPU 上运行 Moonshot 实际的 torch 代码,
与 MLX 移植版进行对比 **(在完整的 K3 维度上,涵盖所有 27 层)**,并且端到端
匹配达到 **1.5e-6 相对误差**(float32 舍入误差)。
一个判断决策:参考代码构建 `nn.RMSNorm(dim)` 时没有 `eps`,因此
torch 回退到 `finfo(dtype).eps` —— 在 fp32 中是 1.19e-7,但**在 bf16 中是 7.8e-3**,
这在实质上产生了不同的归一化器。`VIT_NORM_EPS` 使用 fp32 值,与
upcast 实现相匹配。`projector_ln_eps` (1e-5) 由 config 显式给出,并且
仅应用于投影器的 post-norm。
### 多模态封装
`kimi_k3_vl/` 是 mlx-vlm 结构的包:`Model`(胶水代码),`LanguageModel`,
`VisionModel`,以及 config 管道。核心部分是 **expanding
merge**。
K3 的处理器将原始文本中的每个 `<|kimi_image_placeholder|>` 重写为
```
<|media_begin|>image {W}x{H}<|media_content|><|media_pad|><|media_end|>
```
从而使得 token 化的 prompt 为每个图像携带 **恰好一个**
`<|media_pad|>`(id 163605),它必须扩展为该图像的 *整个* 特征块。这
**不**是大多数 LLaVA 风格模型的工作方式,也不是 mlx-vlm 现有的
Kimi-VL 胶水代码所做的事情 —— 在那里,处理器已经为每个
图像 token 发出了一个占位符,并且合并是一个等长散列。
在这里应用散列会为每个图像保留一个 token,并静默丢弃
其余部分:没有崩溃,没有错误,只是一个在写出流畅散文的同时,实际上
对大部分图片视而不见的模型。`tests/test_vl_wrapper.py` 针对手工构建的预期序列直接断言了这种扩展。
行是通过连接占位符之间的跨度构建的 —— O(images)
次连接,远比参考代码的索引算术更容易检查。
在 batch > 1 时,行是左填充的,与参考代码的 `left_padding`
分支相匹配。
还有两个陷阱:
- config 中的 `full_attn_layers` / `kda_layers` 是**从 1 开始的**:config `[4,8,…]`
意味着 tensor 层是 `3,7,…`。MLA 落在 `3,7,…,91,92` 上。
- 源文件声明了 `quant_method: "compressed-tensors"`,原生 mlx-lm
将其映射为 **affine 4-bit group 32** —— 这对于 `mxfp4-pack-quantized` 数据是错误的。
转换器会剥离该块并写入一个明确的 `quantization` 块。
## 级别
**源专家已经是 MXFP4 —— 4 位的真实信息。** 这
决定了整个级别列表。
MLX 有一个原生的 `mxfp4` 量化模式,使用 *完全相同* 的编码
(低位优先代码,e8m0 缩放因子)。因此,源字节可以
在 **完全不需要算术运算** 的情况下被重新解释为 MLX —— `mxfp4` 级别是位精确的,经过验证端到端零误差。将这些相同的权重重量化为 affine 4-bit 会产生
**9.8% 的平均相对误差**,并且体积 *更大*(4.5 对比 4.25 bpw),因此普通的
`4bit` 级别被严格占优,不予构建。
| 级别 | 专家 bpw | 非专家 | 大小 | 专家误差对比源 |
|---|---|---|---|---|
| **mxfp4** | 4.25 | bf16 | 1.56 TB (1.45 TB 专家 + 115 GB 其余) | **0 — 位精确** |
| `3bit` | 3.50 | 4-bit | 1.22 TB | 有损(第 2 次量化过程) |
| `mixed2` | 2.50 | **bf16** | 0.97 TB | 有损 |
| `2bit` | 2.50 | 4-bit | 0.88 TB | 有损 |
bpw 列是 **专家** 的 bits/weight,而不是模型平均值 —— 专家占
97.94% 的参数,所以它是影响大小的数字。`mixed2` 和
`2bit` 以相同方式量化专家,*仅* 在如何处理
其他 2% 上有所不同:`mixed2` 将它们保留为 bf16,这多花费了 83 GB,并将问题孤立出来
“当没有其他东西退化时,2-bit 专家是否能够存活?”。两者都接受
`--nonexpert-bits` 来将其降低。
(这两个配置在分离之前是字节相同的;这里的大小和
bpw 数据是根据上面的参数表重新计算的,该表精确重现了为 `mxfp4` 测得的 1.561 TB。`tests/test_convert_roundtrip.py`
现在断言没有两个配置描述相同的量化。)
`6bit`, `8bit` 和 `bf16` 不予构建:它们会将cast 的 4-bit 值存储在
2.26 / 2.95 / 5.56 TB,比源文件更大,且质量没有任何提升。
## 多模态:已验证端到端
`scripts/vl_generate.py` 在一个 *已发布的* artifact 上运行整个链条 —— PIL
图像 → `KimiK3VisionProcessor` → MLX 视觉塔 → `<|media_pad|>` 扩展 →
量化文本塔 → tokens:
```
image (448, 448) -> 1024 patches, grid (1, 32, 32), 256 image tokens
prompt 14 tokens, 1 media_pad -> expands to 269
prefill 269 merged positions in 9.8s (expected 269)
--> The image shows two geometric shapes rendered as
```
测试图像恰好包含两个形状(一个红色正方形,一个蓝色圆形),
模型报告了“两个几何形状” —— 对于它从未见过的图像,
计数和类别都是正确的。
`269` 是核心数字。一个等长的散列 —— 即 mlx-vlm 的
Kimi-VL 胶水代码所做的,也是最容易想到的复制方式 —— 本来会合并到 14
个位置,向模型显示一个 token 而不是 256 个,并且依然能产生流畅的
文本。`scripts/vision_test.py` 单独覆盖了该塔:权重与源位相同,
token 计数与处理器相匹配,并且两幅
不同的图像之间的余弦相似度为 0.21(一个忽略其输入的塔本可以
通过所有形状检查)。
## 已发布
| repo | 大小 | 专家 | 校准基于 | tok/s |
|---|---|---|---|---|
| [Kimi-K3-REAP73-MLX-mxfp4-q8](https://huggingface.co/pipenetwork/Kimi-K3-REAP73-MLX-mxfp4-q8) | 451 GB | 242/896 | 混合(11 个来源) | **5.51** |
| [Kimi-K3-REAP80-MLX-mxfp4-q8](https://huggingface.co/pipenetwork/Kimi-K3-REAP80-MLX-mxfp4-q8) | 350 GB | 179/896 | 混合 | **5.54** |
| [Kimi-K3-REAP73-zh-code-MLX-mxfp4-q8](https://huggingface.co/pipenetwork/Kimi-K3-REAP73-zh-code-MLX-mxfp4-q8) | 451 GB | 242/896 | **中文 + 代码** | **5.51** |
| [Kimi-K3-REAPgraded-MLX-mxfp4-q8](https://huggingface.co/pipenetwork/Kimi-K3-REAPgraded-MLX-mxfp4-q8) | 451 GB | 326/896, 双库 | 混合 | **2.68** |
在每次构建中,存活的专家都是 Moonshot 的 MXFP4 的位精确副本;
唯一丢失的信息就是剪枝本身。需要 `--nonexpert-bits 8`,而不是装饰性的:如果非专家使用 bf16,242 专家构建将达到 504 GB,并且在 512 GiB 的机器上加载时会被 OOM 杀死。
**这些是可交互的。** 在 512 GiB M3 Ultra 上达到 ~5.5 tok/s —— 相当于在 ~819 GB/s 下 per-token 流量所允许的 9.5 tok/s 的 58%,所以正如预期的那样是受带宽限制的,而不是病态的。每个 token 读取 ~87 GB 的权重,
其中 61 GB 是非专家 tensor,尽管只占参数的 2%,但在每个 token 上都会被触及。
数据是 3 个 prompt × 96 个 token 的平均值,并与一个独立的测试工具进行了交叉检查;同一级别内的偏差低于 1%。
数字说明了两个早期文本弄反了的问题:
- **剪枝换取的是内存,而不是速度。** Per-token 流量取决于 `top_k` 和
非专家精度,从不取决于 *存储* 了多少专家 —— 所以 350 GB 的
179 专家构建和 451 GB 的 242 专家构建的解码速度完全相同。剪枝是为了
适应更小的机器,而不是为了运行得更快。
- **分级双库构建导致 2.06 倍的解码耗时**(相同内存占用下 2.68 对比 5.51)。参见 [REAP 专家剪枝](#reap-expert-pruning);单层微基准测试测得的 1.16 倍的逐层同步并没有在 92 层之间分摊掉。
## 现实检验
**没有任何 *未剪枝* 的级别可以在任何 Apple Silicon 机器上运行。** 峰值统一
内存最高为 512 GB(M3 Ultra Mac Studio);这里最小的完整级别是
883 GB。要将 4-bit 塞进 512 GB 需要 ≤1.38 bits/weight。这就是
[REAP 剪枝](#reap-expert-pruning) 存在的意义 —— [已发布](#published) 中剪枝的构建确实放得下,也确实能运行。
后果,明确地说:
- **未剪枝的级别** 从未产生过一个 token。没有 perplexity,没有
生成,没有冒烟测试 —— 这不是走了捷径,而是在这硬件上
算术上不可能。本文件中的每个测量数字都来自
REAP 剪枝的构建。
- 对它们 *已* 验证的内容是:mxfp4 级别对比源文件的位精确度,
100% 的 checkpoint-key 覆盖了所有 497,220 个 tensor,小规模下的
架构 prefill/decode 一致性,以及有损级别针对源文件的
逐专家余弦相似度。参见 [验证](#verification)。
- 2-bit 级别是在 Moonshot 的 MXFP4 之上的 **双重量化**,并且
它付出了真实的质量代价。这现在是测量出来的,而不是猜的:一个带有 2-bit 专家的 REAP73
构建是 262 GB,足够小,可以运行并且绰绰有余,
并且记录了 **17.03** 的留存 perplexity,同时在中文和法文 prompt 上循环,而 mxfp4-专家的分级构建则不会。`mixed2` 将非专家保留在 bf16 正是
出于这个原因。
## 验证
```
scripts/test_all.sh # registers kimi_k3.py, runs all 76 tests
scripts/verify.py --path out/Kimi-K3-MLX-mxfp4 --src Kimi-K3-src
scripts/perplexity.py --path out/ --calib-text out/calib.txt \
--skip-tokens --out out/ppl.npz
```
| suite | 覆盖内容 | |
|---|---|---|
| `test_prune_apply.py` | REAP 计划应用 + 路由器重编号 | 18 |
| `test_kimi_k3.py` | 架构 + 497,220-key 覆盖率 | 17 |
| `test_vl_wrapper.py` | placeholder 扩展 + 多模态路径 | 11 |
| `test_reap.py` | 显著性累积和规划模式 | 10 |
| `test_vision_parity.py` | 视觉塔对比 torch 参考 | 9 |
| `test_processor_integration.py` | 真实处理器 -> MLX 塔转换 | 7 |
| `test_convert_roundtrip.py` | 转换器 + profile 区分度 (基于 mini-K3) | 4 |
使用 `scripts/test_all.sh` 而不是直接运行测试套件:测试导入了
`mlx_lm.models.kimi_k3`,因此编辑 `kimi_k3.py` 而不重新注册它,
会静默地测试旧版本。
`tests/test_processor_integration.py` 在真实的 PIL 图像上运行 Kimi-K3 实际的
`KimiK3VisionProcessor`,并断言 MLX 塔发出的
token 数量与 `media_tokens_calculator` 预测的完全一致(对于
611x437, 224x224, 1920x1080 分别为 352 / 64 / 2691)。正是这种相等性保持了 prompt 中预留的
图像槽位与填充它们的特征相对齐。
`tests/test_vision_parity.py` 导入了 Moonshot 真实的 `modeling_kimi_k3.py` 并
在 CPU 上使用匹配的权重运行它(`fla` 被 stub 了 —— 它是
*文本* 塔的 KDA kernel 的一个 CUDA/Triton 依赖,在这里永远不会被触及)。
`tests/test_convert_roundtrip.py` 构建了一个结构上忠实的、以 *源* 磁盘格式存储的微型模型(MXFP4 专家对,`language_model.*` 前缀,带有索引的分片),并将其推过 `convert.py` → `mlx_lm.load` → forward pass 以测试所有四个 profile。它断言输出的 mxfp4 专家是位完全相同的。
`scripts/verify.py` 在不加载真实转换级别的情况下检查它:config
一致性,索引完整性,从 `ModelArgs` 推导出的精确模块覆盖率,以及
一个数值抽查:从输出和源文件中同时对抽样专家进行反量化。它报告每个专家的 **余弦相似度** —— 量化噪声几乎不会
改变它,但如果专家堆叠错误或投影转置错误,相似度会降至 ~0,
而此时的幅度统计可能看起来仍然合理。
## REAP 专家剪枝
`scripts/reap_calibrate.py` + `scripts/reap_plan.py`。这是
让 K3 能在 512 GB 机器上 *运行* 的唯一途径 —— 参见 [现实检验](#reality-check)。
REAP ([Cerebras](https://github.com/CerebrasResearch/reap)) 通过在校准集上计算
`S_j = (1/|C|) Σ g_j(x)·‖e_j(x)‖₂` 来为每个专家评分,保留每层最显著的
专家,并让路由器重新归一化。
校准名义上意味着运行一个 1.56 TB 的模型。但这并不是必须的:层 L 的显著性
仅取决于进入 L 的隐藏状态、其路由器
门控及其专家输出 —— **与下游没有任何关系**。因此它完全像
转换器一样进行流式处理:构建一层,将整个校准集推过它,
记录,释放,推进。专家作为真正的 mxfp4 量化层运行,所以
评分的算术运算正是已发布级别所执行的算术运算。
| 校准 | 峰值 RAM | 专家计算量 |
|---|---|---|
| 32 × 2048 (64k tok) | ~32 GB | 6.4 PFLOP |
| 64 × 2048 (128k tok) | ~41 GB | 12.7 PFLOP |
| 128 × 2048 (256k tok) | ~58 GB | 25.5 PFLOP |
峰值由 AttnRes 的块栈决定(8 × tokens × 7168),而不是
权重。磁盘只读取 1.56 TB 一次。
```
scripts/make_calib.py --out out/calib.txt --mb 12
scripts/reap_calibrate.py --src Kimi-K3-src --out out/reap_saliency.npz \
--calib-text out/calib.txt --seqs 64 --seqlen 2048
scripts/reap_plan.py --saliency out/reap_saliency.npz --mode graded \
--hi 0.15 --lo 0.20 --out out/reap_plan.json
```
然后使用任何 profile 应用该计划:
```
scripts/convert.py --src Kimi-K3-src --out out/Kimi-K3-REAP73-mxfp4 \
--profile mxfp4 --prune-plan out/reap_plan.json
```
剪枝会 **重新编号** 专家 —— 新索引 i 是旧专家 `keep[i]` —— 因此
路由器的 `gate.weight` 行和 `e_score_correction_bias` 会被重新排序以
匹配。如果弄错了,模型会加载、运行并发出流畅的文本,同时将
每个 token 路由到错误的专家;没有形状检查能发现它。转换器
对这两个 tensor 进行断言而不是保护,并且
`tests/test_prune_apply.py` 固定了这种行为:恒等剪枝在位级别上是一个无操作,并且一个剪枝后的模型必须等同于
将完整模型中丢弃专家的 correction bias 设为 −inf 的结果。第三个测试旋转路由器行,以证明
等效性检查确实可以检测到路由错误。
每层专家计数记录在 `config.json` 中的 `expert_counts` 里,因此
`global` 模式的不均匀层可以正确加载 (`kimi_k3.experts_in_layer`)。
规划模式:`uniform`(已发布的 REAP 模型所使用的),`global`(将所有
层放在一起排名,具有 4·top_k 的可路由下限),以及 `graded`。
`graded` 使用 REAP 的显著性排名来分配 **位宽**,而不是
丢弃它:顶层专家使用 mxfp4(在 K3 上是免费的),中间层专家较低,尾部丢弃 —— 在相同的内存下多了约 30% 的专家(在 448 GB 下是 313 对比 240)。
MLX 为每个专家 tensor 固定一种位宽(`QuantizedSwitchLinear` 存储 scalar
`bits`/`group_size`/`mode`;`gather_qmm` 将它们作为 scalar 接收),因此这两个
级别存在于 `kimi_k3.TwoBankSwitchGLU` 内部的两个库中。
朴素的双库前向传播需要 2 倍的专家计算量 —— 在每个
路由对上运行两个库并进行选择。相反,它利用了 `SwitchGLU` 已经执行的排序操作:一旦
对被按专家索引排序,拥有连续索引范围的库就拥有一片连续的 *切片*,
所以每个库只做自己分内的事。在 K3 REAP 维度下进行孤立测试(3584→3072,240 名专家,top-16),单层情况:
| | 对比单库 |
|---|---|
| prefill (256 tok) | 1.05× |
| decode (1 tok) | 1.16× |
| 内存,相同专家数 | **82%** |
分割点依赖于数据,所以它必须传回 host 才能进行切片 ——
并且这种同步本质上就是全部的开销。Decode 只路由 `top_k` 对,
太少了,不足以分摊两次 kernel 启动加上一次设备同步的开销,因此在 1024 对
索引通过一次传输下载,并用 numpy 进行分区。仅此一项就将
单层 decode 的耗时从 1.44× 降到了 1.16×。
**那个微基准测试在与真实模型接触时无法存活。** 在发布的构建上
端到端测量,大小都在 ~450 GB 并且都接好了线:
| build | experts | tok/s |
|---|---|---|
| REAP73 single-bank | 242 | **5.51** |
| REAPgraded two-bank | 326 | **2.68** |
**2.06× 的 decode 惩罚** —— 本质上就是这个设计旨在避免的
“运行两者并选择”方案的全部成本。单层的 1.16× 是真实的,但
具有误导性:开销是逐层的 host 同步,而 K3 有 92 层 MoE,所以在
某一层的 kernel 启动中可以分摊掉的开销,在沿着 decode 关键路径串行排列的 92 层
中就无法分摊了。如果有什么区别的话,Graded 理应 *更快* —— 其部分路由专家是 2-bit 而不是 mxfp4,所以它
每个 token 移动的内存更少 —— 这将整个 2.06× 的代价都归咎于双库机制。
诚实的总结是:graded 以 **一半的 decode 速度** 换取了约 35% 更多的专家和明显更好的中文。
这笔交易是否值得完全
取决于工作负载,并且上面的微基准测试不应被解读为
对其代价的预测。
正确性取决于一项测试:赋予两个库 *相同* 的权重和精度,
结果必须 **完全** 匹配普通的 `SwitchGLU`(只要单库也进行了排序,它在位级别上确实如此)。这将分区/反排序机制与
量化误差隔离开来,因此那里的 bug 无法躲在“低级别是有损的”背后。
**校准数据是一种建模选择,而不是一种形式主义。** 无论
语料库代表不足的内容是什么,都会被静默地剪枝掉 —— 没有错误,没有警告,只是一个看起来很
完好的模型,直到有人用缺失的语言写东西。
`scripts/make_calib.py` 构建了一个刻意的组合:40% 代码(多语言 +
真实 Python 文件),30% 英文网页,15% 中文,15% 分布在 ja/ru/ko/de/
fr/es/ar。有两件事必须通过测量而不是假设来修正:
- C4 的混合 `multilingual` 配置在它的前 200 个
文档中测得 **97% 的拉丁语系** —— 实际上是第二轮的英文。具名
的按语言配置取代了它。
- 在那个混合流中,CJK 占了语料库的 **0.03%**。中文现在有
一个明确的份额,并达到了 ~11%(实际消耗的前缀的 14%)。
来源是交替的,而不是拼接的:`reap_calibrate.py` 读取前
`seqs × seqlen` 个 token,因此拼接的语料库将完全基于
先写入的来源进行校准。C4 的 `zh` 分割也带有一些双重编码的文档,其乱码仍能通过一个简单的“含有 CJK”的测试,所以
通过 CJK 比例阈值过滤了它们。
一个值得一提的警告:K3 的 LatentMoE 在 `up_proj` 之前将 RMSNorm 应用于 *合并后的*
专家输出,因此绝对的专家范数在一定程度上被下游重新归一化了。这可能使 K3 比标准的 MoE 对剪枝更具鲁棒性,但
这也意味着 `‖e_j(x)‖` 测量到的东西有一部分被抵消了 —— 该准则可能
需要重新表述为相对贡献。两个共享专家无论如何都会在每个 token 上触发,
并在任何剪枝中存活下来,提供了一个没有任何东西能触及的底线。
## 专家按领域聚类 —— 已测量
`scripts/reap_calibrate.py` 按来源语料库标记每个校准 token,并
在一次传递中累积每种语言的显著性(一个单一的融合 `(source, expert)`
散列)。`scripts/reap_overlap.py` 随后比较 top-N 专家集。
从 896 个专家中随机挑选两个 top-242 的重合度按概率是 **27%**。对比
这个基准:
| pair | overlap | vs chance |
|---|---|---|
| code-python ↔ code-multi | 57.2% | 2.1× |
| lang-de ↔ lang-es | 59.3% | 2.2× |
| lang-de ↔ web-en | 56.5% | 2.1× |
| chinese ↔ lang-ja | 42.8% | 1.6× |
| **chinese ↔ code-python** | **17.8%** | **0.66× — 低于概率** |
代码、欧洲语言和 CJK 各自形成一个聚类;代码和中文是 *负*
相关的。`scripts/reap_subset.py` 利用了这一点:一个打过标签的运行已经
持有了按来源的显著性,因此一个针对领域的构建不需要第二次
校准 —— 只需对你想要的 bucket 求和即可。
**它有效,而且两面都有利。** 五个构建,都是 451 GB(除了一个),不同
仅在于校准目标。相同的 prompt,贪婪解码,24 个 token:
| build | retained | Chinese | code |
|---|---|---|---|
| mixed | 59.1% | 良好,在 ~18 tok 时循环回 prompt | 正确 |
| REAP-80 (179 experts) | ~50% | 严重的重复循环 | 正确 |
| English+code | 68.4% | **完全崩溃** | 正确 |
| Chinese-only | 79.8% | 没有循环,但含糊不清 | **完全崩溃** |
| **Chinese+code** | **69.3%** | **最好 —— 正确且具体,没有循环** | **正确** |
剪掉一个领域的专家,那个领域就死了;保留它们,它就会改善;结合
两个领域,两者都能保持。专家集合决定了能力,而你
通过选择语料库来选择专家集合。
在 `机器学习的基本原理是` ("the basic principle of machine learning is") 上的原话回复:
```
mixed ,机器学习是人工智能的核心,是一切计算机视觉化、网络化的基础。
机器学习的基本原理是,机器学习是人工智能 <- loops
English+code ,机器学习的基本原理是,机器学习的基本原理是,机器学习的基本原理是 <- collapse
Chinese-only :通过计算机模拟人脑的思维,由计算机实现人类对自然的延伸和扩展
Chinese+code :通过训练数据,学习算法,然后对未知数据进行预测。
机器学习的过程是:输入数据→学习算法→ <- correct and specific
```
以及在代码 prompt 上,中文-only 完全丧失了能力:
```
mixed / En+code / zh-code if not intervals: return [] ; intervals.sort(...) ; merged = [...]
Chinese-only """Merge overlapping intervals." x4 <- collapse
```
**关于证据强度的一个警告。** 这是每个领域一个 prompt,24 个 token,
贪婪解码。它与两个独立的测量结果一致 —— 专家重合度和
显著性保留 —— 这就是为什么它可信的原因,但这不是一个严谨的
评估。一个真正的声明需要大量的 prompt 和每个领域的留存 perplexity。本文件的一个早期版本基于单个代码 prompt 将目标校准记录为 *无效* 结果,而这个 prompt过于确定以至于
每个构建都发出相同的 token;那个结论是错误的,同样的
怀疑态度也适用于这里的积极结果。
这个警告现在部分解除了:`scripts/perplexity.py` 对在
按来源语料库分桶的留存文本上为构建评分,这是本节要求的严谨版本的
评估。它需要 `--skip-tokens` 而不是使用默认值,因为
`reap_calibrate.py` 消耗语料库的 *第一个* `seqs × seqlen` token,从偏移量 0 开始评分会把每个构建放在其自身计划
所拟合的数据上进行评估。
## 在 2-bit 专家上的 AWQ —— 已测量
`scripts/awq.py`。K3 使 AWQ 异常便宜:LatentMoE 通过 **相同** 的 3584-d latent 路由每层的每一个专家,因此每层一个缩放向量就可以覆盖
所有的 896 个专家 —— 1.3 MB,相对于每个专家的表所需的 1.2 GB —— 并且
其逆运算恰好折入 `routed_expert_down_proj`:
```
w1' = w1 · s w3' = w3 · s down_proj' = down_proj / s
```
`alpha` 在每一层上针对激活加权的重建
误差进行了网格搜索,这就是为什么这是校准过的,而不是一种启发式方法。在 65,504 个 token 上的留存 perplexity,对比一个在其他方面字节相同的构建:
| | perplexity |
|---|---|
| 2-bit experts, no AWQ | 17.0288 |
| 2-bit experts + AWQ | **16.6573** — −2.18%, 95% CI [−4.56%, −0.34%] |
**总体数据是对所发生事情的糟糕总结。** AWQ 并没有改变
典型的 token:每个 token 的 NLL 增量的中位数是 −0.0001 nats,并且只有 51% 的
token 得到了改善。它会进行 *重新分配*,按照对照组发现
每个 token 的难度来排序:
| control NLL band | tokens | mean Δ (nats) |
|---|---|---|
| below median | 32,752 | **+0.0403 — AWQ worse** |
| median–p90 | 26,201 | −0.0359 |
| p90–p99 | 5,895 | −0.2442 |
| top 0.1% | 65 | −0.9426 |
这就是 AWQ 宣传的机制 —— 保护携带
激活的通道 —— 表现为 **尾部修复,而不是均值偏移**。它通过
牺牲容易的 token 来买取困难的 token。净结果是获得 13,795 nats 对比丢失 12,350,
两个大得多的相反效应的 1.12:1 残留,这就是为什么它很
脆弱:去掉三个最有利的序列,就会把 −2.18% 变成 −0.76%。
它也存在领域倾斜,并且这种倾斜与校准语料库相符。中文占了
AWQ 校准集的 36.4%,改善了 5.7%;阿拉伯语占了 1.3%,退化了
6.6%,西班牙语 1.4%,日语 2.0%,所有的 CI 都排除了零。每层一个缩放向量无法对
每个领域同时最优 —— 校准部分上面记录的关于剪枝的
同样教训,通过另一条途径再次印证。
`scripts/make_balanced_calib.py` 的存在是为了消除这种混淆:它对现有
语料库重新采样,使每个来源的 token 计数相等,按文档轮询从而使
序列保持混合,并接受 `--skip-tokens` 以避开留存区域。
在尝试这个之前,有两件事值得了解:
- 权重空间探测 **将效果低估了一个数量级**。
在选定的 alpha 下,激活加权重建误差仅比 alpha=0 好
0.18%,因为它取的是均匀平均值,对于一个破坏容易 token 而拯救
困难 token 的改变是盲目的。
- AWQ **与分级计划不兼容**,`convert.py` 拒绝这种
组合。折叠操作每层将 `down_proj` 除以 `s` 一次,但
graded 的高位库为了保持位精确而直接未缩放通过,所以这些专家会
读取 `latent/s`。在 `s ≈ 1 ± 5%` 的情况下,它会静默退化而不是报错。
## 构建
```
scripts/download.sh # 1.56 TB, resumable
scripts/build_all.sh # mxfp4, 3bit, mixed2, 2bit
scripts/build_all.sh mxfp4 # or one tier
```
转换器的峰值内存是几十 GB:它每次只走一层,
并且对于有损级别,它一次反量化并重量化一个专家,而不是
在 bf16 中堆叠 896 个专家(这会达到每层 59 GB)。
## 用法
发布后,量化版本可以在原生的 **mlx-lm** 上运行;唯一额外的步骤是
注册 `kimi_k3.py`,因为架构还没有上游合并。
```
pip install mlx-lm
python - <<'PY'
import os, shutil, mlx_lm
from huggingface_hub import hf_hub_download
dst = os.path.join(os.path.dirname(mlx_lm.__file__), "models", "kimi_k3.py")
shutil.copy(hf_hub_download("pipenetwork/Kimi-K3-MLX-mxfp4", "kimi_k3.py"), dst)
print("registered kimi_k3 ->", dst)
PY
```
## 状态
- [x] 文本架构移植 (`kimi_k3.py`)
- [x] **视觉塔** (`kimi_k3_vision.py`) — 在全 K3 维度下对比 torch 的对等性,1.5e-6
- [x] 流式转换器 + 验证器,4 个 profile,经过了 round-trip 测试
- [x] 证明了 MXFP4 → MLX mxfp4 的位精确传递
- [x] 76/76 测试通过
- [x] 下载源文件 (1.56 TB)
- [x] 真实转换
- [x] **mlx-vlm 封装** (`kimi_k3_vl/`) — 扩展 `<|media_pad|>` 合并,
针对真实的处理器进行了端到端验证
- [x] 发布到 `pipenetwork/` (4 个级别)
- [x] **留存 perplexity** (`scripts/perplexity.py`),按来源分桶
- [x] AWQ 已实现并测量 — [小、脆弱、领域倾斜](#awq-on-the-2-bit-experts--measured)
- [ ] 在平衡的校准语料库上重新运行 AWQ(进行中)
- [ ] 在接线修复后为所有已发布级别重新测量 tok/s
## 来源与许可
`reference/` 包含 **来自
[moonshotai/Kimi-K3](https://huggingface.co/moonshotai/Kimi-K3) 的未修改文件**,被 vendor 进来使得移植版可以与它所针对的代码进行 diff 比对和数值测试:
`modeling_kimi_k3.py`, `modeling_kimi_linear.py`, `configuration_kimi_k3.py`,
处理器,`config.json`,以及一个 gzipped 的 `model.safetensors.index.json`。
这些文件是 Moonshot 的,遵循 Kimi K3 License(其中 llava 衍生的部分
遵循 Apache 2.0)—— 参见上游 repo 中的 LICENSE。转换后的权重
同样带有上游许可;`scripts/convert.py` 会将其复制到每一个级别中。
`reference/` 之外的所有内容 —— `kimi_k3.py`, `kimi_k3_vision.py`,
`kimi_k3_vl/`, `scripts/`, `tests/` —— 属于本移植版。
标签:DLL 劫持, MLX框架, 人工智能, 凭据扫描, 多模态模型, 大语言模型, 模型剪枝, 模型转换, 混合专家模型, 用户模式Hook绕过, 逆向工具