大芳芳 發表於 2026-2-19 17:34:00

Stanford-CS336-Lecture-01 Tokenization

<h1 id="1资源">1.资源</h1>
<p><strong>课件资料</strong>:https://github.com/stanford-cs336/spring2025-lectures</p>
<p>clone下来后,将var目录移动到trace-viewer中的public目录内,将images目录也移动到public目录内,然后cd trace-viewer,以此命令npm install和npm run dev,到浏览器打开如下链接来看第一章的课件:</p>
<p>http://localhost:5173?trace=/var/traces/lecture_01.json</p>
<p>这个链接只是示例,localhost后面的端口根据自己运行实际情况而定。不同的课程有对应的json。</p>
<p><strong>相关中文博客</strong>:https://www.cnblogs.com/apachecn/p/19577358</p>
<p><strong>其他可能参考的资料</strong>:https://datawhaler.feishu.cn/wiki/SBGEw3kFfipFQbkStGocaFVHnSf</p>
<h1 id="2为什么要学standford-cs336">2.为什么要学Standford CS336?</h1>
<ul>
<li>理解大模型底层逻辑。</li>
<li>为你提供如何选择、处理数据,以及如何建模的直觉。</li>
<li>纠正资源至上心态,优化算法能最大化资源利用效率。<strong>精度 = 算法效率 x 资源投入。</strong></li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172846168-1429216727.png" alt="cs336_0" loading="lazy"></p>
<h1 id="3课程大纲">3.课程大纲</h1>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219173006088-1206163192.png" alt="cs336_1" loading="lazy"></p>
<h1 id="4tokenization概览">4.Tokenization概览</h1>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172959371-46406812.png" alt="cs336_2" loading="lazy"></p>
<p>Tokenization的作用是将字符串分割为若干片段,这些片段称为token,并为这些片段逐个赋予一个数字,这个过程称为Tokenization的编码。而这些编码片段也可以通过解码得到原来的字符串。</p>
<p>课程将以<strong>BPE(Byte-Pair Encoding)Tokenization</strong>为例进行tokenization技术的讲解,并要求你在无AI Tools和少量Pytorch API调用的情况下实现一个BPE。</p>
<p>另外可以提到也有无Tokenization技术,主要通过直接读取字节来实现语言建模。然而在现在的前沿模型中,Tokenization技术已经十分常见,课程暂且不论无Tokenization的方法。</p>
<h1 id="5最大化硬件效率">5.最大化硬件效率</h1>
<p>在训练的时候,加快训练速度,最大化硬件资源效率的一个Trick是,<strong>最大化GPU的利用率</strong>。</p>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172954366-831909565.png" alt="cs336_3" loading="lazy"></p>
<p>在训练模型的过程中,GPU的作用是负责计算和模型训练,CPU的职责是负责将模型参数、optimizer参数以及其他超参从外存运到内存,从内存运到GPU中。GPU的速度是相当快的,CPU则在进行数据传输中疲于奔命。因此常常会遇到GPU利用率低的问题,这通常是发生在GPU等待CPU数据传输的情况。所以处理好数据传输,能很好地提高训练速度,最大化硬件资源效率,即<strong>最大化硬件资源效率等价于最小化数据传输</strong>。</p>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172949333-1316752764.png" alt="cs336_4" loading="lazy"></p>
<p>当进行多个GPU并行训练时,数据之间的传输更加慢,<strong>最大化硬件资源效率等价于最小化数据传输</strong>依然正确。</p>
<p>这时候有若干并行性方法去应对这个问题,比如data parallelism,tensor parallelism等。</p>
<h1 id="6推理">6.推理</h1>
<h2 id="推理成本训练成本">推理成本&gt;训练成本:</h2>
<p>随着时间的推移,推理上的成本要超过训练上的成本,这是因为训练的成本通常是一次性的,而推理是按次数而言的,随着用户量增大,使用率提高,推理次数将指数级上升。</p>
<h2 id="推理的两个阶段">推理的两个阶段:</h2>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172944798-389440052.png" alt="cs336_5" loading="lazy"></p>
<p>分为<strong>预填充(Prefill)和解码(Decode)</strong>阶段。预填充一次性将用户提供的prompt tokens等上下文处理完毕,包括计算每个位置的隐藏状态以及最后一个位置的logits然后存储KV-Cache,而解码则是在生成过程中逐步追加新 token,每生成一个 token,上下文就变长一点,模型需要用“旧的 KV 缓存 + 新 token”再算一次下一个 token 的分布,这个过程将会重复直到生成结束。</p>
<p>因此时间上来讲,预填充是很快的,耗时主要集中于解码。因此有一些手段来加速解码,比如<strong>使用小一些的模型进行推理</strong>(模型蒸馏、模型剪枝、模型量化),或者是<strong>一些特别的策略</strong>(先用小而快的模型连续生成一段候选 token,然后由大的模型一次性验证这段候选合理性以决定是否采纳),又或者是<strong>在系统方面进行优化</strong>(优化KV-Cache等)。</p>
<h1 id="7scaling-laws">7.Scaling laws</h1>
<p>有时候为大模型做实验时,会遇到这样的问题:在算力预算C是固定的情况下,模型参数N应该选多大,数据tokens数D应该为多少,才能最小化自己模型的困惑度?</p>
<p>我们通常会觉得,如果模型越大,其能表达更复杂的规律,但如果给它喂的数据太少就“吃不饱”。数据量越大,越能让模型充分地学习,但对于小模型会“吃不下”。所以存在一个“compute-optimal”的平衡点。</p>
<p>假设训练一个Transformer的FLOPs量大致可以写成:<strong>C≈kND</strong></p>
<ul>
<li>C:总训练FLOPs</li>
<li>N:参数量</li>
<li>D:训练tokens数</li>
<li>k:与架构细节有关的常数</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172940910-2016725326.png" alt="cs336_6" loading="lazy"></p>
<p>左图的每一条曲线都存在一个临界值,在临界值左边,模型太小,表达能力不够,损失较高。右边则是模型太大,训练效率不足导致的损失上升。将上面公式变形后有:<strong>D / N ≈ C / (kN²)</strong>。这个公式代表着单个参数所需训练的tokens数,即在固定FLOPs下随着模型参数N变大,每个参数见到的tokens变少,因此训练效率变低。</p>
<p>中间的图表示给定一个算力值FLOPs,模型大概需要多少参数。而右边的图则表示给定一个算力值FLOPs,模型大概需要多少tokens来训练。</p>
<p>Scaling Laws for Neural Language Models 和 Training Compute-Optimal Large Language Models 这两篇文章揭示了这样一个结论:<strong>如果你想让模型在当前算力下达到最优,那N和D大概满足 D ≈ 20 N</strong>。这是一种scaling law。</p>
<p>然而,具体的模型和实验,有自己的scaling laws。上面两篇论文的工作旨在<strong>启发我们要在小规模实验中寻找自己模型的scaling laws,得到相关规律后预测在大规模实验和应用情境下的表现。</strong></p>
<h1 id="8对齐">8.对齐</h1>
<h2 id="对齐方式">对齐方式:</h2>
<p>简单讲一下Alignment。大模型可以分成两种,一种是<strong>基础大模型</strong>,另一种是<strong>指令微调大模型</strong>。一般来说,搜集大量数据,用大量显卡训练许久的基础大模型,它能够很好地完成预测下一个token的任务,但有时候他所生成的内容并非我们用户想要得到的。</p>
<p>比如,GPT-4从大量的网络数据中训练得来,未经对齐你就向其发送这样的字符串:“法国的首都是?”那么其实很有可能GPT-4会回复:“法国首都的经纬度是?”类似这样与回答无关的内容。这是因为它可能是从一个有关法国首都的问题清单中训练的,当你询问其中一个问题时,大模型会返回这个问题后面最后可能出现的内容。</p>
<p>这并非用户想要得到的回答。因此就有了指令微调大模型。基础大模型拥有了强大的预测tokens的能力,便可以用<strong>指令-回答对</strong>进行微调,以能够得到强大的问答能力。这是一种对齐方式。</p>
<p>另外还有其他的对齐方式,比如:<strong>微调大模型的回答风格</strong>(回答格式、回答长度以及回答语气等),还有一些<strong>安全性的因素</strong>(监督大模型生成的有害、错误的回答)。</p>
<p>相关的对齐方法有<strong>监督微调</strong>(supervised_finetuning),<strong>从反馈中学习</strong>(learning_from_feedback)。</p>
<h2 id="监督微调">监督微调:</h2>
<p>又称<strong>SFT</strong>。给大模型微调的数据格式通常是一问一答:</p>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172936683-1876461764.png" alt="cs336_7" loading="lazy"></p>
<p>数据除了一部分是合成数据,大多是人工标注的数据,获取成本和难度较高。</p>
<p>因为基础大模型已经具备了强大的tokens预测能力,因此用SFT进行微调的时候不必需要大量的数据,LIMA: Less Is More for Alignment这篇文章表明大概只需要1000对数据即可,SFT主要做的只是让大模型能够从预测tokens转向回答和完成用户的问题和要求。</p>
<h2 id="从反馈中学习">从反馈中学习:</h2>
<p><img src="https://img2024.cnblogs.com/blog/3339047/202602/3339047-20260219172931864-159646816.png" alt="cs336_8" loading="lazy"></p>
<p>从反馈中学习指的是,给大模型一些基本的例子,在这些例子中问题的回答有两种风格,由人选择其中一个偏爱的回答来微调模型的输出偏好。</p>
<p>可以用一些<strong>形式验证器</strong>(适用于代码或者数学的输出)或者<strong>训练过的验证器</strong>(训练一个LLM对抗器)对结果是否符合偏好进行验证。</p>
<h1 id="9tokenization详解">9.Tokenization详解</h1>
<p>给一个字符串"I love LLM",对每一个Unicode字符,UTF-8编码可以将其转换为8位二进制序列,得到:</p>
<p>01001001 00100000 01101100 01101111 01110110 01100101 00100000 01001100 01001100 01001101</p>
<p>这样的indices序列长度是80,即tokens长度是80。因为是8位二进制序列,因此这样的序列字节数是10,压缩比是1 / 8。也就是说每一个token只能代表 1 / 8 个字节。这样的序列太长了,太长的序列,LLM处理所需的时间就更久,我们当然<strong>希望一个token能代表尽可能多的字节信息,使得序列长度尽可能低</strong>。</p>
<h2 id="byte-tokenization">Byte Tokenization:</h2>
<p>在上面的例子中,用来表示字符的token只有0和1两种。将token的种类汇总为字典,字典的大小为2。因此这种基于二进制位的tokenization方式,特点是序列长,字典小。效率更高的方式应该让序列更短,因此通常用字典大小换序列长。</p>
<p>将8位二进制转换为整数,得到:</p>
<p>73 32 108 111 118 101 32 76 76 77</p>
<p><strong>一个整数代表一个字节的信息,一个token就是一个字节</strong>。整数的范围是0-255。因此字典大小为256。indices的长度是token的长度,因为像73这样的数字在字典中算一个token,所以indices长度是10。可以计算一下压缩比为1。这样的序列其实还是太长,效率还不够。</p>
<h2 id="character-tokenization">Character Tokenization:</h2>
<p>那么可以考虑<strong>将Unicode字符表当成token字典,一个字符就是一个token</strong>。ASCll字符是单字节字符,也就是一个字节一个字符,因此全为ASCll字符的字符串,压缩比为1。对于非ASCll字符,比如emoji和中文或者其他语言,通常是多个字节一个字符,因此这时一个token能表示多个字节的信息,此时压缩比通常大于1。Unicode 字符大约有 15 万个(150K),因此token字典大概也有15万个。让我们来计算一下这里的压缩比。压缩比是一个token能表达的字节信息量。因此压缩比也是1。</p>
<p>然而,CharacterTokenization的问题是词表(token表)太大,实际高频使用的只有一小部分,如此巨大的词表使得embedding矩阵相当大,占显存,拖训练和推理速度。对于一些冷门的字符,其与其他高频的字符占用相同的空间大小。更何况,压缩比也不算十分高。</p>
<h2 id="word-tokenization">Word Tokenization:</h2>
<p>这个tokenization的想法是<strong>把一整段字符串按词分开,一个词或者一个标点当作一个token</strong>,然后给这些不同的词分配整数id,当作词表的一个条目。</p>
<p>通常这种简单的切分方法可以用regex.findall(r"\w+|.", string)实现,string是需要切分的字符串,\w+分出一个词,点号分出一个标点。比如“I love LLM”会被分成“I”、“love”、“LLM”。</p>
<p>GPT-2的做法是把单词前面的空格也算在切分的单词里,比如“I love LLM”会被分成“I”、“ love”、“ LLM”。这样能更好处理数字、标点和空白等情况。</p>
<p>切分完毕后,收集训练语料的所有非重复segement,然后为它们分别分配一个整数id。</p>
<p>计算一下压缩比。可以知道这时token只有3个,压缩比为 10 / 3 ≈ 3.33。压缩比较前面的大大提高了。</p>
<p>然而,问题也很明显。由于自然语言中<strong>单词非常多,因此词表也还是很巨大</strong>。训练时,语料都能在词表字典中找到以映射为整数喂给大模型。但是如果在测试遇到了词表字典中没见过的新词(新造词或者错别字),就会发生<strong>未登录词(OOV)问题</strong>,只能将这些词映射为统一的UNK,这会增加困惑度。</p>
<h2 id="byte-pair-encodingbpe-tokenization">Byte-Pair Encoding(BPE) Tokenization:</h2>
<p>BPE在Byte Tokenization基础上做了进一步优化,<strong>它在indices进行合并同类项的操作,把高频的词或词片段作为一个 token</strong>。</p>
<p>以“the cat in the hat”为例,首先将字符串按Byte Tokenization的方式<strong>编码为字节</strong>,得到整数序列:116 104 101 32 99 97 116 32 105 110 32 116 104 101 32 104 97 116。这是保证不会发生OOV的基础,这是因为无论什么字符(包括中文、emoji),都能拆成 byte,被覆盖到。</p>
<p>词典范围依然是0-255,长度为256。</p>
<p><strong>统计相邻token对的频率,然后选出频率最高的一对</strong>。比如(116,104)出现了2次,(104,101)出现了2次,(32,99)出现了1次,选择频率最高的(104,101),虽然(116,104)也是最高的,但并没有什么影响。说明104和101<strong>可能是某个有意义的子单元,于是将它们进行同类项合并</strong>。</p>
<p><strong>为这个频率最高的Pair分配一个新整数id,然后在indices中将这个Pair替换为这个新整数</strong>。比如将(104,101)分配为256,这个数字正好是词典当前长度。然后在indices中将所有(104,101)替换为256。</p>
<p><strong>重复执行以上操作多次</strong>,就能优化序列长度。比如执行3次,得到258 99 97 116 32 105 110 32 258 104 97 116,具体是将(116,104)合并为256,将(256,101)合并为257,(257,32)合并为258。</p>
<p>上面的例子,最终的indice长度为12,原先为18。计算一下压缩比,压缩比为 18 / 12 ≈ 1.5。</p>
<p>看起来不是很高?上面的合并还不算是最优的结果,毕竟只执行了3次循环。你需要训练一个BPE,使得压缩比尽可能高。</p>
<p>GPT-4,LLaMA等主流模型都在用BPE或其变体,BPE确实在词表大小、序列长度、鲁棒性之间做了很好的均衡。</p><br><br>
来源:https://www.cnblogs.com/RunfarAI/p/19625179
頁: [1]
查看完整版本: Stanford-CS336-Lecture-01 Tokenization