工业化

大型语言模型正在快速工业化。

More is different

更多参数,表现就会不同

这说明:小模型的实验结果未必能直接推广到大模型,因为规模不仅影响性能,还会带来新的能力与计算结构分配的变化。

content-image-1

图1

content-image-1

图2

What can we learn in this class that transfers

 to frontier models?

在这门课中,我们能学到三类可迁移到前沿模型的知识:

  1. 机制(Mechanics):了解技术原理,如 Transformer 的工作方式、模型并行如何利用 GPU 等。
  2. 思维方式(Mindset):充分利用硬件、认真对待规模效应(如缩放定律)。
  3. 直觉(Intuitions):哪些数据和建模决策能带来更高的准确率。

课程可以完整教授机制思维方式(可直接迁移),但直觉只能部分传授,因为它并不总是能跨规模迁移。很多设计决策目前还无法通过理论完全证明,只能依赖实验探索——例如 Noam Shazeer 在 2020 年提出的 SwiGLU 激活函数就是这样得出的。

直觉?

The bitter lesson

当前状况

效率驱动设计决策

content-image-1

Tokenizer

分词(Tokenization)

无分词器方法(Tokenizer-free approaches)**[Xue+ 2021][Yu+ 2023][Pagnoni+ 2024][Deiseroth+ 2024]**

架构(Architecture)

模型架构

起点:原始Transformer模型。

content-image-1

主要变体

训练(Training)

Kernels

GPU (A100)

content-image-1

类比

仓库(warehouse) 相当于DRAM(动态随机存取存储器),而工厂(factory) 相当于SRAM(静态随机存取存储器)。

content-image-1

技巧

Parallelism

核心原则:即使在多GPU之间,数据移动也更慢,因此最小化数据移动的原则依然成立。

content-image-1

主要方法

Inference

目标:根据给定的提示(prompt)生成标记(tokens),这是实际使用模型的关键环节。

content-image-1

应用场景:除了生成内容,推理还用于强化学习、测试计算和模型评估。

全局成本:推理计算(每次使用)的总量超过训练计算(一次性成本)。

两个阶段

  1. 预填充(Prefill):处理输入的提示文本,可以一次性处理所有标记,此阶段受计算限制。
  2. 解码(Decode):一次生成一个标记,此阶段受内存限制。

加速解码的方法

Scaling laws

主要用于在大规模训练前,从小规模实验中预测最优的超参数和损失。

content-image-1

Data

content-image-1

Evaluation

Alignment

对齐的两个阶段

  1. 有监督微调(Supervised finetuning):通过使用人工标注的数据对模型进行微调。
  2. 从反馈中学习(Learning from feedback):通常指通过人类反馈强化学习(RLHF)等方法,进一步优化模型行为。

Supervised finetuning (SFT)

python
sft_data: list[ChatExample] = [
        ChatExample(
            turns=[
                Turn(role="system", content="You are a helpful assistant."),
                Turn(role="user", content="What is 1 + 1?"),
                Turn(role="assistant", content="The answer is 2."),
            ],
        ),
    ]

Preference data

python
preference_data: list[PreferenceExample] = [
        PreferenceExample(
            history=[
                Turn(role="system", content="You are a helpful assistant."),
                Turn(role="user", content="What is the best way to train a language model?"),
            ],
            response_a="You should use a large dataset and train for a long time.",
            response_b="You should use a small dataset and train for a short time.",
            chosen="a",
        )
    ]

数据生成方式

数据示例:一个偏好数据示例可能包含:

Algorithm

Tokenization

GPT-2 Tokenizer Regex

python
GPT2_TOKENIZER_REGEX = r"""'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
 

Tokenization 总览


BPETokenizer 参数

python
from dataclasses import dataclass
 
@dataclass(frozen=True)
class BPETokenizerParams:
    """BPETokenizer 所需的全部参数"""
    vocab: dict[int, bytes]                 # index -> bytes
    merges: dict[tuple[int, int], int]      # index1, index2 -> new_index
 

各类 Tokenizer 实现

Character Tokenizer

python
class CharacterTokenizer(Tokenizer):
    """将字符串表示为 Unicode code points 序列"""
 
    def encode(self, string: str) -> list[int]:
        return list(map(ord, string))
 
    def decode(self, indices: list[int]) -> str:
        return "".join(map(chr, indices))
 

Byte Tokenizer

python
class ByteTokenizer(Tokenizer):
    """将字符串表示为字节序列"""
 
    def encode(self, string: str) -> list[int]:
        string_bytes = string.encode("utf-8")
        indices = list(map(int, string_bytes))
        return indices
 
    def decode(self, indices: list[int]) -> str:
        string_bytes = bytes(indices)
        string = string_bytes.decode("utf-8")
        return string
 

BPE 合并函数

python
def merge(indices: list[int], pair: tuple[int, int], new_index: int) -> list[int]:
    """返回合并后的 indices,将所有 pair 替换为 new_index"""
    new_indices = []
    i = 0
    while i < len(indices):
        if i + 1 < len(indices) and indices[i] == pair[0] and indices[i + 1] == pair[1]:
            new_indices.append(new_index)
            i += 2
        else:
            new_indices.append(indices[i])
            i += 1
    return new_indices
 

BPE Tokenizer

python
class BPETokenizer(Tokenizer):
    """基于 merges 和 vocab 的 BPE tokenizer"""
 
    def __init__(self, params: BPETokenizerParams):
        self.params = params
 
    def encode(self, string: str) -> list[int]:
        indices = list(map(int, string.encode("utf-8")))
        # 注意:实现非常慢
        for pair, new_index in self.params.merges.items():
            indices = merge(indices, pair, new_index)
        return indices
 
    def decode(self, indices: list[int]) -> str:
        bytes_list = list(map(self.params.vocab.get, indices))
        string = b"".join(bytes_list).decode("utf-8")
        return string
 

压缩率计算

python
def get_compression_ratio(string: str, indices: list[int]) -> float:
    """计算 tokenization 后的压缩率"""
    num_bytes = len(bytes(string, encoding="utf-8"))
    num_tokens = len(indices)
    return num_bytes / num_tokens
 

获取 GPT-2 Tokenizer

python
def get_gpt2_tokenizer():
    # 使用 tiktoken
    # 对应 GPT-3.5-turbo 或 GPT-4 可使用 cl100k_base
    return tiktoken.get_encoding("gpt2")
 

Tokenization 示例

原始文本通常表示为 Unicode 字符串

语言模型对 token 序列(整数索引)建立概率分布

Tokenizer 负责将字符串编码为 token,并能解码回字符串

Vocabulary size = 可用 token 数量

示例代码

python
string = "Hello, 🌍! 你好!"
tokenizer = get_gpt2_tokenizer()
 
# 编码 & 解码检查
indices = tokenizer.encode(string)
reconstructed_string = tokenizer.decode(indices)
assert string == reconstructed_string
 
# 压缩率
compression_ratio = get_compression_ratio(string, indices)
 

Observations

Tokenization 方法详解

Character-based Tokenizer

python
assert ord("a") == 97
assert ord("🌍") == 127757
assert chr(97) == "a"
assert chr(127757) == "🌍"
 

示例

python
tokenizer = CharacterTokenizer()
string = "Hello, 🌍! 你好!"
indices = tokenizer.encode(string)
reconstructed_string = tokenizer.decode(indices)
assert string == reconstructed_string
 

特点与问题

python
compression_ratio = get_compression_ratio(string, indices)
 

Byte-based Tokenizer

python
# 单字节字符
assert bytes("a", encoding="utf-8") == b"a"
# 多字节字符
assert bytes("🌍", encoding="utf-8") == b"\xf0\x9f\x8c\x8d"
 

示例

python
tokenizer = ByteTokenizer()
string = "Hello, 🌍! 你好!"
indices = tokenizer.encode(string)
reconstructed_string = tokenizer.decode(indices)
assert string == reconstructed_string
 

特点

python
vocabulary_size = 256
compression_ratio = get_compression_ratio(string, indices)
 

Word-based Tokenizer

python
import regex
string = "I'll say supercalifragilisticexpialidocious!"
segments = regex.findall(r"\w+|.", string)  # 保留单词和标点
 
python
pattern = GPT2_TOKENIZER_REGEX
segments = regex.findall(pattern, string)
 

构建 Tokenizer

问题

python
vocabulary_size = "Number of distinct segments in the training data"
compression_ratio = get_compression_ratio(string, segments)
 

BPE Tokenizer (Byte Pair Encoding)

基本思想


训练 BPE Tokenizer

python
def train_bpe(string: str, num_merges: int) -> BPETokenizerParams:
    indices = list(map(int, string.encode("utf-8")))
    merges: dict[tuple[int, int], int] = {}
    vocab: dict[int, bytes] = {x: bytes([x]) for x in range(256)}
 
    for i in range(num_merges):
        # 统计相邻 token 对出现次数
        counts = defaultdict(int)
        for index1, index2 in zip(indices, indices[1:]):
            counts[(index1, index2)] += 1
 
        # 找出最常见的 pair
        pair = max(counts, key=counts.get)
        index1, index2 = pair
 
        # 合并 pair
        new_index = 256 + i
        merges[pair] = new_index
        vocab[new_index] = vocab[index1] + vocab[index2]
        indices = merge(indices, pair, new_index)
 
    return BPETokenizerParams(vocab=vocab, merges=merges)
 

使用 BPE Tokenizer

python
params = train_bpe("the cat in the hat", num_merges=3)
tokenizer = BPETokenizer(params)
 
string = "the quick brown fox"
indices = tokenizer.encode(string)
reconstructed_string = tokenizer.decode(indices)
assert string == reconstructed_string