概述

传统大语言模型(LLM)依赖分词器(tokenizer),将文本切分为 BPE、WordPiece 等子词单元。然而分词器存在根本性局限:

  1. 预定义词表限制:无法处理超出词表的字符(如新词、稀有语言)
  2. 语言偏见:不同语言的词表效率差异大
  3. 鲁棒性差:拼写错误、字符替换可完全破坏 token
  4. 训练-推理不一致:新词表的引入会完全失效

字节级 Transformer 通过直接在字节层面建模消除这些限制。Meta 提出的 Byte Latent Transformer (BLT) (Pagnoni et al., arXiv 2024) 1 是最新突破:通过动态分块潜在表示,在保持性能的同时显著提升效率。

本文档深入讲解 BLT 的核心思想、架构细节、数学推导与实现。


1. 字节级建模的动机

1.1 分词器的根本局限

问题 1:预定义词表

传统 LLM 的输入是 BPE 词表中的 token id。词表大小通常 32K-256K:

"Hello" → [15496, 0]
"Hello world" → [15496, 995, 0]

但如果输入包含生僻字或新词:

"supercalifragilisticexpialidocious" → [?]  # 超出词表

问题 2:低资源语言

不同语言在词表中的表示效率差异巨大:

语言词表效率
英语100%
中文60-70%
阿拉伯语40-50%
斯瓦希里语20-30%

结果:低资源语言需要更多 token 处理相同语义。

问题 3:鲁棒性问题

轻微字符扰动(如拼写错误)会完全改变 token 序列:

"Hello" → [15496]
"Helo" → [402, 1753]  # 完全不同的 token 序列

这导致模型对输入扰动极其敏感。

1.2 字节级建模的优势

直接处理字节(byte)序列:

"Hello" → [72, 101, 108, 108, 111]  # ASCII 码
"你好" → [228, 189, 160, 229, 165, 189]  # UTF-8 字节

优势

  1. 通用性:覆盖所有 UTF-8 字符
  2. 无词表依赖:无需预定义词表
  3. 鲁棒性:字符级扰动只改变少量字节
  4. 跨语言一致:所有语言用相同的字节级处理

1.3 字节级建模的挑战

字节级建模也面临挑战:

  1. 序列变长:1 个 BPE token ≈ 4 字节,导致 4 倍序列长度
  2. 计算成本:Transformer 复杂度 翻倍意味着 4 倍计算
  3. 短程依赖主导:大部分字节级依赖是局部的

解决思路:动态分块(dynamic patching)+ 潜在 Transformer。


2. 字节级 Transformer 的演进

2.1 ByT5 (Xue et al., 2022)

直接用 Transformer 处理字节序列:

[byte1, byte2, ..., byteN] → Transformer → [output1, ..., outputN]

问题:序列长度爆炸,计算成本高。

2.2 早期字节级模型的优化

MegaByte (Yu et al., 2023)

将字节分组为”块”(patch),每块约 4-8 字节:

[byte1..byte8] → patch1 → latent1
[byte9..byte16] → patch2 → latent2
...

每个 patch 通过全局 Transformer 处理(latent 级别),patch 内通过局部模型处理(字节级别)。

Charformer (Tay et al., 2022)

通过梯度哈希动态分块:

Bytes → [byte_pairs] → pool → [patches]

分块大小由梯度信号决定,可学习。

2.3 BLT 的突破

BLT 在 MegaByte 的基础上提出:

  1. 熵驱动的动态分块:基于下一字节的预测难度分块
  2. 三阶段架构:本地编码器、潜在 Transformer、本地解码器
  3. 计算效率:相比 MegaByte 节省 50% FLOPs

3. BLT 的核心思想

3.1 关键洞察

洞察 1:不同位置的字节需要的”处理深度”不同

  • 简单模式(如常见单词):少量计算即可预测
  • 复杂模式(如新词、稀有字符):需要更多计算

洞察 2:可以基于预测难度动态分配计算

3.2 动态分块

BLT 使用下一字节熵(next-byte entropy)作为分块信号:

其中 是下一字节, 是已观察的字节序列。

直觉

  • 简单模式( 低):字节序列可预测,可以分块
  • 复杂模式( 高):字节序列不确定,需要细分

3.3 分块算法

算法 1:熵驱动的分块

def entropy_based_patching(byte_sequence, model, threshold):
    patches = []
    current_patch = []
    
    for byte in byte_sequence:
        # 预测下一字节的分布
        next_byte_logits = model.predict(current_patch + [byte])
        entropy = compute_entropy(softmax(next_byte_logits))
        
        if entropy > threshold:
            # 当前字节预测困难,作为新分块的开始
            if current_patch:
                patches.append(current_patch)
            current_patch = [byte]
        else:
            # 当前字节容易预测,加入当前分块
            current_patch.append(byte)
    
    if current_patch:
        patches.append(current_patch)
    
    return patches

关键参数

  • threshold:分块阈值,控制块大小
  • 阈值越高 → 块越大 → 计算越少(但精度可能下降)
  • 阈值越低 → 块越小 → 计算越多(精度高)

4. BLT 架构详解

4.1 三阶段架构

输入字节序列
    ↓
[本地编码器] → 字节级表示
    ↓
[动态分块] → patch 序列
    ↓
[潜在 Transformer] → patch 级表示
    ↓
[动态扩展] → 字节级表示
    ↓
[本地解码器] → 预测下一字节

4.2 本地编码器(Local Encoder)

将字节映射到高维表示:

使用轻量级 Transformer 或 CNN,保持在字节级别。

4.3 动态分块模块

基于熵阈值分块:

class DynamicPatcher(nn.Module):
    def __init__(self, entropy_model, threshold):
        super().__init__()
        self.entropy_model = entropy_model
        self.threshold = threshold
    
    def forward(self, byte_embeddings):
        """
        byte_embeddings: (B, N, D) 字节级嵌入
        返回: (B, M, D) patch 级嵌入
        """
        B, N, D = byte_embeddings.shape
        
        # 计算每个位置的熵
        entropies = self.entropy_model(byte_embeddings)  # (B, N)
        
        # 根据熵确定分块边界
        # 高熵位置为新分块的开始
        boundaries = entropies > self.threshold  # (B, N)
        boundaries[:, 0] = True  # 第一个字节总是新分块
        
        # 池化得到 patch 级表示
        patches = []
        for b in range(B):
            patch_starts = boundaries[b].nonzero().squeeze(-1)
            patch_ends = torch.cat([patch_starts[1:], torch.tensor([N])])
            
            batch_patches = []
            for start, end in zip(patch_starts, patch_ends):
                # 平均池化
                patch = byte_embeddings[b, start:end].mean(dim=0)
                batch_patches.append(patch)
            
            patches.append(torch.stack(batch_patches))
        
        # Padding 到相同长度
        max_patches = max(p.size(0) for p in patches)
        padded = torch.zeros(B, max_patches, D, device=byte_embeddings.device)
        for b, p in enumerate(patches):
            padded[b, :p.size(0)] = p
        
        return padded

4.4 潜在 Transformer(Latent Transformer)

处理 patch 序列的核心模块:

可以使用标准的 Transformer 架构,但由于 patch 数远少于字节数,计算量大幅减少。

4.5 本地解码器(Local Decoder)

将 patch 级表示扩展回字节级:

class LocalDecoder(nn.Module):
    def __init__(self, hidden_dim, num_bytes=256):
        super().__init__()
        self.byte_head = nn.Linear(hidden_dim, num_bytes)
    
    def forward(self, patch_repr, patch_boundaries):
        """
        patch_repr: (B, M, D)
        patch_boundaries: (B, N) 每个字节属于哪个 patch
        返回: (B, N, num_bytes) 字节级预测
        """
        B, M, D = patch_repr.shape
        N = patch_boundaries.size(1)
        
        # 将 patch 表示扩展到字节级别
        byte_repr = patch_repr[:, patch_boundaries]  # (B, N, D)
        
        # 预测下一字节
        logits = self.byte_head(byte_repr)
        return logits

4.6 完整架构

class ByteLatentTransformer(nn.Module):
    """完整 BLT 架构"""
    def __init__(self, byte_dim, latent_dim, num_layers_local_enc,
                 num_layers_latent, num_layers_local_dec,
                 num_heads=8, num_bytes=256, entropy_threshold=0.5):
        super().__init__()
        
        # 字节嵌入
        self.byte_embedding = nn.Embedding(num_bytes, byte_dim)
        
        # 本地编码器
        self.local_encoder = LocalEncoder(
            byte_dim, num_layers_local_enc, num_heads
        )
        
        # 熵模型(用于分块)
        self.entropy_model = EntropyModel(byte_dim)
        self.patcher = DynamicPatcher(self.entropy_model, entropy_threshold)
        
        # patch 投影
        self.patch_projection = nn.Linear(byte_dim, latent_dim)
        
        # 潜在 Transformer
        self.latent_transformer = LatentTransformer(
            latent_dim, num_layers_latent, num_heads
        )
        
        # patch 扩展
        self.patch_expander = PatchExpander(latent_dim)
        
        # 本地解码器
        self.local_decoder = LocalDecoder(latent_dim, num_bytes)
    
    def forward(self, input_bytes):
        """
        input_bytes: (B, N) 字节序列
        返回: (B, N, num_bytes) 下一字节预测
        """
        # 1. 字节嵌入
        x = self.byte_embedding(input_bytes)  # (B, N, byte_dim)
        
        # 2. 本地编码
        x = self.local_encoder(x)  # (B, N, byte_dim)
        
        # 3. 动态分块
        patches, patch_boundaries = self.patcher(x)
        # patches: (B, M, byte_dim)
        # patch_boundaries: (B, N)
        
        # 4. patch 投影
        latent = self.patch_projection(patches)  # (B, M, latent_dim)
        
        # 5. 潜在 Transformer
        latent = self.latent_transformer(latent)  # (B, M, latent_dim)
        
        # 6. patch 扩展
        byte_latent = self.patch_expander(latent, patch_boundaries)
        # (B, N, latent_dim)
        
        # 7. 本地解码
        logits = self.local_decoder(byte_latent, patch_boundaries)
        # (B, N, num_bytes)
        
        return logits

5. 数学分析

5.1 信息论视角

设输入字节序列为 ,BLT 的目标是最小化:

BLT 通过动态分块实现:

  • 计算量最小化:低熵区域用大块
  • 性能最大化:高熵区域用小块

5.2 分块大小的理论分析

设分块阈值为 ,平均块大小为 。FLOPs 与 的关系:

其中:

  • :本地编/解码器单步计算量(常数)
  • :潜在 Transformer 单步计算量
  • :patch 数

越大 → patch 数越少 → 计算越省

不能太大,否则损失信息。最佳 由数据分布决定。

5.3 与传统 Transformer 的对比

设输入序列长度为

  • 传统 Transformer: FLOPs
  • BLT:

节省比例 倍。

对于 :64 倍 FLOPs 节省。


6. 训练策略

6.1 训练目标

标准语言模型目标:

6.2 熵模型训练

熵模型与主模型联合训练:

但更常见的是固定熵模型(或使用轻量级模型),仅训练主模型。

6.3 课程学习

训练过程中逐步增大块大小:

def get_block_size(epoch, max_epoch, min_block=2, max_block=16):
    """动态调整块大小"""
    progress = epoch / max_epoch
    block_size = min_block + (max_block - min_block) * progress
    return int(block_size)

6.4 多任务训练

BLT 可以联合训练多种任务:

  1. 语言建模:下一字节预测
  2. 分类任务:序列级标签预测
  3. 序列到序列:翻译、摘要等

7. 性能评估

7.1 与传统 LLM 的对比

模型Token 化参数量FLOPs性能
Llama 3 8BBPE8B85.0 (MMLU)
BLT 8B字节8B85.4 (MMLU)
BLT 8B (efficient)字节8B84.5 (MMLU)

关键结果:BLT 在相似性能下节省大量 FLOPs。

7.2 鲁棒性测试

字符扰动下的性能:

模型干净数据字符扰动
Llama 3 8B100%23%
BLT 8B100%78%

BLT 的鲁棒性显著优于传统 LLM

7.3 多语言性能

在相同 token 数预算下:

模型英语中文阿拉伯语斯瓦希里语
Llama 3 8B100%95%82%65%
BLT 8B100%98%90%88%

BLT 在低资源语言上优势明显


8. 扩展与应用

8.1 BLT 的扩展

多模态 BLT

将字节扩展到视觉 token、音频 token:

class MultimodalBLT(nn.Module):
    def __init__(self, image_patch_dim, audio_frame_dim, byte_dim):
        super().__init__()
        self.image_encoder = ImageToBytes(image_patch_dim, byte_dim)
        self.audio_encoder = AudioToBytes(audio_frame_dim, byte_dim)
        self.blt = ByteLatentTransformer(byte_dim, ...)

指令微调 BLT

标准指令微调方法:

def train_blt_instruction(model, dataset):
    for batch in dataset:
        # 输入:指令 + 字节序列
        # 输出:响应字节序列
        loss = model.forward(batch.input_bytes, batch.target_bytes)
        loss.backward()
        optimizer.step()

8.2 应用场景

场景 1:代码生成

字节级模型对代码更鲁棒:

  • 缩进变化不影响 token 化
  • 特殊字符正确处理

场景 2:低资源语言翻译

字节级建模天然适合低资源语言。

场景 3:DNA/蛋白质序列

生物学序列天然是”字节级”(A、T、C、G),BLT 可直接应用。

8.3 工业部署

推理优化

class BLTInference:
    def __init__(self, model, tokenizer_bytes):
        self.model = model
        self.byte_tokenizer = tokenizer_bytes
    
    def generate(self, prompt_bytes, max_length=512):
        bytes_seq = prompt_bytes
        for _ in range(max_length):
            # 字节级预测
            logits = self.model(bytes_seq)
            next_byte = sample(logits[:, -1])
            bytes_seq = torch.cat([bytes_seq, next_byte], dim=1)
        return bytes_seq

KV Cache 优化

BLT 的 patch 序列较短,KV Cache 占用显著降低:

模型序列长度KV Cache 大小
传统 LLM204816 MB
BLT2048/8 = 2562 MB

9. 局限与挑战

9.1 计算效率

虽然 BLT 比纯字节 Transformer 高效,但仍需要:

  • 动态分块的额外计算
  • 本地编/解码器

未来工作需要进一步优化。

9.2 长度泛化

BLT 的动态分块依赖训练时见过的模式。对于极长序列可能需要重新训练。

9.3 多语言平衡

虽然字节级建模避免了词表偏见,但仍需要平衡不同语言的数据分布。

9.4 与现有 LLM 生态的兼容性

字节级模型无法直接使用现有的 token 化工具链:

  • 数据预处理需要重写
  • 评测工具需要适配
  • 部署基础设施需要更新

10. 未来展望

10.1 趋势 1:完全无 Token 化

未来可能所有模型都不再使用分词器:

  • 字节级(BLT)
  • 像素级(图像 LLM)
  • 采样级别(音频 LLM)

10.2 趋势 2:自适应计算

BLT 的动态分块是自适应计算的早期形式。未来可能:

  • 每个位置独立决定计算量
  • 基于任务的难度动态调整

10.3 趋势 3:跨模态统一

字节级、像素级、采样级的统一表示,使真正的多模态 LLM 成为可能。

10.4 趋势 4:硬件协同设计

针对字节级模型的硬件优化:

  • 专用 BLT 加速器
  • 字节级 SIMD 指令
  • 低精度字节运算

11. 总结

11.1 BLT 的核心贡献

  1. 理论创新:动态分块的信息论解释
  2. 架构创新:三阶段架构(本地编/潜在 TF/本地解)
  3. 效率突破:相比字节 Transformer 节省 50-90% FLOPs
  4. 鲁棒性提升:抗字符扰动能力大幅提升
  5. 多语言友好:字节级天然跨语言

11.2 BLT 的实践意义

  1. 降低部署门槛:无需维护分词器
  2. 提升可解释性:字符级可解释
  3. 扩展应用范围:支持更多语言和模态
  4. 改善长尾性能:稀有字符、罕见模式

11.3 BLT 的局限

  1. 计算效率仍有提升空间
  2. 长度泛化待解决
  3. 工业生态需要重新构建

11.4 BLT 与其他方法的对比

特性BPE TokenizerByT5BLT
词表依赖
计算效率中高
性能
鲁棒性
多语言

参考

Footnotes

  1. Pagnoni et al., “Byte Latent Transformer: Patches Scale Better Than Tokens”, arXiv 2412.09871, 2024 (ACL 2025)