跳转至

大模型从0到1|第一讲:概述和Tokenization

课程链接:Stanford CS336 Spring 2025 - Lecture 1


课程团队

Course Staff

CS336: Language Models From Scratch (Spring 2025)

  • 这是 CS336 的第二次开课
  • 斯坦福版本规模扩大了 50%
  • 讲座将发布到 YouTube,向全世界开放

为什么要开设这门课?

问题:研究者与底层技术的脱节

让我们问问 GPT-4:

"Why teach a course on building language models from scratch? Answer in one sentence."

核心问题: 研究者正在与底层技术脱节

时间线: - 8 年前: 研究者会实现并训练自己的模型 - 6 年前: 研究者会下载模型(如 BERT)并微调 - 今天: 研究者只是提示专有模型(GPT-4/Claude/Gemini)

抽象层次的提升: - ✅ 提高生产力 - ❌ 但这些抽象是有漏洞的(与编程语言或操作系统不同) - ❌ 仍有需要撕开整个技术栈的基础研究

核心理念: 对这项技术的完全理解对于基础研究是必要的

本课程方法: 通过构建理解

但有一个小问题...


大模型的工业化

Industrialization

规模现状: - GPT-4 据称有 1.8T 参数 - GPT-4 据称训练成本 $100M - xAI 用 200,000 个 H100 构建集群训练 Grok - Stargate(OpenAI, NVIDIA, Oracle)4 年投资 $500B

透明度问题:

没有关于前沿模型如何构建的公开细节。

来自 GPT-4 技术报告:

GPT-4 No Details


More is Different(规模带来质变)

挑战: - 前沿模型对我们来说遥不可及 - 构建小型语言模型(<1B 参数)可能无法代表大型语言模型

示例 1: 注意力 vs MLP 的 FLOPs 占比随规模变化

Roller FLOPs

示例 2: 行为的涌现(Emergence)

Wei Emergence


本课程能学到什么可以迁移到前沿模型?

三类知识:

  1. 机制(Mechanics): 事物如何工作
  2. Transformer 是什么
  3. 模型并行如何利用 GPU
  4. 可迁移

  5. 思维方式(Mindset): 充分利用硬件,认真对待规模

  6. 扩展定律(Scaling Laws)
  7. 可迁移

  8. 直觉(Intuitions): 哪些数据和建模决策产生好的准确性

  9. ⚠️ 部分可迁移(不一定跨规模迁移)

直觉?🤷

现实: 一些设计决策无法(尚未)证明合理性,只能来自实验

示例: Noam Shazeer 引入 SwiGLU 的论文

Divine Benevolence


The Bitter Lesson(痛苦的教训)

错误解读: 规模就是一切,算法不重要

正确解读: 能够扩展的算法才重要

accuracy = efficiency × resources

效率的重要性: - 在更大规模下,效率更加重要(不能浪费) - 2012-2019 年,ImageNet 上的算法效率提升了 44 倍

框架: 给定一定的计算和数据预算,能构建的最佳模型是什么?

换句话说,最大化效率


大模型发展历程

前神经网络时代(2010年代之前)

  • 语言模型测量英语熵 - Shannon (1950)
  • N-gram 语言模型 - 用于机器翻译、语音识别 - Brants et al. (2007)

神经网络组件(2010年代)

  • 首个神经语言模型 - Bengio et al. (2003)
  • 序列到序列建模 - 用于机器翻译 - Sutskever et al. (2014)
  • Adam 优化器 - Kingma & Ba (2014)
  • 注意力机制 - 用于机器翻译 - Bahdanau et al. (2015)
  • Transformer 架构 - 用于机器翻译 - Vaswani et al. (2017)
  • 混合专家(MoE) - Shazeer et al. (2017)
  • 模型并行 - GPipe (2018), ZeRO (2019), Megatron-LM (2019)

早期基础模型(2010年代末)

  • ELMo: LSTM 预训练,微调帮助任务
  • BERT: Transformer 预训练,微调帮助任务
  • Google T5 (11B): 将一切转换为文本到文本

拥抱规模,更加封闭

  • OpenAI GPT-2 (1.5B): 流畅文本,零样本的初步迹象,分阶段发布
  • 扩展定律: 为扩展提供希望/可预测性 - Kaplan et al. (2020)
  • OpenAI GPT-3 (175B): 上下文学习,封闭
  • Google PaLM (540B): 大规模,训练不足
  • DeepMind Chinchilla (70B): 计算最优扩展定律

开源模型

  • EleutherAI: 开放数据集(The Pile)和模型(GPT-J)
  • Meta OPT (175B): GPT-3 复制,许多硬件问题
  • Hugging Face / BigScience BLOOM: 专注于数据来源
  • Meta Llama 系列: Llama, Llama 2, Llama 3
  • Alibaba Qwen 系列: Qwen 2.5
  • DeepSeek 系列: DeepSeek 67B, DeepSeek-V2, DeepSeek-V3
  • AI2 OLMo 2: OLMo 7B, OLMo 2

开放程度的层次

  1. 封闭模型(如 GPT-4o): 仅 API 访问
  2. 开放权重模型(如 DeepSeek): 权重可用,论文有架构细节,一些训练细节,无数据细节
  3. 开源模型(如 OLMo): 权重和数据可用,论文有大部分细节(但不一定有理由、失败实验)

当今的前沿模型

  • OpenAI o3
  • Anthropic Claude Sonnet 3.7
  • xAI Grok 3
  • Google Gemini 2.5
  • Meta Llama 3.3
  • DeepSeek r1
  • Alibaba Qwen 2.5 Max
  • Tencent Hunyuan-T1

什么是可执行讲座?

这是一个可执行讲座,一个通过执行来传递讲座内容的程序。

可执行讲座的优势: - 查看和运行代码(因为一切都是代码!) - 查看变量值和执行流程 - 看到讲座的层次结构 - 跳转到定义和概念

示例:

total = 0  # 可以检查值
for x in [1, 2, 3]:  # 可以看到 x 的变化
    total += x  # 可以看到 total 的更新


课程信息

官网: https://stanford-cs336.github.io/spring2025/

学分: 5 个学分

工作量警告:

来自 2024 年春季课程评估的评论: "整个作业的工作量大约相当于 CS 224n 的所有 5 个作业加上最终项目。而这只是第一个作业。"

为什么应该选这门课

  • 你有强迫性需要理解事物如何工作
  • 你想锻炼研究工程能力

为什么不应该选这门课

  • 你这个季度实际上想完成研究(和你的导师谈谈)
  • 你对学习 AI 最新技术感兴趣(如多模态、RAG 等)→ 应该选研讨课
  • 你想在自己的应用领域获得好结果 → 应该提示或微调现有模型

如何在家跟随

  • 所有讲座材料和作业将在线发布
  • 讲座通过 CGOE 录制并在 YouTube 上提供(有一定延迟)
  • 我们计划明年再次开设这门课

作业

  • 5 个作业: 基础、系统、扩展定律、数据、对齐
  • 无脚手架代码: 但提供单元测试和适配器接口帮助检查正确性
  • 本地实现测试正确性,然后在集群上运行进行基准测试(准确性和速度)
  • 排行榜: 某些作业(在训练预算下最小化困惑度)
  • AI 工具: CoPilot、Cursor 可能会影响学习,自行承担风险

计算集群

  • 感谢 Together AI 提供计算集群 🙏
  • 请阅读使用指南
  • 提前开始作业,因为临近截止日期集群会很满!

课程核心:效率

资源: 数据 + 硬件(计算、内存、通信带宽)

核心问题: 给定固定资源,如何训练最佳模型?

示例: 给定 Common Crawl 数据和 32 个 H100 GPU 2 周时间,应该怎么做?

设计决策

Design Decisions


课程概览

Part 1: 基础(Basics)

目标: 让完整流程的基本版本运行起来

组件: Tokenization, 模型架构, 训练

Tokenization

Tokenizer 在字符串和整数序列(token)之间转换

Tokenized Example

直觉: 将字符串分解为流行的片段

本课程: Byte-Pair Encoding (BPE) tokenizer

无 Tokenizer 方法: ByT5, MEGABYTE, BLT, TFree - 直接使用字节,有前景,但尚未扩展到前沿

架构

起点: 原始 Transformer

Transformer Architecture

变体: - 激活函数: ReLU, SwiGLU - 位置编码: Sinusoidal, RoPE - 归一化: LayerNorm, RMSNorm - 归一化位置: Pre-norm vs Post-norm - MLP: Dense, Mixture of Experts - 注意力: Full, Sliding Window, Linear - 低维注意力: Group-Query Attention (GQA), Multi-Head Latent Attention (MLA) - 状态空间模型: Hyena

训练

  • 优化器: AdamW, Muon, SOAP
  • 学习率调度: Cosine, WSD
  • 批大小: Critical batch size
  • 正则化: Dropout, Weight Decay
  • 超参数: 网格搜索(头数、隐藏维度)

Assignment 1

GitHub: https://github.com/stanford-cs336/assignment1-basics

任务: - 实现 BPE tokenizer - 实现 Transformer、交叉熵损失、AdamW 优化器、训练循环 - 在 TinyStories 和 OpenWebText 上训练 - 排行榜:在 H100 上 90 分钟内最小化 OpenWebText 困惑度


Part 2: 系统(Systems)

目标: 充分利用硬件

组件: Kernels, 并行化, 推理

Kernels

A100 GPU 架构:

A100 Architecture

类比: 仓库 : DRAM :: 工厂 : SRAM

Factory Bandwidth

技巧: 通过最小化数据移动来最大化 GPU 利用率

工具: CUDA / Triton / CUTLASS / ThunderKittens

并行化

多 GPU 场景(8 个 A100):

8xA100 Topology

原则: GPU 间数据移动更慢,但同样的"最小化数据移动"原则适用

技术: - 集合操作(gather, reduce, all-reduce) - 跨 GPU 分片(参数、激活、梯度、优化器状态) - 并行策略:数据并行、张量并行、流水线并行、序列并行

推理

目标: 给定提示生成 token(实际使用模型所需!)

用途: 强化学习、测试时计算、评估

全局视角: 推理计算(每次使用)超过训练计算(一次性成本)

两个阶段:

Prefill Decode

  • Prefill(类似训练): Token 已给定,可一次处理(计算受限)
  • Decode: 需要逐个生成 token(内存受限)

加速解码方法: - 使用更便宜的模型(剪枝、量化、蒸馏) - 推测解码:使用便宜的"草稿"模型生成多个 token,然后用完整模型并行评分(精确解码!) - 系统优化:KV 缓存、批处理

Assignment 2

GitHub(2024): https://github.com/stanford-cs336/spring2024-assignment2-systems

任务: - 用 Triton 实现融合 RMSNorm kernel - 实现分布式数据并行训练 - 实现优化器状态分片 - 对实现进行基准测试和性能分析


Part 3: 扩展定律(Scaling Laws)

目标: 在小规模做实验,预测大规模的超参数/损失

问题: 给定 FLOPs 预算 C,使用更大的模型 N 还是训练更多 token D?

计算最优扩展定律: Kaplan et al. (2020), Chinchilla

Chinchilla Isoflop

结论: D = 20 N(例如,1.4B 参数模型应在 28B token 上训练)

注意: 这没有考虑推理成本!

Assignment 3

GitHub(2024): https://github.com/stanford-cs336/spring2024-assignment3-scaling

任务: - 我们定义训练 API(超参数 → 损失)基于之前的运行 - 提交"训练作业"(在 FLOPs 预算下)并收集数据点 - 拟合扩展定律到数据点 - 提交扩展后超参数的预测 - 排行榜:在 FLOPs 预算下最小化损失


Part 4: 数据(Data)

问题: 我们希望模型具有什么能力?

多语言?代码?数学?

The Pile Chart

评估

  • 困惑度: 语言模型的教科书评估
  • 标准化测试: MMLU, HellaSwag, GSM8K
  • 指令遵循: AlpacaEval, IFEval, WildBench
  • 扩展测试时计算: Chain-of-thought, 集成
  • LM-as-a-judge: 评估生成任务
  • 完整系统: RAG, agents

数据策划

  • 数据不会从天而降
  • 来源: 从互联网爬取的网页、书籍、arXiv 论文、GitHub 代码等
  • 版权: 诉诸合理使用来训练版权数据?
  • 许可: 可能需要许可数据(如 Google 与 Reddit)
  • 格式: HTML, PDF, 目录(不是文本!)

数据处理

  • 转换: 将 HTML/PDF 转换为文本(保留内容、一些结构、重写)
  • 过滤: 保留高质量数据,删除有害内容(通过分类器)
  • 去重: 节省计算,避免记忆;使用 Bloom 过滤器或 MinHash

Assignment 4

GitHub(2024): https://github.com/stanford-cs336/spring2024-assignment4-data

任务: - 将 Common Crawl HTML 转换为文本 - 训练分类器过滤质量和有害内容 - 使用 MinHash 去重 - 排行榜:在 token 预算下最小化困惑度


Part 5: 对齐(Alignment)

基础模型: 原始潜力,非常擅长完成下一个 token

对齐: 使模型真正有用

对齐目标: - 让语言模型遵循指令 - 调整风格(格式、长度、语气等) - 纳入安全性(如拒绝回答有害问题)

监督微调(SFT)

指令数据: (prompt, response) 对

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."),
    ],
)

数据: 通常涉及人工标注

直觉: 基础模型已经有技能,只需要少量示例来激发它们

方法: 监督学习,微调模型以最大化 p(response | prompt)

从反馈中学习

偏好数据: 使用模型生成多个响应(如 [A, B])到给定提示

用户提供偏好(如 A < B 或 A > B)

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",
)

验证器: - 形式验证器(如代码、数学) - 学习验证器:针对 LM-as-a-judge 训练

算法: - PPO: 来自强化学习的近端策略优化 - DPO: 直接策略优化,用于偏好数据,更简单 - GRPO: 组相对偏好优化,移除价值函数

Assignment 5

GitHub(2024): https://github.com/stanford-cs336/spring2024-assignment5-alignment

任务: - 实现监督微调 - 实现直接偏好优化(DPO) - 实现组相对偏好优化(GRPO)


效率驱动设计决策

当前: 我们受计算约束,因此设计决策将反映充分利用给定硬件

  • 数据处理: 避免在坏/无关数据上浪费宝贵计算
  • Tokenization: 使用原始字节很优雅,但在当今模型架构下计算效率低
  • 模型架构: 许多变化是为了减少内存或 FLOPs(如共享 KV 缓存、滑动窗口注意力)
  • 训练: 我们可以只用一个 epoch!
  • 扩展定律: 在较小模型上使用更少计算进行超参数调优
  • 对齐: 如果将模型更多调整到期望用例,需要更小的基础模型

未来: 我们将变得数据受限...


Tokenization 详解

本单元受 Andrej Karpathy 关于 tokenization 的视频启发,推荐观看! YouTube 视频


什么是 Tokenization?

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

string = "Hello, 🌍! 你好!"

语言模型: 在 token 序列(通常用整数索引表示)上放置概率分布

indices = [15496, 11, 995, 0]

需求: - 编码(Encode): 将字符串转换为 token 的过程 - 解码(Decode): 将 token 转换回字符串的过程

Tokenizer: 实现 encode 和 decode 方法的类

class Tokenizer(ABC):
    def encode(self, string: str) -> list[int]:
        raise NotImplementedError

    def decode(self, indices: list[int]) -> str:
        raise NotImplementedError

词汇表大小(Vocabulary Size): 可能的 token 数量(整数)


Tokenization 示例

交互式网站: https://tiktokenizer.vercel.app/?encoder=gpt2

观察: - 单词及其前面的空格是同一个 token 的一部分(如 " world") - 开头和中间的单词表示不同(如 "hello hello") - 数字被 tokenize 为每几位数字

GPT-2 Tokenizer 实战:

tokenizer = tiktoken.get_encoding("gpt2")
string = "Hello, 🌍! 你好!"

# 编码
indices = tokenizer.encode(string)
# 输出: [15496, 11, 12520, 234, 171, 120, 234, 0, 220, 20998, 25001, 171, 120, 234]

# 解码
reconstructed_string = tokenizer.decode(indices)
# 输出: "Hello, 🌍! 你好!"

assert string == reconstructed_string  # 往返一致性

# 压缩率
compression_ratio = len(string.encode("utf-8")) / len(indices)
# 约 1.5-2.0

方法 1: 基于字符的 Tokenization

原理: Unicode 字符串是 Unicode 字符的序列

转换: - 字符 → 码点(整数):ord() - 码点 → 字符:chr()

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

实现:

class CharacterTokenizer(Tokenizer):
    def encode(self, string: str) -> list[int]:
        return list(map(ord, string))

    def decode(self, indices: list[int]) -> str:
        return "".join(map(chr, indices))

测试:

tokenizer = CharacterTokenizer()
string = "Hello, 🌍! 你好!"
indices = tokenizer.encode(string)
# [72, 101, 108, 108, 111, 44, 32, 127757, 33, 32, 20320, 22909, 33]

reconstructed_string = tokenizer.decode(indices)
assert string == reconstructed_string

问题:

  1. 词汇表太大: 约 150K Unicode 字符
  2. 效率低: 许多字符非常罕见(如 🌍),词汇表使用效率低
  3. 压缩率差: compression_ratio ≈ 1.0

方法 2: 基于字节的 Tokenization

原理: Unicode 字符串可以表示为字节序列

UTF-8 编码: 最常见的 Unicode 编码 - 某些字符用 1 个字节表示:"a"b"a" - 其他字符需要多个字节:"🌍"b"\xf0\x9f\x8c\x8d"

实现:

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

测试:

tokenizer = ByteTokenizer()
string = "Hello, 🌍! 你好!"
indices = tokenizer.encode(string)
# [72, 101, 108, 108, 111, 44, 32, 240, 159, 140, 141, 33, 32, 228, 189, 160, 229, 165, 189, 33]

reconstructed_string = tokenizer.decode(indices)
assert string == reconstructed_string

优点: - ✅ 词汇表小:256(一个字节可以表示 256 个值)

问题: - ❌ 压缩率糟糕:compression_ratio = 1.0 - ❌ 序列太长 - ❌ 由于 Transformer 的上下文长度有限(注意力是二次的),这不太好...


方法 3: 基于单词的 Tokenization

原理: 将字符串分割成单词(经典 NLP 方法)

简单正则表达式:

string = "I'll say supercalifragilisticexpialidocious!"
segments = regex.findall(r"\w+|.", string)
# ['I', 'll', 'say', 'supercalifragilisticexpialidocious', '!']

GPT-2 风格正则表达式:

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

segments = regex.findall(GPT2_TOKENIZER_REGEX, string)
# ['I', "'ll", ' say', ' supercalifragilisticexpialidocious', '!']

问题:

  1. 单词数量巨大: 类似 Unicode 字符
  2. 许多单词罕见: 模型学不到太多
  3. 没有固定词汇表大小
  4. 未见过的单词: 需要特殊的 UNK token,影响困惑度计算

方法 4: Byte Pair Encoding (BPE)

历史: - 1994 年由 Philip Gage 引入用于数据压缩 - 2016 年适配到 NLP 用于神经机器翻译(Sennrich et al.) - GPT-2 使用 BPE

基本思想: 在原始文本上训练 tokenizer 以自动确定词汇表

直觉: 常见字符序列用单个 token 表示,罕见序列用多个 token 表示

算法草图: 从每个字节作为 token 开始,逐步合并最常见的相邻 token 对


BPE 训练过程

示例:

string = "the cat in the hat"
num_merges = 3

步骤 1: 从字节开始

indices = [116, 104, 101, 32, 99, 97, 116, 32, 105, 110, 32, 116, 104, 101, 32, 104, 97, 116]
# 对应: t h e   c a t   i n   t h e   h a t

步骤 2: 统计相邻 token 对的出现次数

counts = {
    (116, 104): 2,  # "th"
    (104, 101): 2,  # "he"
    (101, 32): 2,   # "e "
    (32, 116): 1,   # " t"
    ...
}

步骤 3: 找到最常见的对并合并

# 第一次合并: (116, 104) -> 256  # "th"
indices = [256, 101, 32, 99, 97, 116, 32, 105, 110, 32, 256, 101, 32, 104, 97, 116]

# 第二次合并: (256, 101) -> 257  # "the"
indices = [257, 32, 99, 97, 116, 32, 105, 110, 32, 257, 32, 104, 97, 116]

# 第三次合并: (257, 32) -> 258  # "the "
indices = [258, 99, 97, 116, 32, 105, 110, 32, 258, 104, 97, 116]

结果:

vocab = {
    0-255: 原始字节,
    256: b"th",
    257: b"the",
    258: b"the ",
}

merges = {
    (116, 104): 256,
    (256, 101): 257,
    (257, 32): 258,
}

BPE 实现

数据结构:

@dataclass(frozen=True)
class BPETokenizerParams:
    vocab: dict[int, bytes]              # index -> bytes
    merges: dict[tuple[int, int], int]   # (index1, index2) -> new_index

训练函数:

def train_bpe(string: str, num_merges: int) -> BPETokenizerParams:
    # 从字节开始
    indices = list(map(int, string.encode("utf-8")))
    merges = {}
    vocab = {x: bytes([x]) for x in range(256)}

    for i in range(num_merges):
        # 统计相邻对
        counts = defaultdict(int)
        for index1, index2 in zip(indices, indices[1:]):
            counts[(index1, index2)] += 1

        # 找到最常见的对
        pair = max(counts, key=counts.get)
        index1, index2 = 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)

合并辅助函数:

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

Tokenizer 类:

class BPETokenizer(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

使用 BPE Tokenizer

训练:

string = "the cat in the hat"
params = train_bpe(string, num_merges=3)

编码新文本:

tokenizer = BPETokenizer(params)
string = "the quick brown fox"
indices = tokenizer.encode(string)
reconstructed_string = tokenizer.decode(indices)
assert string == reconstructed_string

Assignment 1 中的改进

在 Assignment 1 中,你需要超越这个基础实现:

  1. 优化 encode(): 当前循环所有合并,只循环相关的合并
  2. 特殊 token: 检测并保留特殊 token(如 <|endoftext|>
  3. 预 tokenization: 使用 GPT-2 tokenizer 正则表达式
  4. 性能优化: 尽可能提高实现速度

总结

Tokenization 要点

  • Tokenizer: 字符串 ↔ token(索引)
  • 基于字符、字节、单词的 tokenization: 高度次优
  • BPE: 查看语料库统计的有效启发式方法
  • Tokenization 是必要之恶: 也许有一天我们会直接从字节做起...

下节课预告

主题: PyTorch 构建块,资源核算


参考资源

Tokenization: - Andrej Karpathy's Tokenization Video - BPE Wikipedia - Original BPE Paper (Gage 1994) - BPE for NMT (Sennrich et al. 2016) - tiktoken (OpenAI)

课程资源: - Course Website - Assignment 1 GitHub - 2024 Leaderboard


💬 评论