nanowhale: HuggingFace 实习生从零实现的微型 DeepSeek-V4
> 一句话版本: HuggingFace 给实习生安排了一个任务 —— 从零复现 DeepSeek-V4 的全部创新架构压缩到 110M 参数,在单张 H100 上训 5K 步就开源出来了。等于有人帮你把 DeepSeek-V4 几十页技术论文拆成了 500 行 PyTorch 代码。
- 来源: https://huggingface.co/HuggingFaceTB/nanowhale-100m-base
- 训练代码: https://github.com/huggingface/nanowhale
- Chat 版本: https://huggingface.co/HuggingFaceTB/nanowhale-100m (3,500 月下载)
- 日期: 2026-04-24(首次上传),2026-05-04(更新)
- 作者: HuggingFace Smol Models Research 团队 / 实习生 cmpatino(ml-intern 项目)
- 许可: Apache-2.0(模型权重),MIT(训练代码)
背景故事
ml-intern 是 HuggingFace 的内部 AI 导师项目,给实习生分配工程任务来学大模型实战。
这一次,他们给实习生 cmpatino 的任务是:
> 用 DeepSeek-V4 的全部架构创新训练一个微型 MoE 模型。
结果就是 nanowhale 🐳 —— 从零实现的 DeepSeek-V4,不依赖任何 DeepSeek 的预训练权重,只借用了它们的 tokenizer。
架构:DeepSeek-V4 的迷你复刻
相比于用 Llama/RWKV 等成熟架构的教学模型,nanowhale 的特殊之处在于它完整实现了 DeepSeek-V4 的四大核心创新:
MLA(Multi-head Latent Attention)
DeepSeek-V4 的核心注意力机制——通过低秩投影(lora_rank=160)压缩 KV cache。在这个 110M 模型里:
- 8 个注意力头,只有 1 个 KV head(MQA 风格)
- 每个 head 的维度 96(32 RoPE + 64 NoPE,NoPE = 无位置编码的部分)
- 输出侧分组投影(o_groups=2, o_lora_rank=80)
这是 MLA 首次以如此清晰的迷你形式在开源代码中出现。
MoE(Mixture of Experts)
- 4 个路由专家 + 1 个共享专家
- 每个 token 激活 top-2 专家
- SwiGLU FFN,中间层 640 维
- SqrtSoftplus 路由评分(不是常规的 softmax)
- noaux_tc 方法(无辅助损失,通过 router z-loss 控制均衡)
Hyper-Connections(替代残差连接)
DeepSeek-V4 用 Hyper-Connections 替代了标准的残差连接:
- hc_mult=4(每层把 hidden state 复制 4 份)
- 用 Sinkhorn routing(2 次迭代)学习组合权重
- 正是这个组件导致 bf16 NaN 问题 —— 在极小尺度下值域溢出
MTP(Multi-Token Prediction)
- 1 个 next-token 预测 head
- DeepSeek-V4 原版用多个 MTP head 提升推理效率
训练细节
| 阶段 | 数据集 | 步数 | Token 数 | 精度 |
|---|---|---|---|---|
| **预训练** | FineWeb-Edu | 5,000 | ~2.6B | bf16 |
| **SFT** | SmolTalk (46 万对话) | 3,000 | ~72.7M | fp32 ❗ |
硬件:单卡 NVIDIA H100 80GB(估算 H100 成本 ~$3/h × 约 15h = ~$50)
| 指标 | 预训练后 | SFT 后 |
|---|---|---|
| 验证困惑度 | 13.62 | 12.90 |
| Token 准确率 | 33.8% | 48.5% |
| 训练 loss | ~5.3 | 2.607(eval) |
已知问题(非常诚实)
模型卡直接标注了三项已知限制:
1. bf16 NaN:Hyper-Connections 在这个小尺度下值溢出 —— 必须用 fp32 推理和训练。 DeepSeek 原版在 671B 参数时这个架构没问题,但缩小到 110M 后数值稳定性变了。这里有实用的教育价值:不是所有架构都能线性缩放到小尺寸。
2. 词表过大:129K 的 DeepSeek-V4 tokenizer 导致 37% 的参数(41M/110M)花在 embedding 上,留给实际语言建模能力的只剩 69M。这个 129K tokenizer 对 7B 模型合适,对 110M 是负担。
3. from_pretrained 奇怪行为:自定义架构导致 from_pretrained 会重新初始化部分权重 —— 官方推荐的加载方式是 load_state_dict。
代码库结构
nanowhale/
├── modeling_deepseek_v4.py ← 核心模型定义(~500 行)
├── configuration_deepseek_v4.py
├── scripts/
│ ├── train_pretrain.py # 预训练脚本
│ ├── train_sft.py # SFT 微调
│ ├── chat.py # 交互式聊天
│ ├── eval_smoke.py # 评估
│ └── upload_to_hub.py # 上传到 HuggingFace
└── tokenizer/
├── tokenizer.json # DeepSeek-V4 tokenizer
└── tokenizer_config.json
与其他教学模型的对比
| 模型 | 参数 | 架构 | 用途 | 训练成本 |
|---|---|---|---|---|
| **nanoGPT** | ~0.1B | 标准 Transformer | GPT-2 教学 | 极低 |
| **nanowhale** | **0.11B** | **DeepSeek-V4** (MLA+MoE+HC+MTP) | **前沿架构教学** | ~$50 |
| **nanoChat** | ~0.1B | 标准 Transformer | Chat 教学 | 极低 |
| TinyLlama | 1.1B | Llama | 实际可用 | ~$500 |
| SmolLM2 | 1.7B | Llama | 实际可用 | ~$1K |
实际跑一下
from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file
config = AutoConfig.from_pretrained("HuggingFaceTB/nanowhale-100m-base", trust_remote_code=True)
model = AutoModelForCausalLM.from_config(config, trust_remote_code=True).float()
weights_path = hf_hub_download("HuggingFaceTB/nanowhale-100m-base", "model.safetensors")
model.load_state_dict(load_file(weights_path), strict=True)
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/nanowhale-100m-base")
⚠️ 注意:必须 trust_remote_code=True,且推荐用 load_state_dict 而非 from_pretrained。
评分
| 维度 | 评分 | 说明 |
|---|---|---|
| 教学价值 | ★★★★★ | 目前唯一公开的 DeepSeek-V4 迷你实现,500 行代码搞定了 MLA+MoE+HC+MTP |
| 代码质量 | ★★★★★ | HuggingFace 工程水准,结构清晰 |
| 实用性 | ★★☆☆☆ | 110M 模型生成质量很低,纯粹教育用途 |
| 文档诚实度 | ★★★★★ | 已知问题写得非常清楚,包括为什么 from_pretrained 会坏 |
| 独特价值 | ★★★★★ | 填补了"想学 DeepSeek-V4 但看不懂 50 页论文"的空缺 |
一句话总结
> HuggingFace 实习生从零复刻了 DeepSeek-V4 的全部架构创新(MLA / Hyper-Connections / MoE / MTP),压缩到 110M 参数跑通 —— 想学 DeepSeek-V4 架构的话,这是目前最好的入门代码。