Standford CS336(一)课程介绍
斯坦福CS336课程(2025春季)聚焦"从零构建语言模型",针对当前AI研究领域出现的"技术脱节"现象,强调通过实践深入理解模型机制。课程揭示了语言模型工业化面临的挑战:GPT-4等前沿模型需1.8万亿参数和1亿美元训练成本,且核心细节不公开。课程提出"多即不同"原则,指出小型与大型模型存在本质差异,重点培养三种能力:模型机制理解、硬件
文章目录
仓库链接:https://github.com/stanford-cs336/spring2025-lectures
斯坦福CS336:从零开始构建语言模型 (2025春季)
课程简介
本课程是斯坦福大学CS336课程的第二次开课,专注于“从零开始构建语言模型”。值得注意的是,本课程在斯坦福大学的规模增长了50%,显示出其日益增长的受欢迎程度和重要性。
为什么开设这门课程?
开设这门课程的核心原因在于当前人工智能研究领域的一个显著趋势:研究人员与底层技术之间正在出现一种“脱节”现象。在过去,大约八年前,研究人员通常会亲自动手实现并训练自己的模型,这使得他们对模型的内部机制有着深刻的理解。然而,随着时间的推移,这种实践方式逐渐演变。六年前,研究人员开始倾向于下载预训练好的模型(例如BERT),然后在此基础上进行微调,这简化了开发流程,但也减少了对模型从头构建的参与度。到了今天,许多研究人员甚至仅仅通过提示(prompt)来与专有模型(如GPT-4、Claude、Gemini等)进行交互,而无需了解其背后的复杂实现细节。
这种抽象层次的提升无疑极大地提高了生产力,使得研究人员能够更快地迭代和实验。然而,课程指出,这些抽象并非完美无缺,它们是“有漏洞的”(leaky),这意味着在某些情况下,底层的复杂性仍然会暴露出来,影响模型的行为和性能。与编程语言或操作系统等成熟的抽象不同,大型语言模型的抽象层仍然处于发展阶段,其内部工作原理对外部使用者而言并非完全透明。更重要的是,人工智能领域仍然有大量的基础研究工作需要完成,而这些研究往往需要“撕开”现有的抽象层,深入到技术栈的底层进行探索和创新。因此,课程强调,对这项技术拥有全面而深入的理解,是进行基础研究不可或缺的前提。
基于此,本课程的核心理念是“通过构建来理解”。这意味着学生将通过亲手实现语言模型的各个组成部分,从而获得对其工作原理的深刻洞察。然而,课程也坦诚地指出,这其中存在一个“小问题”——即“语言模型的工业化”所带来的巨大挑战。
语言模型的工业化
当前,大型语言模型的开发和训练已经进入了一个工业化时代,其规模和成本达到了前所未有的水平。这使得个人或小型研究团队难以企及:
- 参数规模与训练成本: 以GPT-4为例,据称其拥有高达1.8万亿的参数,并且训练成本估计达到了惊人的1亿美元。这表明了训练一个前沿模型所需的巨大计算资源和资金投入。
- 超大规模计算集群: 像xAI这样的公司正在构建庞大的计算集群,例如用于训练Grok的20万块H100显卡集群,这进一步凸显了训练大型模型对硬件基础设施的极端需求。
- 巨额投资: 甚至有像Stargate这样的项目,由OpenAI、NVIDIA和Oracle联合发起,计划在四年内投资5000亿美元,旨在推动AI基础设施的建设,这预示着未来AI模型规模将继续膨胀。
除了巨大的成本和规模,另一个挑战是前沿模型的构建细节通常不公开。例如,GPT-4的技术报告中明确指出,由于其规模和能力的独特性,报告中并未包含关于模型架构、训练数据、硬件或训练方法等方面的具体细节。这种信息的不透明性使得外部研究人员难以复现或深入分析这些最先进的模型。
“多即不同” (More is different)
“多即不同”这一概念在大型语言模型领域尤为重要。它指出,虽然我们可以在课程中构建小型语言模型(参数量通常小于10亿),但这些小型模型的行为和特性可能与前沿的大型语言模型存在显著差异,因此不能简单地将小型模型的经验直接推广到大型模型上。
具体示例包括:
- 计算资源分配的变化: 随着模型规模的扩大,注意力机制和多层感知机(MLP)之间FLOPs(浮点运算)的分配比例会发生变化。在小型模型中,MLP可能占据主导地位,但在大型模型中,注意力机制的计算开销会变得更加显著,这需要不同的优化策略。
- 行为的涌现性: 大型语言模型在达到一定规模后,会展现出一些在小型模型中不曾出现的“涌现能力”(emergent abilities)。这些能力并非简单地通过线性叠加而产生,而是随着模型规模的增加,在复杂性和功能上发生质的飞跃。例如,大型模型可能突然展现出解决复杂推理问题、遵循复杂指令或进行多步规划的能力,而这些能力在较小模型中是缺失的。
从本课程中能学到什么?
尽管存在“多即不同”的挑战,本课程仍然致力于教授对前沿模型有价值的知识。课程将重点放在三种类型的知识上:
- 机制 (Mechanics): 这部分知识关注事物“如何运作”的底层原理。例如,学生将深入理解Transformer架构的内部机制,包括其编码器-解码器结构、自注意力机制、前馈网络等。此外,课程还将探讨模型并行化如何有效地利用多个GPU来训练超大型模型,包括数据并行、模型并行和流水线并行等技术。这些都是构建和优化大型语言模型的基础。
- 思维模式 (Mindset): 这部分知识培养学生一种“最大限度地利用硬件”的思维方式,并认真对待“规模”问题。这包括理解缩放定律(scaling laws),即模型性能如何随着计算资源、数据量和模型参数的增加而变化。这种思维模式强调在资源有限的情况下,如何高效地设计和训练模型,以达到最佳性能。
- 直觉 (Intuitions): 这部分知识涉及“哪些数据和建模决策能带来良好的准确性”。然而,课程也坦诚地指出,这部分直觉的教授只能是部分性的,因为许多直觉并不一定能跨规模迁移。例如,在小型模型上有效的某些超参数设置或数据处理策略,可能在大型模型上不再适用,甚至可能产生负面影响。许多设计决策并非基于严格的理论推导,而是来源于大量的实验和经验,例如SwiGLU激活函数的引入就是如此。
课程明确表示,可以有效地教授机制和思维模式,因为这些知识具有较强的可迁移性。然而,对于直觉部分,由于其高度依赖于具体规模和实验,因此只能进行有限的教授。
苦涩的教训 (The Bitter Lesson)
“苦涩的教训”是Rich Sutton在2019年提出的一种观点,它对人工智能领域的发展路径提出了深刻的见解。课程对此进行了澄清,指出其错误解读是“规模是唯一重要的,算法不重要”。这种解读忽视了算法在效率和创新中的作用。
而正确解读是“能够扩展的算法才是重要的”。这意味着,虽然规模确实带来了强大的能力,但真正有价值的是那些能够随着规模扩大而持续提升性能、并且能够高效利用资源的算法。一个无法有效扩展的算法,即使在小规模下表现良好,也难以在大型模型时代发挥作用。
课程进一步用一个公式来概括这一思想:准确性 = 效率 x 资源。这个公式强调了效率在模型性能中的关键作用。事实上,在更大规模的模型训练中,效率变得尤为重要,因为计算资源和时间成本都非常高昂,我们无法承受任何浪费。例如,有研究表明,从2012年到2019年,ImageNet上的算法效率提高了44倍,这意味着在相同的计算资源下,模型性能得到了显著提升。
因此,本课程的核心思想是:在给定一定的计算和数据预算的情况下,如何构建出最佳的模型?换句话说,就是要最大化效率!这包括在数据处理、模型架构、训练策略等各个环节中,寻找最有效的方法来利用有限的资源,从而在当前计算受限的环境下,实现模型性能的最大化。
当前语言模型发展历程
语言模型的发展经历了多个阶段,从早期的统计方法到如今的深度学习模型,每一次飞跃都伴随着新的技术突破和范式转变。
神经网络之前 (2010年代前)
在深度学习兴起之前,语言模型主要基于统计学方法:
- 香农的语言模型: 克劳德·香农在1950年提出了一个开创性的语言模型,用于衡量英语的熵。他通过预测下一个字符或单词来量化语言的信息量,这为后来的语言模型研究奠定了理论基础。
- N-gram语言模型: 在机器翻译和语音识别等领域,N-gram语言模型得到了广泛应用。这类模型通过计算N个单词序列(N-gram)的出现频率来预测下一个单词,例如,三元组(trigram)模型会考虑前两个单词来预测第三个单词。尽管简单,但在当时取得了显著的成功。
神经网络要素 (2010年代)
2010年代是神经网络在自然语言处理领域崭露头角的十年,一系列关键技术和模型架构的出现,为现代大型语言模型奠定了基础:
- 第一个神经网络语言模型: 约书亚·本吉奥(Yoshua Bengio)等人在2003年提出了第一个神经网络语言模型,它使用前馈神经网络来学习单词的分布式表示(词嵌入),并预测下一个单词。这标志着神经网络在语言建模中的首次成功应用。
- 序列到序列建模 (Sequence-to-sequence modeling): 2014年,Sutskever等人提出了序列到序列(Seq2Seq)模型,该模型由一个编码器和一个解码器组成,能够将一个序列映射到另一个序列,在机器翻译任务中取得了突破性进展。这是许多现代生成式模型的基础。
- Adam优化器: 2014年,Kingma和Ba提出了Adam(Adaptive Moment Estimation)优化器,它结合了Adagrad和RMSprop的优点,能够自适应地调整学习率,并有效地处理稀疏梯度和非平稳目标。Adam迅速成为深度学习中最流行的优化器之一。
- 注意力机制 (Attention mechanism): 2015年,Bahdanau等人引入了注意力机制,允许模型在生成输出时,动态地关注输入序列中的相关部分。这解决了传统Seq2Seq模型在处理长序列时信息瓶颈的问题,极大地提升了机器翻译等任务的性能。
- Transformer架构: 2017年,Vaswani等人提出了划时代的Transformer架构,完全放弃了循环神经网络(RNN)和卷积神经网络(CNN),仅依靠自注意力机制来处理序列数据。Transformer的并行计算能力和捕获长距离依赖的优势,使其成为后续大型语言模型的基础架构。
- 专家混合 (Mixture of Experts, MoE): 2017年,Shazeer等人提出了专家混合模型,通过引入多个“专家”网络,并使用一个门控网络来选择性地激活这些专家,从而在不显著增加计算量的情况下,大幅增加模型的参数量和容量。这为构建超大型模型提供了新的思路。
- 模型并行化: 随着模型规模的增长,单个设备已无法容纳整个模型。模型并行化技术应运而生,包括GPipe(2018年)、ZeRO(2019年)和Megatron-LM(2019年)等,它们将模型的不同部分分布到不同的设备上进行计算,从而实现超大型模型的训练。
早期基础模型 (2010年代末)
在Transformer架构的推动下,一批“基础模型”开始出现,它们通过大规模预训练,为各种下游任务提供了强大的通用能力:
- ELMo: 2018年,Peters等人提出了ELMo(Embeddings from Language Models),它使用双向LSTM进行预训练,生成上下文相关的词嵌入。ELMo的出现表明,通过大规模预训练获得的语言表示,可以显著提升各种自然语言处理任务的性能。
- BERT: 2018年,Devlin等人发布了BERT(Bidirectional Encoder Representations from Transformers),它使用Transformer编码器进行双向预训练,通过掩码语言模型(Masked Language Model)和下一句预测(Next Sentence Prediction)任务,学习深层双向的语言表示。BERT在多项NLP任务上刷新了SOTA,并开创了“预训练-微调”的范式。
- Google的T5 (11B): 2020年,Raffel等人提出了T5(Text-to-Text Transfer Transformer),它将所有文本处理任务都统一为“文本到文本”的格式,并在大规模数据集上进行预训练。T5的成功展示了统一框架的强大潜力。
拥抱规模,更加封闭
进入2020年代,语言模型的发展进入了一个“拥抱规模”的阶段,模型参数量持续膨胀,同时许多前沿模型变得更加封闭:
- OpenAI的GPT-2 (1.5B): 2019年,OpenAI发布了GPT-2,一个拥有15亿参数的语言模型。它能够生成非常流畅和连贯的文本,并首次展现出零样本学习(zero-shot learning)的能力,即在没有特定任务训练数据的情况下执行任务。GPT-2的发布采取了分阶段的方式,以评估其潜在风险。
- 缩放定律 (Scaling laws): 2020年,Kaplan等人提出了语言模型的缩放定律,揭示了模型性能如何随着计算量、模型参数和数据集大小的增加而呈现可预测的趋势。这些定律为大型模型的未来发展提供了指导和希望。
- OpenAI的GPT-3 (175B): 2020年,OpenAI发布了GPT-3,一个拥有1750亿参数的巨型模型。GPT-3最引人注目的能力是“上下文学习”(in-context learning),即通过在输入中提供少量示例,模型就能执行新任务,而无需进行权重更新。GPT-3的发布是封闭的,主要通过API提供服务。
- Google的PaLM (540B): 2022年,Google发布了PaLM(Pathways Language Model),一个拥有5400亿参数的大规模语言模型。尽管规模巨大,但有观点认为其在训练上可能存在“训练不足”(undertrained)的情况,即在给定模型规模下,训练数据量或训练步数可能不足以充分发挥其潜力。
- DeepMind的Chinchilla (70B): 2022年,DeepMind的研究表明,在给定计算预算的情况下,模型参数量和训练数据量之间存在一个最优比例。他们提出了Chinchilla模型,一个700亿参数的模型,通过增加训练数据量而非仅仅增加参数量,在相同计算预算下超越了更大的模型,强调了“计算最优缩放定律”的重要性。
开放模型
与封闭模型相对,一些研究机构和公司致力于推动“开放模型”的发展,旨在促进AI研究的透明度和可访问性:
- EleutherAI的开放数据集和模型: EleutherAI是一个致力于开放AI研究的社区,他们发布了The Pile等大型开放数据集,并训练了GPT-J等开放模型,为研究人员提供了宝贵的资源。
- Meta的OPT (175B): Meta发布了OPT(Open Pre-trained Transformer Language Models),旨在复现GPT-3的性能,并提供了详细的训练日志和代码。然而,OPT的训练过程中也遇到了大量的硬件问题,这反映了训练超大型模型的复杂性。
- Hugging Face / BigScience的BLOOM: BLOOM是一个由Hugging Face和BigScience社区合作训练的1760亿参数的多语言模型,其特点是高度关注数据来源的透明度和多样性,旨在构建一个真正开放和负责任的大型语言模型。
- Meta的Llama系列模型: Meta发布了Llama系列模型(Llama、Llama 2、Llama 3),这些模型在性能上与一些封闭模型相媲美,并且以开放的方式发布,极大地推动了开源大型语言模型生态系统的发展。
- 阿里巴巴的Qwen模型: 阿里巴巴也发布了Qwen系列模型,在中文和多语言能力方面表现出色,为中文社区提供了强大的开放模型选择。
- DeepSeek的模型: DeepSeek发布了一系列开放模型,包括DeepSeek-67B、DeepSeek-V2和DeepSeek-V3,这些模型在代码和数学等领域表现突出。
- AI2的OLMo 2: AI2(Allen Institute for AI)发布了OLMo 2,这是一个专注于科学研究的开放语言模型,提供了完整的训练数据、代码和模型权重,旨在促进可复现的AI研究。
开放性级别
语言模型的“开放性”并非一个简单的二元概念,而是存在不同的级别:
- 封闭模型 (Closed models): 这类模型通常由少数公司开发和维护,仅通过API提供访问,用户无法获取模型的权重、架构细节或训练数据。例如,OpenAI的GPT-4o就属于此类。
- 开放权重模型 (Open-weight models): 这类模型会发布模型的权重,并提供关于架构细节的论文,以及一些训练过程的细节,但通常不公开训练数据。DeepSeek的模型就是开放权重模型的典型代表。
- 开源模型 (Open-source models): 这是最高级别的开放性,不仅提供模型的权重和架构细节,还会公开训练数据,并提供详细的论文,尽可能地披露所有相关信息。例如,AI2的OLMo模型就是开源模型的典范,它旨在促进透明和可复现的AI研究。然而,即使是开源模型,也可能不包括所有决策背后的“原理”或“失败实验”的细节。
当今前沿模型
当前,大型语言模型领域竞争激烈,各大科技公司和研究机构都在不断推出新的前沿模型,推动着技术边界的拓展。这些模型通常在多模态能力、推理能力、上下文理解等方面展现出最先进的水平:
- OpenAI的o3: OpenAI持续推出其最新的模型迭代,o3是其在特定领域或能力上的最新进展。
- Anthropic的Claude Sonnet 3.7: Anthropic的Claude系列模型以其在安全性和伦理方面的关注而闻名,Sonnet 3.7是其最新版本,在性能和安全性之间取得了平衡。
- xAI的Grok 3: xAI是埃隆·马斯克创立的AI公司,Grok模型以其独特的个性和实时信息处理能力而受到关注,Grok 3是其最新迭代。
- Google的Gemini 2.5: Google DeepMind的Gemini系列模型是其在多模态AI领域的旗舰产品,Gemini 2.5代表了其在理解和生成多种数据类型方面的最新进展。
- Meta的Llama 3.3: Meta的Llama系列模型在开源社区中广受欢迎,Llama 3.3是其最新版本,持续提升性能和可用性。
- DeepSeek的r1: DeepSeek在代码和数学领域有其独特的优势,r1是其在该领域的最新研究成果。
- 阿里巴巴的Qwen 2.5 Max: 阿里巴巴的Qwen系列模型在中文和多语言能力方面表现突出,Qwen 2.5 Max是其高性能版本。
- 腾讯的Hunyuan-T1: 腾讯的混元大模型系列,Hunyuan-T1是其在大型语言模型领域的最新探索。
什么是“可执行讲座”?
本课程采用了一种创新的教学形式,即“可执行讲座”(Executable Lecture)。这不仅仅是传统的幻灯片或讲义,而是一个程序,其执行过程本身就是讲座内容的呈现。这种形式旨在弥合理论与实践之间的鸿沟,让学生能够更深入地理解复杂的概念。
“可执行讲座”的优势在于:
- 代码的可见性与可运行性: 由于整个讲座都是以代码的形式呈现,学生可以清晰地看到每个概念是如何通过代码实现的。更重要的是,这些代码是可运行的,学生可以实时修改参数、观察结果,从而直观地理解代码的逻辑和模型的行为。这比仅仅阅读代码或观看演示视频更能加深理解。
- 讲座的层次结构: 可执行讲座通常会以结构化的方式组织内容,通过代码的函数调用或模块导入来体现讲座的逻辑流程和层次关系。学生可以轻松地浏览讲座的不同部分,理解各个主题之间的关联性,从而建立起一个完整的知识体系。
- 概念的快速跳转与定义: 在传统的讲座中,当遇到一个新概念时,学生可能需要翻阅资料或进行搜索。而在可执行讲座中,通常会提供便捷的跳转功能,允许学生直接点击某个概念,立即查看其定义、相关代码示例或更详细的解释。这大大提高了学习效率,使得学生能够根据自己的节奏和需求进行深度探索。
通过这种互动式的、以代码为中心的教学方式,课程旨在为学生提供一个更具沉浸感和实践性的学习体验,帮助他们更好地掌握大型语言模型的复杂知识。
作业
本课程包含5个作业,涵盖了语言模型构建的各个核心方面:基础(basics)、系统(systems)、缩放定律(scaling laws)、数据(data)和对齐(alignment)。这些作业旨在让学生亲身实践,从零开始构建语言模型的关键组件。
- 无模板代码: 与许多提供大量模板代码的课程不同,本课程的作业不提供模板代码。这意味着学生需要从头开始编写大部分代码,这极大地锻炼了他们的编程和解决问题的能力。然而,为了帮助学生检查代码的正确性,课程会提供单元测试和适配器接口。
- 本地测试与集群运行: 学生需要在本地实现并测试代码的正确性,然后将代码提交到计算集群上进行基准测试,以评估模型的准确性和运行速度。这种实践模式模拟了真实世界中AI模型开发的流程。
- 排行榜: 对于部分作业,课程会设置排行榜,例如在给定90分钟H100显卡预算下最小化OpenWebText困惑度。这种竞争机制可以激励学生优化他们的模型,追求更高的效率和性能。
- AI工具的使用: 课程提醒学生,AI工具(如GitHub Copilot、Cursor等)虽然能提高编码效率,但也可能“剥夺”学习的机会。因此,学生应自行承担使用这些工具的风险,并权衡其对学习效果的影响。
课程组成
本课程的核心理念是围绕“效率”展开。在大型语言模型领域,资源(包括数据和硬件,如计算能力、内存和通信带宽)是有限的。因此,课程的核心问题是:如何在给定固定资源的情况下,训练出最佳的模型?
课程通过一个具体的例子来阐明这个问题:如果给你一个Common Crawl数据转储和32块H100显卡,为期2周,你应该怎么做才能训练出最好的模型?这促使学生思考如何在有限的资源约束下,做出最优的设计决策。
设计决策涵盖了模型构建的各个方面,从数据处理到模型架构,再到训练策略,每一个环节都需要权衡效率和性能。
课程概述
课程内容被划分为五个主要组成部分,每个部分都深入探讨了语言模型构建的关键方面:
- 基础 (Basics): 涵盖语言模型的基本构建模块,包括分词、模型架构和训练流程。
- 系统 (Systems): 专注于如何最大限度地利用硬件资源,提高训练和推理效率。
- 缩放定律 (Scaling Laws): 探讨模型性能如何随计算、数据和参数规模的变化而变化,以及如何利用这些定律进行模型设计。
- 数据 (Data): 深入研究数据在语言模型训练中的作用,包括数据收集、清洗、预处理和质量评估。
- 对齐 (Alignment): 关注如何使语言模型的行为与人类的价值观和意图保持一致,包括偏见消除、安全性等。
效率驱动设计决策
当前,大型语言模型的训练主要受限于计算资源。因此,所有的设计决策都将围绕着如何“最大限度地利用现有硬件”这一目标展开:
- 数据处理: 为了避免浪费宝贵的计算资源,需要高效地处理数据,避免在低质量或不相关的数据上进行训练。这意味着需要精心设计数据清洗、过滤和采样策略。
- 分词 (Tokenization): 尽管直接使用原始字节进行处理在理论上可能更优雅,但对于当前主流的模型架构而言,这种方式在计算上效率低下。因此,选择高效的分词方法(如BPE)至关重要,以确保输入序列的长度适中,从而降低计算成本。
- 模型架构: 许多模型架构的创新都旨在减少内存占用或浮点运算(FLOPs)。例如,共享KV缓存(Key-Value Cache Sharing)和滑动窗口注意力(Sliding Window Attention)等技术,都是为了在保持性能的同时,降低模型的计算和内存需求。
- 训练策略: 在某些情况下,即使只进行一个epoch的训练,也能获得不错的效果,这表明了训练效率的重要性。优化训练策略,如选择合适的批处理大小、学习率调度等,可以显著缩短训练时间。
- 缩放定律: 利用缩放定律,可以在训练小型模型时使用较少的计算资源进行超参数调优。通过在小规模上进行实验,可以预测大型模型的行为,从而节省大量的计算成本。
- 对齐: 如果能够更好地将模型调整到所需的用例,那么即使是较小的基础模型也能满足需求。这意味着通过有效的对齐技术,可以减少对超大规模基础模型的依赖,从而降低训练和部署成本。
课程展望,未来语言模型的发展可能会从计算受限转向数据受限,这意味着数据质量和多样性将成为新的瓶颈。
基础 (Basics)
“基础”部分的目标是让学生能够构建一个完整语言模型管道的基本版本,使其能够正常工作。这包括三个核心组成部分:分词(Tokenization)、模型架构(Model Architecture)和训练(Training)。
分词 (Tokenization)
核心概念: 分词器(Tokenizer)是语言模型中的一个关键组件,它的主要功能是将人类可读的字符串(例如句子或文档)转换为一系列整数序列,这些整数被称为“token”(词元)。反之,它也能将这些整数序列解码回原始字符串。
直觉: 分词的直觉在于将字符串分解成“常用片段”。例如,一个单词可能被分解成多个子词单元,或者一个常见的短语被视为一个单一的token。这种分解方式旨在平衡词汇量大小和序列长度,从而提高模型的效率和性能。
本课程的分词器: 本课程将重点介绍并实现**字节对编码(Byte-Pair Encoding, BPE)**分词器。BPE是一种数据压缩算法,后来被成功应用于自然语言处理领域,尤其是在神经机器翻译中,并被GPT-2等大型语言模型广泛采用。
无分词器方法: 尽管BPE等分词器是主流,但也有一些研究探索“无分词器”的方法,例如直接使用原始字节进行处理。这些方法包括byt5、megabyte、blt和tfree等。虽然这些方法在理论上具有优雅性,并且在某些场景下表现出潜力,但目前尚未能扩展到前沿大型模型的规模,主要原因在于直接处理字节会导致输入序列过长,从而增加计算开销。
架构 (Architecture)
起点: 本课程将以原始Transformer架构作为构建语言模型的起点。Transformer是当前大型语言模型的基础,其核心在于自注意力机制,能够高效地处理序列数据并捕获长距离依赖。
Transformer的变体: 尽管原始Transformer是基础,但为了适应不同的需求和优化性能,研究人员提出了许多变体:
- 激活函数: 除了传统的ReLU(Rectified Linear Unit),还出现了如SwiGLU(Swish-Gated Linear Unit)等新型激活函数,它们在某些情况下能带来更好的性能。
- 位置编码: Transformer模型本身不具备处理序列顺序的能力,因此需要位置编码来引入位置信息。常见的位置编码包括正弦位置编码(sinusoidal positional encoding)和旋转位置编码(RoPE, Rotary Position Embedding),后者在处理长序列时表现出优势。
- 归一化: 归一化层在深度学习模型中扮演着重要角色,有助于稳定训练和加速收敛。常见的归一化方法包括LayerNorm(层归一化)和RMSNorm(均方根归一化)。
- 归一化位置: 归一化层在模型中的放置位置也会影响性能,主要有两种模式:pre-norm(预归一化)和post-norm(后归一化)。
- MLP(多层感知机): Transformer中的前馈网络通常由MLP组成。除了标准的密集型MLP,专家混合(Mixture of Experts, MoE)结构也被引入,通过稀疏激活多个专家网络来增加模型容量,同时控制计算成本。
- 注意力机制: 除了标准的全局自注意力,还出现了多种注意力变体,以提高效率或处理特定场景,例如滑动窗口注意力(Sliding Window Attention)和线性注意力(Linear Attention)。
- 低维注意力: 为了进一步优化注意力机制的效率,研究人员提出了如Group-Query Attention (GQA) 和Multi-Head Latent Attention (MLA) 等低维注意力方法。
- 状态空间模型: 除了Transformer,状态空间模型(State-Space Models, SSMs)也开始在语言建模中展现潜力,例如Hyena模型,它们提供了一种替代的序列建模范式。
训练 (Training)
语言模型的训练是一个复杂的过程,涉及多个关键组件和策略:
- 优化器: 优化器负责更新模型的参数以最小化损失函数。除了经典的Adam优化器,还有AdamW(Adam with Weight Decay Decoupling)、Muon和SOAP等变体,它们在不同场景下可能提供更好的收敛性和泛化能力。
- 学习率调度: 学习率调度器(Learning Rate Scheduler)控制着训练过程中学习率的变化。常见的调度策略包括余弦退火(cosine annealing)和WSD(Warmup-Stable-Decay)等,它们有助于模型在训练初期稳定学习,并在后期精细调整。
- 批处理大小: 批处理大小(Batch Size)是指每次模型参数更新时使用的样本数量。选择合适的批处理大小至关重要,它会影响训练的稳定性和效率,例如“临界批处理大小”(critical batch size)的概念。
- 正则化: 正则化技术用于防止模型过拟合,提高泛化能力。常见的正则化方法包括Dropout(随机失活)和权重衰减(Weight Decay)。
- 超参数调优: 模型的性能高度依赖于超参数(如头数、隐藏维度等)的选择。通常采用网格搜索(Grid Search)或随机搜索等方法来寻找最优的超参数组合。
作业1
作业1是本课程的第一个实践项目,旨在让学生亲手实现语言模型的基础组件:
- 实现BPE分词器: 学生需要从头开始实现字节对编码(BPE)分词器,理解其工作原理和合并过程。
- 实现Transformer、交叉熵损失、AdamW优化器和训练循环: 学生将构建Transformer模型的核心部分,并实现用于语言建模的交叉熵损失函数,以及AdamW优化器和完整的训练循环。
- 在TinyStories和OpenWebText上进行训练: 学生将在两个不同的数据集上训练他们的模型:TinyStories(一个用于生成简单故事的小型数据集)和OpenWebText(一个更大规模的文本数据集)。
- 排行榜: 作业1还设有一个排行榜,学生的目标是在给定90分钟H100显卡预算的情况下,最小化模型在OpenWebText上的困惑度(perplexity)。这鼓励学生不仅要实现功能,还要优化模型的效率和性能。
系统 (Systems)
“系统”部分的目标是深入探讨如何最大限度地利用硬件资源来训练和部署大型语言模型。在当前计算资源仍然是瓶颈的背景下,理解并优化系统层面的效率至关重要。
分词的进一步讨论
尽管分词是当前语言模型处理文本的必要步骤,但课程也指出,分词是一个必要的“恶”(necessary evil)。这意味着它虽然解决了将文本转换为模型可处理的数字序列的问题,但也引入了额外的复杂性和潜在的效率损失。理想情况下,如果能够直接从原始字节进行处理,将能够避免分词带来的各种问题,例如词汇表大小、未知词(OOV)问题以及分词器本身的计算开销。然而,正如前面所讨论的,直接处理字节会导致序列过长,对于当前的Transformer架构而言,这在计算上是不可行的。因此,未来的研究方向之一可能是开发能够高效处理原始字节的模型架构,从而彻底摆脱对分词器的依赖。
Tokenizer 抽象接口
为了更好地组织和管理分词器的实现,课程定义了一个抽象接口Tokenizer
。这个接口规定了所有分词器都必须实现的两个核心方法:
encode(self, string: str) -> list[int]
: 这个方法接收一个字符串作为输入,并将其转换为一个整数列表(即token序列)。这是将人类语言转换为模型可理解格式的关键步骤。decode(self, indices: list[int]) -> str
: 这个方法接收一个整数列表(token序列)作为输入,并将其解码回原始的字符串。这是将模型的输出(token序列)转换回人类可读语言的逆过程。
通过定义这样的抽象接口,可以确保不同类型的分词器(如字符级、字节级或BPE分词器)都遵循相同的规范,从而提高了代码的可维护性和可扩展性。
BPETokenizerParams 数据类
BPETokenizerParams
是一个数据类,它封装了定义一个BPE分词器所需的所有参数。使用数据类的好处是它提供了一种简洁的方式来创建只包含数据(而非行为)的类,并且自动生成了__init__
、__repr__
等方法,使得代码更加清晰和易于管理。这个数据类包含两个核心属性:
vocab: dict[int, bytes]
: 这是一个字典,将整数索引映射到其对应的字节序列。这个词汇表包含了所有在BPE训练过程中学习到的token,包括原始字节和合并后的字节对。merges: dict[tuple[int, int], int]
: 这是一个字典,记录了BPE训练过程中执行的所有合并操作。每个键是一个元组,表示要合并的两个相邻token的索引;对应的值是合并后新生成的token的索引。这个字典定义了BPE分词器如何将更小的单元逐步合并成更大的单元。
CharacterTokenizer (字符级分词器)
字符级分词器是一种最简单的分词方法,它将字符串中的每一个Unicode字符都视为一个独立的token。其实现方式如下:
encode
方法:通过Python内置的ord()
函数,将字符串中的每个字符转换为其对应的Unicode码点(一个整数)。例如,`ord(
a
会返回97,
ord(“🌍”)`会返回127757。
decode
方法:通过Python内置的chr()
函数,将Unicode码点转换回对应的字符。例如,chr(97)
会返回"a"
,chr(127757)
会返回"🌍"
。
优点: 简单直观,不会出现未知词(UNK token)的问题,因为任何字符都有对应的Unicode码点。
问题:
- 词汇量巨大: Unicode字符集非常庞大,包含约15万个字符。这意味着字符级分词器需要维护一个非常大的词汇表,这会增加模型的复杂性和内存消耗。
- 效率低下: 许多Unicode字符(如表情符号或不常用的语言字符)非常罕见。将这些罕见字符作为独立的token会使得词汇表的使用效率低下,因为模型需要为这些低频token分配参数,但它们在训练数据中出现的次数很少,导致模型难以有效学习其表示。
- 压缩率低: 字符级分词器对文本的压缩能力非常有限,因为每个字符都被视为一个独立的单元。这意味着生成的token序列会非常长,对于Transformer等依赖于序列长度的模型来说,这会显著增加计算成本(尤其是注意力机制的二次方复杂度)。
ByteTokenizer (字节级分词器)
字节级分词器是另一种简单的分词方法,它将字符串表示为一系列字节。由于计算机内部最终处理的都是字节,这种方法在理论上更加接近底层。最常用的Unicode编码是UTF-8,它是一种变长编码,可以将Unicode字符编码为1到4个字节。
encode
方法:首先使用UTF-8编码将字符串转换为字节序列(string.encode("utf-8")
),然后将每个字节转换为一个整数(0-255)。例如,英文字符"a"
编码为b"a"
(一个字节),而表情符号"🌍"
编码为b"\xf0\x9f\x8c\x8d"
(四个字节)。decode
方法:将整数列表(字节序列)转换回字节串(bytes(indices)
),然后使用UTF-8解码将其转换回字符串(string_bytes.decode("utf-8")
)。
优点:
- 词汇量小且固定: 字节的取值范围是0到255,因此字节级分词器的词汇量固定为256个,这比字符级分词器小得多,易于管理。
- 不会出现未知词: 任何字符串都可以被编码为字节序列,因此不会出现无法表示的“未知词”问题。
问题:
- 压缩率极低: 字节级分词器对文本的压缩能力非常差,因为每个字节都被视为一个独立的token。对于多字节字符(如中文字符或表情符号),一个字符可能被分解成多个token。这意味着生成的token序列会非常长,甚至比字符级分词器更长。例如,一个中文字符在UTF-8中通常占用3个字节,那么它就会被分解成3个token。这种极低的压缩率导致模型需要处理非常长的输入序列,这对于Transformer等模型来说是巨大的计算负担,因为其注意力机制的计算复杂度与序列长度的平方成正比。因此,尽管字节级分词器在理论上很“纯粹”,但在实际应用中,由于序列长度问题,它在当前主流模型中并不实用。
WordTokenizer (词汇分词)
词汇分词是一种更接近人类语言直觉的方法,它将字符串分割成独立的单词或标点符号。这种方法在传统自然语言处理(NLP)中非常常见。
- 分割规则: 通常使用正则表达式来识别单词边界,例如,将字母数字字符序列视为一个单词,而将标点符号视为独立的token。例如,句子“I’ll say supercalifragilisticexpialidocious!”可能会被分割成
["I", "'ll", "say", "supercalifragilisticexpialidocious", "!"]
。 - 映射到整数: 为了将这些分割后的词汇(segments)输入到模型中,需要为每个唯一的词汇分配一个整数ID,从而构建一个词汇表。
问题:
- 词汇量巨大: 英语中存在大量的单词,而且随着新词的不断出现,词汇量会持续增长。这导致词汇表非常庞大,类似于Unicode字符集的问题。
- 稀有词问题: 许多单词非常罕见,在训练数据中出现的频率很低。模型难以有效学习这些稀有词的表示,导致性能下降。
- 固定词汇表难以实现: 词汇分词很难提供一个固定大小的词汇表。如果预先设定一个固定大小的词汇表,那么未在词汇表中的新词或罕见词就需要被特殊处理。
- 未知词 (UNK token): 对于在训练过程中未见过的单词,通常会将其替换为一个特殊的“未知词”(UNK)token。这种处理方式不仅“丑陋”,而且会丢失原始信息,并可能影响模型的困惑度计算,因为模型无法区分不同的未知词。
Byte Pair Encoding (BPE) (字节对编码)
字节对编码(BPE)是一种混合了字符级和词汇级分词优点的分词方法,它在当前大型语言模型中得到了广泛应用。BPE算法最初由Philip Gage在1994年提出,用于数据压缩。后来,Sennrich等人在2016年将其应用于神经机器翻译,取得了显著成功,并被GPT-2等模型采纳。
基本思想: BPE的核心思想是训练一个分词器,使其能够自动从原始文本中学习出一个最优的词汇表。其直觉是:频繁出现的字符序列(或子词单元)应该被合并成一个单一的token,而罕见的序列则可以由多个较小的token组成。这在词汇量大小和序列长度之间取得了良好的平衡。
训练过程: BPE的训练过程是一个迭代的合并过程:
- 初始化: 首先,将文本中的每个字节(或字符,取决于初始设置)都视为一个独立的token。例如,对于字符串“the cat in the hat”,初始token序列可能是
['t', 'h', 'e', ' ', 'c', 'a', 't', ' ', 'i', 'n', ' ', 't', 'h', 'e', ' ', 'h', 'a', 't']
。 - 统计频率: 统计所有相邻token对的出现频率。例如,
('t', 'h')
、('h', 'e')
等。 - 选择最常见对: 找到出现频率最高的相邻token对。
- 合并: 将这个最常见的token对合并成一个新的token,并将其添加到词汇表中。例如,如果
('t', 'h')
是最常见的对,就将其合并为'th'
。 - 更新序列: 在文本中,所有出现该对的地方都替换为新的token。
- 重复: 重复步骤2-5,直到达到预设的合并次数,或者没有新的合并可以进行。
通过这种迭代合并,BPE能够逐步构建出一个包含常用子词单元的词汇表。例如,"ing"
、"tion"
、"un"
等常见的词缀或音节会被合并成独立的token,而整个单词(如"tokenization"
)也可能被合并成一个token。对于不常见的单词,它会被分解成更小的、常见的子词单元,从而避免了未知词问题。
merge
函数: 这个辅助函数负责在给定token序列中,将所有出现的特定token对替换为新的token。它遍历输入序列,如果遇到要合并的pair
,就将其替换为new_index
,否则保留原始token。
train_bpe
函数: 这个函数实现了BPE的训练逻辑。它接收一个字符串和要执行的合并次数作为输入。在每次迭代中,它会统计当前token序列中所有相邻token对的频率,找出最常见的对,然后将其合并成一个新的token,并更新词汇表和当前token序列。新token的索引通常从256(字节的范围)开始递增。
使用分词器: 一旦BPE分词器通过训练获得了词汇表和合并规则,就可以用它来编码新的文本。BPETokenizer
类会存储这些训练好的参数。当调用encode
方法时,它会首先将输入字符串转换为初始的字节序列,然后按照训练时学习到的合并规则,迭代地应用合并操作,直到无法再进行合并。decode
方法则执行相反的操作,将token序列转换回字节序列,最终解码为字符串。
作业1中BPE的改进点: 在作业1中,学生需要对BPE的实现进行优化和扩展:
- 优化
encode()
效率: 当前的encode()
实现可能效率低下,因为它会循环遍历所有合并规则。学生需要优化这一过程,只关注那些实际存在于当前序列中的合并。 - 特殊token处理: 语言模型通常会使用一些特殊的token(如
<|endoftext|>
表示文本结束)。学生需要确保分词器能够检测并保留这些特殊token,不将其分解。 - 预分词: 在应用BPE之前,可以先进行一步“预分词”(pre-tokenization),例如使用GPT-2分词器中使用的正则表达式,将文本初步分割成单词或标点符号,然后再在这些初步分割的单元上应用BPE。这可以提高分词的效率和质量。
- 性能优化: 尽可能地提高BPE实现的运行速度,这对于处理大规模文本数据至关重要。
更多推荐
所有评论(0)