Byte Latent Transformer:基于字节熵动态切分的无分词器大模型架构
在大模型训练和推理中,tokenizer(分词器)几乎是所有预训练语言模型默认的前置组件。它把文本切成语义单位,再映射成 id 序列。但 tokenizer 带来的词表管理、OOV(未登录词)、跨语言不均衡、错误传播等问题,也让越来越多研究者开始重新审视:能不能把分词器直接去掉?Byte Latent Transformer(简称 BLT)就是这条路线上的一个重要工作。它不再依赖任何分词器,而是把输入看作原始 UTF-8 字节流,并用字节熵动态切分 patch,再交给全局 Transformer 处理。
这篇文章会按“背景 -> 核心机制 -> 最小模拟 -> 训练推理 -> 对比分析 -> 面试排错 -> 最佳实践”的顺序展开。学完之后,你能说清楚 BLT 为什么能扔掉分词器、字节熵切 patch 的算法思路是什么、局部编码器和全局 Transformer 如何配合、以及面试中被问到“BLT 和 BPE 的区别”时该怎么回答。
1. 先理解:为什么大模型要扔掉分词器
1.1 分词器到底承担了什么工作
传统语言模型无法直接处理离散字符序列的原始分布,因此需要分词器把字符串转换成有限的 token 集合。最常用的是 BPE(Byte Pair Encoding)、WordPiece 和 Unigram。这些算法的共同点是:从大量语料中统计子词频率,把高频子词合并成词表中的一个 token,低频内容退化为更细粒度甚至单字节。
分词器的工作可以概括为三步:
- 预训练阶段基于语料学习词表。
- 运行时把文本切分为 token 序列。
- 将每个 token 映射成整数 id,供嵌入层使用。
这一套流程看起来成熟,但它把“文本如何切分”这个决策固化了下来。一旦模型完成训练,词表就固定了,所有输入都必须先走同一套切分规则。这带来了很多问题。
1.2 分词器的典型问题:OOV、错误传播、跨语言不均衡
第一个问题是 OOV。无论词表多大,都可能遇到没见过的单词、拼写错误、新造词、数字组合或特殊符号。传统处理方式是 [UNK] 或回退到子词,但这样会丢失信息。即使是 BPE 这类算法,也只能通过更细粒度的合并规则缓解,不能彻底消除。
第二个问题是错误传播。分词是不可微的离散操作。如果分词阶段对文本切错了,后续模型无法感知。比如“深度/学习”和“深度学/习”可能产生完全不同语义,而 tokenizer 只给它一条路径。
第三个问题是跨语言不均衡。中英文混合文本中,英文单词很容易被拆成常见子词,而中文汉字本身是单音节,BPE 通常会把常用汉字合并成词。不同语言在 token 化后的序列长度差异很大。英文“hello world”可能切出 2 个 token,中文“你好世界”可能是 2 到 4 个 token。这种不均衡会导致模型对某些语言更“费力”,因为同样的语义需要更多 token 参与计算。
分词器还有一个隐藏成本:词表管理。词表太大,嵌入层和输出层的参数量会膨胀;词表太小,表示能力不足。词表需要按版本维护,在线服务中还会遇到新词注册、负载均衡、缓存命中率等问题。
1.3 字节级输入是一条新出路
更底层地看,所有文本最终都能表示为 UTF-8 字节序列。一个 Unicode 字符在 UTF-8 中占 1 到 4 字节。英文 ASCII 字符占 1 字节,中文汉字通常占 3 字节,极端字符占 4 字节。如果模型直接操作字节,理论上可以用一个固定大小为 256 的 token 表覆盖所有文本。这就是字节级模型的出发点。
早期字节级模型的问题在于序列长度过长。一个中文句子如果按字符计数可能只有 10 个 token,按字节计数会变成 30 个 token,Transformer 的自注意力复杂度是平方级,序列变长会显著增加计算量。BLT 的解决方案是:不把所有字节都直接送进全局 Transformer,而是先按信息量动态合并成 patch,再用 patch 作为 Transformer 的基本处理单元。这样既保持了字节级的灵活性和通用性,又避免了过长的序列开销。
2. Byte Latent Transformer 的核心机制
2.1 从字节序列到 patch:动态分组的含义
BLT 的基本输入是字节序列。假设文本是 "cat",UTF-8 编码后是三个字节 [0x63, 0x61, 0x74]。传统 tokenizer 可能会把 "cat" 当作一个 token;BLT 则会根据局部字节熵,把这一串字节动态划分成若干个 patch。
“patch”可以理解为一个动态的字节块。每个 patch 包含的字节数不是固定的 2 或 4,而是取决于这一段的“可预测程度”。如果一段字节很容易预测,比如空格、常见标点、重复字符,就可以合并成一个较长的 patch;如果一段字节信息量很大、难以预测,比如罕见词、随机噪声、中文字符,就切成较短 patch,让全局 Transformer 对这一段投入更多注意力。
这种动态分组方式让 BLT 在字节级输入和计算效率之间取得了平衡。低熵区域用少量 patch 表示,高熵区域用更多 patch 表示,整体计算量可以大幅下降。
2.2 字节熵是什么,为什么用它决定分组
信息论中的熵用来衡量一个随机变量的不确定程度。在处理字节序列时,某个位置的“字节熵”指的是:给定前面的字节,下一个字节有多难预测。
可以用一个滑动窗口近似:取当前位置前后的字节,统计出现频率,计算信息熵。熵高,说明字节分布接近均匀,候选可能性多;熵低,说明下一个字节几乎确定,例如连续空格后大概率还是空格。
BLT 利用这个特性设计切分策略:
- 高熵区域:下一次取值不确定,需要更多模型表达能力,所以 patch 切得短,让每个字节都有机会被全局注意力仔细处理。
- 低熵区域:下一次取值很确定,可以批量合并,减少全局 Transformer 的计算量。
更容易理解的例子是普通英文文本。英文单词内部字母组合有强规律,比如 "tion" 中 t 出现后,后面的字母相对可预测,所以字节熵较低;而单词边界、罕见拼写、混合脚本处,字节熵会突然升高。BLT 会在这些高熵边界处断开 patch,而不是机械地每隔固定字节切一刀。
2.3 BLT 的双层架构:局部编码器、全局 Transformer、局部解码器
BLT 整体分为三个部分。
第一部分是局部编码器。它接收原始字节流,把每个字节映射成低维向量,并按照 patch 边界把字节向量聚合成一个 patch 向量。这个编码器通常是一个浅层模型,参数量很小,只负责局部特征提取。
第二部分是全局 Transformer。它的输入是 patch 向量序列,使用因果注意力继续建模。和传统 tokenizer-based Transformer 相比,这里的 token 不再是固定词表 id,而是动态 patch 的表示。全局 Transformer 负责处理长程依赖、语义推理和知识存储。
第三部分是局部解码器。全局 Transformer 预测下一个 patch 的语义表示后,局部解码器负责把这个 patch 表示展开成具体的字节序列。展开过程也是自回归的:预测一个字节,再把已经生成的字节作为上下文,继续生成下一个字节,直到 patch 结束。
可以这样理解:全局 Transformer 像是一个“高层决策者”,只处理信息密度高的 patch;局部编码器和解码器像是“执行团队”,负责把字节整理成 patch,以及把 patch 细化为字节。
2.4 基于熵的 patch 切分算法
论文中的 patch 切分有两种常见思路:一是启发式统计熵,二是学习一个熵预测器。
启发式统计熵的伪代码类似这样:
学习式熵预测器则是用一个小型模型,根据前面已经生成的字节预测未来一个窗口内的字节熵。这样切分决策不再是独立的局部统计,而是能结合更多历史信息。训练时,预测器会和整个 BLT 一起端到端优化;推理时,每生成一段字节就预测一次后续熵,并在高熵点断开 patch。
两种方式各有取舍。启发式简单、可解释、没有额外训练成本,但阈值需要人工调整;学习式更灵活,能适配不同语言分布,但增加了一个需要训练的组件。
3. 用 Python 模拟字节熵动态切分
3.1 需要准备的环境
这一节不讨论训练一个完整 BLT 大模型,因为那需要大规模分布式算力。这里用一段可运行的 Python 代码,演示“计算字节熵 -> 动态切 patch”的核心思想。
你需要准备:
- Python 3.8 或更高版本。
- 标准库
math和collections,不需要额外安装。
如果你希望跑更大规模实验,可以安装 PyTorch 和 HuggingFace Transformers,但后面的最小模拟不依赖它们。
3.2 完整示例代码
先写一个函数,计算一段字节序列的熵:
再写一个基于滑动窗口的切分函数:
然后准备一个中英文混合文本:
运行后,你可能会看到类似输出:
这里 patch 数量会随窗口大小和阈值变化。窗口越大,熵估计越稳定,但边界越难定位;阈值越高,切分越粗,patch 越少。
3.3 关键代码讲解
compute_entropy 使用字符出现频率估计信息熵。频率分布越均匀,熵越大;分布越集中,熵越小。例如 "aaaa" 的熵为 0,因为只有一个符号;"abcd" 的熵为 2,因为 4 个符号等概率出现。
split_patches_by_entropy 中的判断条件有三层含义:
ent >= threshold:窗口内字节不确定度高于阈值,这里可能是新词开始、中英混合边界或特殊符号。i - start + 1 >= min_patch_len:防止 patch 过小。i + 1 < len(byte_seq):保证最后一个字节不被误切。
这个实现是一个简化版,没有做边界平滑。真实 BLT 切分时会更复杂,比如对窗口做 padding、用多个窗口求平均、设置最大和最小长度等。
3.4 如何验证模拟结果
验证模拟代码是否合理,可以用三组测试:
- 全英文重复文本:例如
"hello " * 20,熵很低,patch 应该很大。 - 随机中英混合:熵很高,patch 应该很多。
- 纯中文文本:UTF-8 中一个汉字 3 字节,高频汉字之间有规律,但整体字节分布相对均匀,熵值会处于中等水平。
通过调整 threshold,你可以直观看到 patch 数量变化。这个实验虽然不能复现 BLT 完整训练,但它能帮助你建立直觉:为什么高熵区域需要更多 patch 表示,为什么低熵区域可以大幅压缩。
注意:真实 BLT 切 patch 不保证边界是完整字符边界,字节序列切在字符中间是正常现象。局部编码器就是把字节向量化,并不要求它一定构成可阅读的 UTF-8 字符。
4. 训练与推理:BLT 的工作流程
4.1 patch 的编码与解码流程
训练阶段,输入文本先转成字节序列,然后按熵切分为 patch。对每个 patch:
- 局部编码器读入 patch 内所有字节向量。
- 编码器通过轻量级注意力或卷积结构,把字节信息聚合成一个 patch 向量。
- 这个 patch 向量成为全局 Transformer 的一个输入 token。
全局 Transformer 输出下一个 patch 的预测表示后,局部解码器开始生成字节。局部解码器输入当前 patch 的预测表示和已经生成的字节,逐步生成剩余字节。生成过程中,每个字节都会和真实字节计算交叉熵损失。
4.2 全局 Transformer 的输入和输出
全局 Transformer 看到的序列长度由 patch 数量决定,而不是字节数量。例如一段 1000 字节的文本,如果平均每个 patch 10 字节,全局序列长度是 100;如果平均每个 patch 5 字节,全局序列长度是 200。全局序列越短,自注意力计算量越低。
全局 Transformer 输出的每个位置是一个 patch 级别的向量。它要预测的是“下一个 patch 的语义”,而不是直接预测下一个 token id。这和传统语言模型的输出维度设计不同。传统模型输出层映射到词表大小,BLT 则需要把 patch 向量传给局部解码器。
4.3 损失计算与优化
训练 BLT 时,整体损失通常包含两部分:
- 全局 Transformer 对下一个 patch 表示的预测损失。这部分让全局 Transformer 学会高层语义。
- 局部解码器在 patch 内部预测字节的交叉熵损失。这部分让模型学会把 patch 表示展开为具体字节。
如果使用了学习式熵预测器,还需要加入熵预测损失,让预测器学会判断后续字节的信息量。这样切分边界也能随训练动态调整。
由于 BLT 的所有组件都是可微的,只有“边界切分”这一步是离散操作。实际训练中,常见的做法是让熵预测器输出连续的熵估计,再通过阈值或采样确定边界,这样梯度可以通过预测器传递到模型。
4.4 推理时的熵阈值选择
推理时的性能分两个阶段:
- 预填充阶段:输入 Prompt 转成字节序列,切 patch,一次性交给全局 Transformer。
- 生成阶段:每生成一个 patch,再根据生成结果预测下一个 patch 的边界,继续生成。
阈值直接影响效率和生成质量:
- 阈值低:patch 更短,全局序列更长,计算量更大,但模型对细节处理更细。
- 阈值高:patch 更长,计算量更小,但可能在罕见词上失去精度。
实际部署时,阈值可以作为超参数调整。有些服务对延迟敏感,可以适当提高阈值,让每个 patch 更长;对质量敏感的生成任务,可以降低阈值,多给全局 Transformer 一些 token。
5. BLT 与分词器方案的对比
5.1 从 token 数量、计算量、鲁棒性角度看
传统 BPE 模型的序列长度由 token 数决定。BLT 的全局序列长度由 patch 数决定。两者不能直接比较,因为 patch 是动态的。但在低熵文本上,BLT 可以明显压缩序列长度;在高熵文本上,它会增加 patch 数,计算量与信息量成正比。
鲁棒性方面,BLT 的优势在于没有固定词表。任意拼写、emoji、代码符号、生僻汉字都能表示。传统 BPE 模型遇到词表外的内容时,需要依赖 fallback 规则;BLT 只需要把新文本编码成 UTF-8 字节。
5.2 对比表
| 维度 | BPE/WordPiece 模型 | Byte Latent Transformer |
|---|---|---|
| 前置组件 | 需要分词器,有固定词表 | 无分词器,直接处理字节流 |
| 序列单位 | 固定 token | 动态 patch |
| 边界决定 | 语料统计频率 | 局部字节熵 |
| 多语言混合 | 不同语言 token 化差异大 | 字节级统一处理 |
| OOV 问题 | 存在,需 fallback | 无固定词表,几乎不存在 OOV |
| 计算效率 | 与 token 数量相关 | 与 patch 数量相关,低熵区域压缩明显 |
| 实现复杂度 | 成熟、工具多 | 仍在演进,工程实现更复杂 |
5.3 典型应用场景和适用边界
BLT 适合处理多语言、代码、噪声文本、罕见词密集的场景。搜索引擎里的查询词、日志文本、OCR 输出、语音识别转写结果,经常包含拼写错误和非常规字符,这类场景传统 tokenizer 很容易把错误信息抹掉。BLT 能保留更多字节级细节。
不过 BLT 也有适用边界。如果任务本身是标准英文书面语,语料分布稳定,BPE 模型已经非常成熟,可以直接复用大量预训练权重和推理框架。此时上 BLT 收益不大,成本和工程改动却不小。选型时应该根据数据特点和部署条件决定,而不是追求架构新。
6. 面试高频问题与排查思路
6.1 面试官常问的 6 个问题
面试中关于 BLT 的问题通常围绕“为什么去掉分词器”“熵如何计算”“patch 怎么切”“和 BPE 比有什么优势”“计算量到底省在哪”“实现时有哪些难点”展开。
可以按以下思路回答:
- 为什么去掉分词器?分词器导致 OOV、跨语言不均衡、错误传播;字节级输入通用性更强。
- 什么是字节熵?给定上下文,下一个字节的不确定程度;用滑动窗口可以近似估计。
- 为什么高熵区域 patch 短?高熵表示难预测,需要更多模型注意力。
- patch 边界怎么确定?启发式阈值或学习式熵预测器,再做后处理。
- 计算量省在哪?低熵区域被压缩成少数 patch,全局 Transformer 序列长度缩短。
- 生成时怎么工作?全局 Transformer 预测 patch 表示,局部解码器展开为字节。
6.2 实现 BLT 时容易踩的坑
| 坑点 | 现象 | 原因 | 解决方式 |
|---|---|---|---|
| 字节窗口切割在 UTF-8 字符中间 | 解码测试时出现乱码 | 字节流没有按字符边界对齐 | 真实模型不要求字符边界,打印调试时用 errors=replace |
| patch 过小 | patch 数量爆炸 | 熵阈值过低或最小长度限制缺失 | 设置 min_patch_len,并对候选边界做合并 |
| patch 过大 | 高信息量区域被合并 | 阈值过高,边界定位延迟 | 降低阈值,或使用多个窗口熵平均 |
| 边界抖动 | 相同句子在不同上下文切分不一致 | 熵对上下文敏感 | 训练熵预测器,或对熵序列做平滑 |
| 损失不下降 | 局部解码器无法生成字节 | 全局 patch 表示信息不足 | 增大局部编码器容量或缩短 patch 平均长度 |
| 训练吞吐低 | 全局 Transformer 利用率不高 | patch 长度不均匀,batch 内 padding 多 | 动态 batch 或限制 patch 最大长度 |
6.3 排查链路:从熵计算到 patch 边界再到训练 loss
排查 BLT 问题时,建议按以下顺序:
- 先检查输入字节序列是否正确。用
text.encode("utf-8")打印前几十字节,确认没有编码问题。 - 检查熵计算函数。用一个已知文本测试,比如
"aaaa"熵应为 0,"abcd"熵约为 2。 - 检查边界条件。打印每个候选边界的位置、窗口熵、patch 长度,确认切分是否符合直觉。
- 检查训练 loss。如果字节交叉熵不下降,优先看局部解码器输出;如果 patch 表示预测 loss 不下降,再看全局 Transformer 输入是否稳定。
- 检查推理生成。生成一段文本,看是否出现重复、崩溃或无意义字节,根据输出调整熵阈值和最大 patch 长度。
这套排查思路同样适用于其他动态序列切分模型。本质是先验证数据流,再验证算法,最后验证模型。
注意:训练 BLT 这类模型需要处理动态长度序列。PyTorch 的
pack_padded_sequence和注意力 mask 都要配套调整,不能直接把变长 patch 拼成固定 tensor 后忽略 mask。
7. 最佳实践与扩展方向
7.1 学习环境和生产环境的差异
学习环境里,用一段模拟代码理解熵切分就足够了。你可以改窗口大小、阈值、输入文本,观察 patch 数量变化。生产环境则要额外考虑:
- 服务端需要高效的字节编码库,避免 Python 层逐字节处理。
- 推理框架要支持变长序列和动态 mask,不能盲目静态 padding。
- 熵预测器需要和主模型一起部署,增加了一点推理开销。
- 日志和监控要记录平均 patch 长度、边界分布,方便定位质量问题。
- 上线前要准备回滚方案,因为 BLT 是较新架构,工具生态不如 BPE 成熟。
7.2 可复用实验清单
如果你想在自己的数据集上验证 BLT 思想,可以先跑以下清单:
- 准备一份多语言混合文本,统计字节分布和字符比例。
- 实现滑动窗口熵计算,并绘制字节位置与熵值曲线。
- 设置不同阈值,观察 patch 数量、平均 patch 长度、最大最小长度。
- 挑选几个长文本样本,打印 patch 边界和对应文本片段。
- 对比固定长度切分和熵动态切分在 patch 数量上的差异。
- 如果有训练资源,实现一个小规模 BLT 模型,用 1 亿参数以内验证可行性。
- 记录训练 loss、验证集困惑度,和相同规模 BPE 模型对比。
7.3 后续可以深入的方向
BLT 后面的方向可以从三个角度扩展:
一是改进熵预测器。目前启发式阈值简单,但不够灵活;学习式预测器可以结合语义信息,让边界更合理。
二是把 BLT 和现代推理优化结合。比如使用推测解码(speculative decoding)、连续批处理(continuous batching)、PagedAttention 等,探索动态 patch 下如何提高 GPU 利用率。
三是多模态扩展。既然 BLT 可以处理任意字节,那么图像、音频、代码、文本都可以统一编码为字节流。让同一个 patch 切分机制覆盖多模态输入,研究空间很大。
在实际项目中,不建议一上来就替换主流 tokenizer。更好的做法是先在小规模任务上复现 BLT 的熵切分实验,验证收益,再逐步扩大到更大模型。真正理解“为什么按熵切”比“用什么模型实现”更重要,这也是面试官最想看到的能力。