从玩具到前沿:训练一个真正的大模型,到底难在哪里
你以为训练大模型就是把数据喂进模型、跑几天等它收敛?本文用 Marin 项目——一个从零训练 500B+ 参数 MoE 前沿模型的完整工程——告诉你,玩具模型和前沿模型之间的鸿沟,比你想象的要深得多。
一、你以为的"训练",只是冰山一角
你在笔记本上跑过一个 GPT-2 吗?下载数据集,定义模型,model.fit(),等它跑完,看看 loss 曲线下降,觉得"训练大模型也不过如此"。
但当你需要训练的不是一个 1.5 亿参数的玩具,而是一个 5000 亿参数、消耗 5×10²⁴ 次浮点运算 的混合专家模型时,你会发现:你之前做的那一切,只是整个冰山露出水面的那一小角。
冰山下面是什么?是 PB 级数据的清洗和去重,是跨三个大洲的异构集群调度,是训练中途发现损失爆炸后的紧急修复,是 80 个小规模实验筛选出的架构改进,是一套能外推 300 倍的缩放定律,是一套每秒写入数百万条记录的遥测系统,是发现评测数据被污染后的紧急去重……
这篇文章,就是要把这座冰山完整地向你展示。不是为了炫技,而是为了让你理解:前沿模型训练不是"更大的玩具模型",而是一个完全不同量级的工程挑战。它不是把你在 notebook 里做的事情放大 1000 倍——它在每一个维度上都面临着你在玩具训练中永远不会遇到的问题。

图1:玩具模型训练与前沿模型训练的全维度对比。左边是你在笔记本上做的事——一行代码加载数据、一张 GPU 跑训练、一个 loss 曲线看结果。右边是前沿模型训练需要面对的一切——PB 级数据流水线、跨大洲异构集群、MoE 架构、反应式训练过程、缩放定律、遥测系统、去污染……每一个都是独立的工程挑战。
二、数据:不是"下载一个数据集"那么简单
你练玩具模型时的数据
from datasets import load_dataset
dataset = load_dataset("wikitext", "wikitext-2-raw-v1")
一行代码,数据就来了。干净、整齐、直接用。最多再做个 tokenizer.encode(),分词完毕。整个数据处理环节不超过五行代码。
前沿模型的数据现实
前沿模型训练使用的数据量是 数万亿 token。这是什么概念?wikitext-2 大约 2MB,而前沿训练使用的数据量是它的 数十亿倍。这些数据来自 Common Crawl 的数百 TB 网页抓取、学术论文、代码仓库、书籍、维基百科、StackExchange 帖子、多语言语料……它们原始、混乱、充满噪声,来自几十个不同的源头,格式各不相同——有的是 HTML,有的是 PDF 提取的文本,有的是 JSON,有的是 Parquet,有的甚至是你从未见过的自定义二进制格式。
你不能手动清洗这些数据——数据量太大。你不能把它们全读到内存里——内存装不下。你不能随便处理——每一步都必须可复现,因为重跑一遍的成本可能是数万美元。
Marin 为此构建了一套名为 DataKit 的完整数据处理流水线。它不是一个脚本,不是一个 Jupyter notebook,而是一个多阶段、可缓存、可复现的工业级 pipeline,设计目标是在数千个 CPU 核心上并行处理 PB 级数据。
DataKit 的完整流水线
整个流水线有七个阶段,每个阶段都有严格的工程约束:
下载 → 标准化 → 嵌入 → 分类/过滤 → 去重 → 合并 → 分词
标准化阶段:所有原始数据被转换为 Parquet 列式格式,强制包含 id(确定性内容哈希)和 text(UTF-8 编码)两个核心列。id 不是随机生成的——它是内容哈希,意味着同样的内容永远产生同样的 ID,无论你在哪个地区、哪台机器上运行。为什么用 Parquet 而不是 CSV 或 JSON?因为 Parquet 支持列投影(只读你需要的列,不用加载整个文件)和过滤下推(在存储层就过滤掉不需要的行,不用把数据读到内存再过滤)。这对 PB 级数据来说不是优化,是生存条件——如果你把整个 PB 级文件读到内存再过滤,你的集群会直接 OOM。
嵌入阶段:用 Luxical 等嵌入模型为每篇文档生成向量表示。这些向量用于后续的质量分类和领域分配。嵌入计算本身就是一个大规模推理任务——你需要为数万亿 token 的文档逐一生成向量,这需要在数百个 CPU 上并行运行嵌入模型。
质量分类阶段:FastTransformer 质量打分模型对每篇文档评分。这不是"大概看看数据质量",而是对数万亿 token 中的每一篇都打分,低于阈值的直接丢弃。质量模型本身也是训练出来的——它需要在一个高质量的"正例"数据集和一个低质量的"负例"数据集上训练,学会区分有价值的文本和互联网垃圾。
去重阶段:这可能是整个流水线中最关键的阶段。去重分为两层:
- 全局精确去重:基于内容哈希。完全相同的文档(字节级别相同)被识别并只保留一份。这用 Zephyr 的
group_by全局 shuffle 实现——所有文档按哈希 shuffle 到不同分区,相同哈希的文档落在同一个分区,然后去重。 - 模糊去重:基于 MinHash。近似相同的文档(比如只是改了几个词、调了格式)被识别。先计算每篇文档的 MinHash 签名(一种将文档压缩成固定大小签名的技术,相似的文档有相似的签名),然后用阈值找相似签名对,再验证这些对是否真的相似。这一步能砍掉 10-30% 的重复数据。如果不去重,模型会在重复文本上过拟合——想象一下,如果训练集里同一篇文章出现了 50 次,模型会过度学习这篇文章,而不是学到通用的语言能力。
分词阶段:这是流水线的终点,也是"文档结构终结"的起点。Marin 使用 Llama 3 分词器(实验证明它在 bits-per-byte 指标上优于 Llama 2 和 NeoX),将文档拼接成固定长度的 token 序列,输出为 Levanter 缓存格式。从这里开始,"第几篇文章"的概念消失了,只剩下连续的 token 流。

图2:DataKit 数据处理流水线全景。从原始数据源到训练就绪的 token 缓存,七个阶段层层递进。标准化阶段统一格式为 Parquet,嵌入阶段生成文档向量,质量分类过滤低质量文本,两层去重消除精确和近似重复,最终分词为训练就绪的 token 流。
可复现性不是可选项
在玩具模型里,你重新跑一遍,结果差不多就行。但在前沿训练里,一个 pipeline 可能要跑几周,消耗数万美元的算力。如果因为某个路径变了导致缓存失效、整个 pipeline 重跑,那是不可接受的。
Marin 的 Artifact 系统 用内容哈希标识每个步骤的输入输出——只要输入不变,输出就从缓存读取,不重新计算。你改了一个下游步骤的代码,上游不需要重跑。跨地区复制数据时,相同的输入产生相同的输出路径。任何一步出了问题,只需要重跑那一步。
核心约束:步骤的 hash_attrs 必须包含它读取的每一个参数,且不能包含任何地区特定的路径。违反这条规则,同样的数据在不同地区会得到不同的输出路径,缓存就废了。

图3:Artifact 可复现性系统。每个步骤的输入输出由内容哈希标识,形成一条有向无环图(DAG)。改一个下游步骤(红色)只让该步骤及其下游失效(红色虚线),上游(蓝色)全部命中缓存。跨地区复制时,相同的哈希产生相同的路径,缓存自动命中。
三、集群调度:不是"开一台 GPU 机器"那么简单
你练玩具模型时的硬件
一张 RTX 3090,或者租一张 A100,跑就完了。如果不够,加一张。
前沿模型的硬件现实
Marin 同时运行在三种完全不同的硬件上,分布在多个大洲:
- Google TPU:v4-2048(2048 颗 TPU 芯片)、v5e-256、v5p-512,通过 Multislice 技术跨多个切片协调。TPU 拥有高带宽互联(ICI),适合大规模模型并行,但编程模型和 GPU 完全不同——你需要用 JAX 而不是 PyTorch,需要理解 XLA 编译器的行为。
- CoreWeave H100:8 卡 H100 节点,通过 Kubernetes 调度。H100 拥有 Transformer Engine 和 FP8 支持,但 8 卡之间的 NVLink 带宽有限,大规模训练需要多节点通信。
- CoreWeave GB200:4 卡 GB200 NVL72 节点,最新一代硬件。Grace CPU + Blackwell GPU 紧耦合,NVL72 机架级 NVLink 互联。
这三种硬件的编程模型、内存层次、互联拓扑完全不同。你不能写三套代码——你需要一套统一的调度系统,让同一份训练任务可以透明地跑在任何一种硬件上。
Iris:分布式作业编排
Iris 不是一个简单的"提交脚本到集群"的工具。它是一个完整的分布式作业编排系统。
控制器 每个 tick 执行三阶段循环:
- 调度:纯决策——哪个排队的任务应该被放到哪个 worker 上
- 协调:I/O 密集——观察任务状态、检测 worker 健康、处理失败
- 自动扩缩:资源管理——根据需求创建或销毁机器
TaskBackend 契约 是 Iris 最精妙的设计。控制器是"薄分发器",具体执行由后端实现:RpcTaskBackend(GCP/裸金属,控制器直接 RPC 通信 worker)或 K8sTaskProvider(Kubernetes,Kueue 调度,Iris 只观察 Pod 状态)。两者返回纯数据结果,不接触数据库,可互换。
代码分五层,每层只能导入下层:基础层(RPC/Actor)→ 集群词汇层(类型定义)→ 执行基底(机器生命周期 + 任务后端)→ 控制器(决策内核)→ 入口点(CLI/SDK)。

图4:Iris 五层架构。控制器是薄分发器,通过 TaskBackend 契约与两种后端(RPC 和 K8s)交互。每层只能导入下层模块,确保代码可维护性。控制器内部又细分为持久化脊柱、状态谓词、决策内核、命令外壳和传输循环五个子层。
跨集群联邦
Marin 的默认入口是 GCP TPU 集群,但大部分 GPU 训练跑在 CoreWeave 上。用户提交作业时不需要知道任务最终去哪里——Iris 的联邦路由自动决定。
路由分两步:提交时分类(检查设备类型、卡数、地区限制)和 调度时放置(匹配可用容量)。
精妙细节:容量不跨后端累加——8 卡作业不能拆成 4+4,因为所有卡需要在同一个高速互联域内通信。排队作业不占容量——避免死锁。预留账本防止超分配——心跳 30 秒一次,但 tick 更频繁,账本记录已分配容量防止同一空闲数字被反复使用。

图5:跨集群联邦路由。用户只向 GCP TPU 集群提交作业(入口),Iris 自动将 GPU 作业路由到 CoreWeave 的 H100 或 GB200 集群。预留账本在每个 tick 检查自上次心跳以来的分配量,防止超分配。
四、模型架构:不是"堆 Transformer 层"那么简单
你练玩具模型时的架构
一个标准的 Transformer,每一层都是同样的结构:注意力 + 前馈网络。所有参数对所有 token 都激活。简单、直接。
前沿模型的架构现实:混合专家(MoE)
Marin 旗舰模型 Grug 是 500B+ 参数的 MoE:384 个路由专家 + 2 个共享专家,每个 token 只激活 top-8。总参数大(容量高),但每 token 计算量小(只有 ~5B 活跃参数)。这意味着模型能学到的知识远多于同等计算量的稠密模型。
但 MoE 带来三个稠密模型不需要面对的问题:
专家负载均衡:如果路由不均匀——80% 的 token 都去了前 10 个专家——剩下的 370 个专家就浪费了。传统方案是加辅助负载均衡损失,但会干扰主训练。Marin 的方案是分位数平衡:维护一个全局直方图(10,000 个 bin),记录每个专家被选择的频率,为每个专家计算"偏差"——高频专家获得负偏差(降低被选概率),低频获得正偏差。偏差是"下一步生效、停止梯度"的,不影响当前梯度,只影响下一步路由。无需辅助损失。
跨设备通信:不同专家分布在不同设备上。token 被路由到设备 B 的专家,激活需要从设备 A 传到 B——这就是 All-to-All 通信。Marin 使用 fixed_pooled_wave_all_to_all 后端:发送方分三波、接收方每波运行所有本地专家。容量因子 1.15,超出容量的 token 被丢弃——这是设计权衡:丢弃一些 token 比让所有专家保持最大容量更高效。
共享专家:2 个共享专家,每个 token 都经过,学习通用表示。384 个路由专家学习特化表示。

图6:Grug MoE 架构。384 个路由专家 + 2 个共享专家,top-8 路由。分位数平衡通过全局直方图动态调整专家偏差,无需辅助损失。All-to-All 通信跨设备传输激活,容量因子 1.15,超出部分丢弃。
五、训练过程:不是"设好超参数然后等"那么简单
你练玩具模型时的训练流程
设好学习率、batch size、epoch 数,点"运行",等它跑完。loss 不下降就调学习率重来。
前沿模型的训练现实:“Tootsie Roll” 过程
Marin 8B 的训练被称为 "Tootsie Roll"过程——不断折叠新的数据、新的技术、新的发现进来。它不是预先计划好的,而是反应式的:根据不断变化的时间线、数据可用性和训练中的发现,实时调整。
五个阶段,每个以动物命名:
- Kestrel(0→2.7T tokens):DCLM 数据 + WSD-S 调度,2×v5e-256
- Ocelot(2.7T→3.78T):迁移到 v4-2048,batch +50%,切换到 WSD
- Jellyfish(3.78T→4.78T):DCLM 快用完,冷却阶段,折叠高质量数据
- Phoenix(4.78T→11.1T):发现还有时间,重新加热,切换到 Nemotron-CC
- Starling(11.1T→12.75T):时间又不够,第二次冷却
没有一个是预先计划的。 每个决策都是反应式的。这就是前沿训练和玩具训练的根本区别。

图7:"Tootsie Roll"反应式训练。五个阶段根据时间线和数据可用性实时调整——获得新硬件就迁移(Ocelot),数据用完就冷却(Jellyfish),发现更多时间就重新加热(Phoenix)。
32B 训练:损失爆炸与架构修复
32B 训练到 70k 步时损失剧烈震荡——不是正常波动,是架构层面的不稳定。
尝试修复:重启(失败)、换 Muon 优化器(失败),最终切换到 Qwen3 架构引入 QK-Norm(对 Query/Key 做 RMSNorm,防止深层网络中注意力 logits 爆炸)。8B(32 层)没问题,32B(64 层)就崩溃了。
教训:小规模有效的架构,大规模可能崩溃。

图8:32B 训练损失。Phase 1(Llama 架构)在 70k-80k 步出现损失爆炸,多种修复尝试失败后,Phase 3 切换到 Qwen3 架构(QK-Norm),损失立即稳定。
六、缩放定律:用小数预测大数
你练玩具模型时的缩放
模型大一点,数据多一点,效果好一点。线性思维。
Delphi:用小数预测大数
一个 10²³ FLOPs 的训练花费数百万美元。你不可能训练 10 个不同配置来比较。
Delphi 的做法:
- 等 FLOPs 扫描:78 个计算预算点(3×10¹⁸ 到 10²³),每个点训练多种大小的模型
- 二次拟合:每个预算点上用二次函数拟合损失 vs 模型大小,找最优
- 幂律拟合:D* ~ A × C^α
- 外推 300 倍:用 10²⁰ 以下的小实验预测 10²² 的最优配置
用几小时的小实验,准确预测数百万美元训练的最优配置。

图9:Delphi 缩放定律。78 个等 FLOPs 扫描点(彩色圆点),二次拟合找每个预算点的最优模型大小(星号),幂律拟合外推 300 倍。小实验的预测(虚线)与实际大规模训练结果(实线)高度吻合。
七、Agent MoE 实验筛选
你练玩具模型时的实验
改一个超参数,跑一次,看结果。
80 个小规模筛选
Marin 的 MoE 模型有无数改进方向。不可能直接在 500B 规模上尝试——太贵。
Agent MoE 实验筛选:在小规模 MoE(d512-d2048)上快速筛选。80 个实验结果:
- 有效 16 个:部分 RoPE+PKO(1.09-1.23x)、Pallas SConv(1.13-1.24x)、MuonH(1.19-1.33x)、注意力复用(1.025-1.121x)
- 无效 32 个:更细的专家(吞吐-30%)、冻结路由器、非线性路由器
- 混合 13 个、有前景 9 个
80% 的想法失败,但失败排除了错误方向。小规模筛选,大规模验证。

图10:Agent MoE 实验筛选漏斗。80 个实验在小规模 MoE 上快速筛选(d512-d2048),综合质量提升和吞吐量变化计算有效加速。16 个通过门槛进入大规模验证,32 个被明确排除。
八、遥测:Finelog
你练玩具模型时的监控
每个 epoch 打印 loss,画曲线图。
前沿模型的遥测现实
训练持续数周、消耗数百万美元时,你需要秒级可观测性:GPU 利用率、专家负载、通信开销、straggler 检测、内存泄漏、吞吐量退化……
Finelog:Rust 实现的日志服务器,不可变 Parquet 段存储,分段索引(三元组布隆过滤器加速 LIKE 查询、精确值倒排加速等值查询、值计数加速 GROUP BY、覆盖投影替代源 Parquet),命名空间隔离(iris.worker、iris.task、iris.profile),后台压缩合并小段。
设计原则:测量数据(Finelog)和决策数据(SQLite)分离,各自独立优化。

图11:Finelog 高性能遥测架构。Rust 原生扩展运行在 Python 进程内,写入不可变 Parquet 段。每个段拥有 .fidx 索引束(布隆过滤器、倒排索引、值计数、覆盖投影)。后台压缩器定期合并小段。命名空间隔离不同来源的数据。
九、数据去污染
你练玩具模型时的去污染
检查测试集是否在训练集里,字符串匹配,删掉。
前沿模型的去污染现实
32B 训练中发现 GSM8k 被污染——Dolmino 缓存包含评测数据 + LCG shuffle 保留局部结构。模型在 GSM8k 上分数异常高,但其他数学评测正常——污染不是显而易见的。
修复三管齐下:
- Feistel 置换:替换 LCG,消除局部模式
- 干净数据源:移除被污染的 Dolmino bundle
- Bloom 过滤器:对所有评测语料库构建 Bloom 过滤器,过滤训练集中的相似文档。假阳性可接受(误删干净文档),假阴性不可接受(漏掉污染文档)
数据污染不是"有没有"的问题,是"藏得有多深"的问题。

图12:三管齐下的去污染流程。Feistel 置换替代 LCG shuffle 消除局部结构,干净数据源替换被污染的 bundle,Bloom 过滤器对所有评测语料库做概率匹配。
十、冷却与中间训练
你练玩具模型时的流程
训练到收敛,结束。
前沿模型的阶段编排
前沿训练是多阶段编排:
- 预训练:大规模通用数据(Nemotron-CC、StarCoder),高学习率,广覆盖
- 冷却:降低学习率,切换到高质量数据(学术论文、代码、数学),"精炼"能力
- 中间训练:针对特定能力强化
冷却阶段使用 Z-Loss 正则化防止 logits 爆炸(深层网络的注意力 logits 可能变得极大),用 Feistel shuffle 确保数据充分打散。Marin 8B 经历了两次冷却(Jellyfish 和 Starling),32B 的 Mantis 冷却还修复了数据污染。

图13:预训练→冷却→中间训练的阶段转换。每个阶段有不同的学习率调度(左)、数据混合(中)和目标(右)。冷却阶段降低学习率、切换到高质量数据,Z-Loss 防止 logits 爆炸。
十一、Zephyr:分布式数据处理引擎
DataKit 的每个阶段需要数千 CPU 并行处理 PB 级数据。
Zephyr 提供类 Spark API,但针对 Marin 深度优化:
- 惰性执行:定义 pipeline(filter → map → group_by → write),先优化执行计划再运行
- Pull-based 任务分发:Worker 主动拉取任务,避免负载不均
- 文件系统背压:阶段间通过磁盘传递数据(PickleDiskChunk),每个 worker 一次处理一个 chunk,内存可控
- 操作融合:连续 filter+map 合并为一个任务,减少中间数据 I/O
- Hash 路由 shuffle:group_by 用 hash 路由将相同 key 发到同一分区,zstd 压缩存储
运行在 Fray 之上,自动检测是否在 Iris 作业中——是则用 Iris worker 池,否则用本地进程池。

图14:Zephyr 分布式数据处理引擎。惰性执行先优化计划,pull-based 分发避免负载不均,文件系统背压控制内存,操作融合减少 I/O。运行在 Fray 之上,自动适配 Iris 或本地环境。
十二、开放开发
传统的"开放"
发布模型权重,写篇论文,结束。
Marin 的开放开发
Marin 公开整个过程知识:
- 每次实验(成功和失败)记录在 GitHub issue
- 每个设计决策有设计文档,记录为什么选 A 不选 B
- 每次事故有事後分析,记录在 Echo 知识库
- 每个训练阶段有回顾文章,包括犯过的错误
- 每个架构改进经过 80 个筛选实验验证
你看到的不只是"我们做了什么",而是"我们尝试了什么、什么失败了、为什么失败、然后改成了什么"。 这种透明度在前沿模型训练中极其罕见。大多数团队只发布最终结果,隐藏所有失败和弯路。但正是这些失败和弯路,构成了最有价值的过程知识。

图15:Marin 的开放开发全流程。GitHub issue 记录每次实验(包括失败),设计文档记录每个决策,Echo 知识库记录每次事故,回顾文章记录训练过程中的错误和教训。所有过程知识对社区开放。
总结:鸿沟到底在哪里
前沿模型训练和玩具模型训练的鸿沟,不在于"模型更大"——你可以把 Transformer 堆到 100 层,但那只是更大的玩具。
鸿沟在于:前沿模型训练是一个完整的工业系统,在十个维度上同时面临挑战:
- PB 级数据的工业级处理(DataKit + Zephyr)
- 跨大洲异构集群的统一调度(Iris + 联邦路由)
- 数百亿参数 MoE 架构的工程实现(Grug + 专家并行 + 分位数平衡)
- 反应式的训练过程管理(Tootsie Roll + 冷却 + 中间训练)
- 用小数预测大数的缩放定律(Delphi + IsoFLOP 扫描)
- 大规模实验筛选方法论(Agent MoE + 80 个筛选实验)
- 秒级精度的训练可观测性(Finelog)
- 系统性的数据去污染(Bloom 过滤器 + Feistel 置换)
- 严格的可复现性保证(Artifact 系统 + 内容哈希)
- 完整的过程知识开放(开放开发)
这十个维度同时存在、相互耦合、必须一起解决。你在笔记本上跑 GPT-2 时,不需要考虑数据去重、集群调度、专家负载均衡、损失爆炸、缩放定律、数据污染、遥测系统、可复现性……但在前沿,每一个都是生死攸关的工程挑战。
这就是鸿沟。
本文基于 Marin 项目的公开代码和文档撰写。Marin 是一个开源的前沿模型训练平台,所有代码、设计文档、实验记录均公开可用。
附录 A:技术细节深入
A.1 MoE 专家并行的内存挑战
在 MoE 模型中,专家并行带来独特的内存挑战。每个 GPU 需要存储它负责的专家的参数、优化器状态和梯度。对于 384 个专家分布在 64 个设备上的配置,每个设备存储 6 个专家。但优化器状态(如 Adam 的一阶和二阶矩)需要额外的内存,通常是参数量的 2-3 倍。
Marin 的解决方案是优化器状态卸载:将优化器状态存储在 CPU pinned 内存中,只在需要时传输到 GPU。这减少了 GPU 内存压力,允许更大的模型或更大的 batch size。代价是增加了 CPU-GPU 之间的数据传输,但通过精心设计的传输调度,这个开销可以被隐藏。
A.2 分位数平衡的数学原理
分位数平衡的核心思想是维护每个专家被选择频率的经验分布,然后为每个专家计算一个偏差项,使得路由决策趋向均匀。
具体来说,维护一个全局直方图 H,记录每个专家在过去 N 个 batch 中被选择的次数。对于专家 i,其被选择的频率为 f_i = H[i] / sum(H)。然后计算偏差 b_i = -alpha * (f_i - 1/E),其中 E 是专家总数,alpha 是学习率。这个偏差被加到路由 logits 上,但使用 stop_gradient,意味着它不影响当前 batch 的梯度计算,只影响下一个 batch 的路由决策。
这种方法的优势是:它不引入额外的损失项,不会干扰主训练目标;它是自适应的,能够跟踪数据分布的变化;它是稳定的,因为偏差是逐步调整的。
A.3 All-to-All 通信的优化
在专家并行中,All-to-All 通信是主要的性能瓶颈之一。每个 token 需要从其源设备传输到目标设备(该 token 被路由到的专家所在的设备)。这是一个不规则的通信模式——不同设备之间传输的数据量不同,取决于路由决策。
Marin 的 fixed_pooled_wave_all_to_all 后端通过以下方式优化通信:
- 固定池:每个发送方为每个目标设备维护一个固定大小的发送池,避免动态内存分配
- 分波发送:将通信分成三波,每波传输一部分数据,允许计算和通信重叠
- 容量因子:每个专家有 1.15 倍的容量,超出部分的 token 被丢弃。这是一个设计权衡——丢弃一些 token 比让所有专家保持最大容量更高效,因为不均匀的路由意味着某些专家几乎不会达到容量上限
A.4 Artifact 系统的实现细节
Artifact 系统的核心是内容哈希。每个步骤的输入和输出都有一个由以下因素决定的哈希:
- 输入 artifact 的哈希(递归地)
- 步骤的代码(函数实现)
- 步骤的参数(配置、超参数)
- 依赖的版本(库版本、模型版本)
这个哈希用于生成输出路径。如果任何因素改变,哈希改变,输出路径改变,缓存失效。这确保了可复现性——相同的输入总是产生相同的输出。
但这也带来了挑战:如果依赖的版本改变(比如一个库升级了),即使代码和参数没变,缓存也会失效。为了解决这个问题,Marin 使用锁定文件(uv.lock)固定所有依赖的版本,确保跨运行的依赖一致性。
A.5 Finelog 的分段索引设计
Finelog 的分段索引(.fidx)是一个精心设计的元数据结构,用于加速查询。每个不可变的 Parquet 段文件都有一个对应的 .fidx 文件,包含多种索引方法:
- 三元组布隆过滤器(TrigramBloom):将文本分解为三元组(连续三个字符),为每个三元组构建布隆过滤器。查询
LIKE '%pattern%'时,先检查 pattern 的三元组是否可能存在于段中,快速过滤掉不可能的段。 - 精确值倒排索引(ExactPostings):为特定列的常见值构建倒排索引。查询
column = 'value'时,直接定位到包含该值的行,不需要扫描整个段。 - 值计数索引(ValueCounts):预计算特定列的值计数。查询
GROUP BY column COUNT(*)时,直接使用预计算的结果,不需要重新聚合。 - 覆盖投影(CoveringProjection):对于只引用特定列的查询,存储一个只包含这些列的投影文件。查询时直接读取投影文件,不需要读取完整的源 Parquet。
这些索引方法的设计原则是:索引是源的派生数据,不是独立的数据结构。如果源段改变,索引必须重新生成。这通过校验和保证——.fidx 文件包含源段的校验和,如果不匹配,索引被视为无效。
A.6 联邦路由的容量计算
联邦路由的一个关键挑战是准确计算每个对等集群的可用容量。容量不是简单的"空闲 GPU 数量"——它需要考虑多个因素:
- 已分配但未启动的作业:这些作业已经占用了容量,但还没有实际运行
- 排队中的作业:这些作业在等待容量,但不应该算作"已占用"
- 抢占式作业:这些作业可能被抢占,释放容量给更高优先级的作业
Marin 的解决方案是预留账本:每个 tick 记录自上次心跳以来分配了多少容量。有效容量 = 报告的可用容量 - 预留容量。当收到新的心跳(包含更新的可用容量)时,预留账本被重置。
这防止了超分配:如果没有预留账本,30 秒内(心跳间隔)的多个 tick 可能每次都分配出"报告的可用容量",导致总分配量远超实际容量。
A.7 冷却阶段的学习率调度
冷却阶段的学习率调度与预训练不同。预训练通常使用 warmup-stable-decay(WSD)调度:先 warmup 到高学习率,保持稳定,最后 decay。冷却阶段则直接从预训练的稳定学习率开始 decay。
Marin 使用线性 decay 或余弦 decay。线性 decay 简单直接:学习率从 lr_start 线性降到 lr_end(通常接近 0)。余弦 decay 更平滑:学习率按照余弦函数的形状下降,初期下降慢,中期下降快,后期下降慢。
冷却阶段的数据混合也不同于预训练。预训练使用广泛的通用数据(网页、代码、书籍),冷却阶段切换到高质量数据(学术论文、数学、代码、维基百科)。这是因为冷却的目标是"精炼"模型的能力——把预训练获得的原始知识转化为更精确、更有用的形式。
A.8 数据去污染的 Bloom 过滤器实现
Bloom 过滤器是一种空间高效的概率数据结构,用于测试一个元素是否在集合中。它可能有假阳性(元素不在集合中但返回 true),但不会有假阴性(元素在集合中但返回 false)。
Marin 为每个评测语料库构建一个 Bloom 过滤器:
- 将评测语料库的每个文档分词
- 为每个 token 序列计算多个哈希(通常 7 个)
- 将这些哈希映射到位数组的多个位置,设置为 1
去污染时:
- 将训练文档分词为 n-gram(连续 n 个 token)
- 为每个 n-gram 计算相同的多个哈希
- 检查位数组的对应位置是否都为 1
- 如果是,该 n-gram "可能"在评测语料库中(假阳性概率约 1%)
- 如果一个文档的多个 n-gram 都"可能"在评测语料库中,该文档被过滤
假阳性可接受(误删一些干净文档),假阴性不可接受(漏掉污染文档)。Bloom 过滤器的假阴性率为 0,满足这个要求。
附录 B:关键数字
- 模型参数:500B+(Grug MoE),384 路由专家 + 2 共享专家,top-8 路由
- 训练数据:数万亿 token,来自数十个数据源
- 计算量:5×10²⁴ FLOPs(旗舰模型)
- 硬件:TPU v4-2048、v5e-256、v5p-512;H100 8 卡节点;GB200 NVL72
- 集群:GCP(TPU + CPU)+ CoreWeave(H100 + GB200),跨三个地区
- 数据处理:PB 级,数千 CPU 核心并行
- 遥测:每秒数百万条记录,Rust 实现的 Finelog
- 实验筛选:80 个 Agent MoE 实验,16 个通过门槛
- 缩放定律:78 个 IsoFLOP 扫描点,外推 300 倍
- 代码规模:~57 万行 Python + ~4 万行 Rust + 78 个 Vue 组件
附录 C:进一步阅读
更多推荐

所有评论(0)