<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Let's learn Stanford CS336 - LLM Systems! | Lin Shang</title><link>https://lin-shang.github.io/courses/stanford-cs-336/</link><atom:link href="https://lin-shang.github.io/courses/stanford-cs-336/index.xml" rel="self" type="application/rss+xml"/><description>Let's learn Stanford CS336 - LLM Systems!</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sun, 14 Dec 2025 00:00:00 +0800</lastBuildDate><image><url>https://lin-shang.github.io/courses/stanford-cs-336/featured.jpg</url><title>Let's learn Stanford CS336 - LLM Systems!</title><link>https://lin-shang.github.io/courses/stanford-cs-336/</link></image><item><title>CS336 lecture-01 - tokenizer</title><link>https://lin-shang.github.io/courses/stanford-cs-336/section-01_lecture_01/</link><pubDate>Tue, 16 Dec 2025 00:00:00 +0800</pubDate><guid>https://lin-shang.github.io/courses/stanford-cs-336/section-01_lecture_01/</guid><description>
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;&lt;strong&gt;致谢&lt;/strong&gt;：本单元内容的灵感来源于 Andrej Karpathy 关于 Tokenization 的
，强烈推荐观看。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在语言模型中，&lt;strong&gt;Tokenizer（分词器）&lt;/strong&gt; 扮演着“翻译官”的角色，负责在 &lt;strong&gt;字符串（Strings）&lt;/strong&gt; 和 &lt;strong&gt;Token 序列（Indices）&lt;/strong&gt; 之间进行转换。 语言模型本质上是对 Token 序列（通常由整数索引表示）的概率分布进行建模。
例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入字符串：&lt;code&gt;&amp;quot;Hello, 🌍! 你好!&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Token 序列：&lt;code&gt;[15496, 11, 995, 0]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们需要一个过程将字符串编码（Encode）为 Token，也需要一个过程将 Token 解码（Decode）回字符串。虽然将文本视为 Unicode 字符序列或字节序列在理论上是可行的，但在实际应用中，基于 &lt;strong&gt;BPE（字节对编码）&lt;/strong&gt; 的方法是目前最高效的启发式算法。&lt;/p&gt;
&lt;p&gt;以下我们将逐步演进，从最简单的分词方法直到 BPE。&lt;/p&gt;
&lt;h2 id="1-基础分词方法的尝试与局限"&gt;1. 基础分词方法的尝试与局限&lt;/h2&gt;
&lt;p&gt;为了直观感受分词器的工作原理，我们可以先定义一个抽象基类，并尝试几种朴素的实现。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstractmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BPETokenizerParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;All you need to specify a BPETokenizer.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# index -&amp;gt; bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;merges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# index1, index2 -&amp;gt; new_index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="11-字符级分词-character-based-tokenization"&gt;1.1 字符级分词 (Character-based Tokenization)&lt;/h3&gt;
&lt;p&gt;Unicode 字符串本质上是字符的序列。我们可以直接利用&lt;code&gt;ord()&lt;/code&gt; 将字符转换为码点（整数），用 &lt;code&gt;chr()&lt;/code&gt; 转回字符。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CharacterTokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tokenizer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;将字符串表示为 Unicode 码点序列&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;chr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 测试&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# string = &amp;#34;Hello, 🌍! 你好!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ord(&amp;#34;a&amp;#34;) == 97, ord(&amp;#34;🌍&amp;#34;) == 127757&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;局限性：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;词表过大&lt;/strong&gt;：Unicode 字符集大约有 &lt;strong&gt;1百万&lt;/strong&gt;个字符，这意味着模型的词汇表（Vocabulary Size）非常庞大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;效率低下&lt;/strong&gt;：许多字符（如 🌍）非常罕见，占据了词表空间却很少被学习到，这是对资源的浪费。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="12-字节级分词-byte-based-tokenization"&gt;1.2 字节级分词 (Byte-based Tokenization)&lt;/h3&gt;
&lt;p&gt;为了解决词表过大的问题，我们可以将 Unicode 字符串视为字节序列（UTF-8 编码）。每个字节由 0 到 255 之间的整数表示。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ByteTokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tokenizer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;将字符串表示为字节序列&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;string_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string_bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;string_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string_bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 测试&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# bytes(&amp;#34;a&amp;#34;, encoding=&amp;#34;utf-8&amp;#34;) == b&amp;#34;a&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# bytes(&amp;#34;🌍&amp;#34;, encoding=&amp;#34;utf-8&amp;#34;) == b&amp;#34;\xf0\x9f\x8c\x8d&amp;#34; (4个字节)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;局限性：&lt;/strong&gt;&lt;br&gt;
虽然词表大小完美地控制在 256，但&lt;strong&gt;压缩率（Compression Ratio）&lt;/strong&gt; 极差。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Compression Ratio = Bytes / Tokens&lt;/code&gt;在这里，比率为 1。&lt;/li&gt;
&lt;li&gt;这意味着序列会变得非常长。由于 Transformer 的注意力机制计算复杂度是序列长度的平方级，过长的序列会导致推理极其缓慢且上下文窗口被浪费。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-blue-100 dark:bg-blue-900 border-blue-500"
data-callout="note"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-blue-600 dark:text-blue-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8l.8-2.685a4.5 4.5 0 0 1 1.13-1.897zm0 0L19.5 7.125"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;&lt;strong&gt;ASCII，Unicode，UTF-8 到底是什么？&lt;/strong&gt; &amp;mdash;&amp;gt; 这篇&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;p&gt;值得深入学习!&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="13-词级分词-word-based-tokenization"&gt;1.3 词级分词 (Word-based Tokenization)&lt;/h3&gt;
&lt;p&gt;这是传统 NLP 中常用的方法，使用正则表达式按空格或标点切分。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# GPT-2 使用的正则模式示例&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;GPT2_TOKENIZER_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&amp;#39;s|&amp;#39;t|&amp;#39;re|&amp;#39;ve|&amp;#39;m|&amp;#39;ll|&amp;#39;d| ?\p&lt;/span&gt;&lt;span class="si"&gt;{L}&lt;/span&gt;&lt;span class="s2"&gt;+| ?\p&lt;/span&gt;&lt;span class="si"&gt;{N}&lt;/span&gt;&lt;span class="s2"&gt;+| ?[^\s\p&lt;/span&gt;&lt;span class="si"&gt;{L}&lt;/span&gt;&lt;span class="s2"&gt;\p&lt;/span&gt;&lt;span class="si"&gt;{N}&lt;/span&gt;&lt;span class="s2"&gt;]+|\s+(?!\S)|\s+&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;word_tokenizer_demo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 简单的正则切分&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;\w+|.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;局限性：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;词汇量依然巨大（甚至比 Unicode 字符还多）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UNK 问题&lt;/strong&gt;：训练中未见过的词（Out of Vocabulary）会被标记为 &amp;lt;UNK&amp;gt;，这会破坏信息的完整性并影响困惑度（Perplexity）的计算。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-最佳实践-byte-pair-encoding-bpe"&gt;2. 最佳实践：
(BPE)&lt;/h2&gt;
&lt;p&gt;BPE(字节对编码)最初由 Philip Gage 在 1994 年作为
提出，后被
工作引入 NLP 领域（在那之前，基于词的 tokenizer 方法是主流），并被
等现代大模型广泛采用。&lt;/p&gt;
&lt;h3 id="21-核心思想"&gt;2.1 核心思想&lt;/h3&gt;
&lt;p&gt;BPE的 基础想法：在一个原始文本上训练一个分词器来确定一个词表。
BPE 的核心直觉是：常见字符序列用单个 Token 表示，罕见序列用多个Token 表示。它是“字符级”和“词级”的折中方案。&lt;/p&gt;
&lt;p&gt;GPT-2的论文使用基于词的分词方法将文本拆分为初始片段，并在每个片段上运行原始的BPE算法。&lt;/p&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-emerald-100 dark:bg-emerald-900 border-emerald-500"
data-callout="tip"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-emerald-600 dark:text-emerald-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;&lt;strong&gt;BPE 算法流程草图&lt;/strong&gt;&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;初始化&lt;/strong&gt;：从字节级 Token 开始（初始词表大小为 256）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统计频率&lt;/strong&gt;：遍历语料，统计所有相邻 Token 对的出现频率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寻找最频对&lt;/strong&gt;：找到出现频率最高的一对（例如 &lt;code&gt;(e, s)&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合并与更新&lt;/strong&gt;：将这对 Token 合并为一个新的 Token（例如 &lt;code&gt;es&lt;/code&gt;，分配新索引 256），并更新词表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迭代&lt;/strong&gt;：重复上述步骤 2-4，直到达到预设的合并次数或目标词表大小。&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="22-训练一个-tokenizer"&gt;2.2 训练一个 tokenizer&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;the cat in the hat&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_bpe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_merges&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;train_bpe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_merges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BPETokenizerParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; 这里是一个简单的训练 bpe 的代码原型
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; 按照之前的设想，我们应该维护好下面三个变量：
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; 1. indices 所有原始文档的 utf-8 字节编码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; 2. merges 记录每次遍历时的相邻 token 共现计数器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; 3. 初始 vocab ，所有的词表拓展都从 256 个字节编码开始。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;merges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;# 对关联 index 进行合并 index1,index2 =&amp;gt; merged index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="c1"&gt;# index -&amp;gt; bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_merges&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 统计每对 tokens 的出现次数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index2&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt; &lt;span class="c1"&gt;# 抽取每一个相邻 pair&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;index1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 找到出现次数最多的 pair&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;index1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 融合 Pair ，新增 Token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;merges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BPETokenizerParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;merges&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;merges&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;new_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;返回`indices`，但将所有`pair`实例替换为`new_index`&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 初始化一个新的 indice 来完成对现有 indice 的更新&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_indices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_indices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_indices&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="23-使用tokenizer"&gt;2.3 使用tokenizer&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 推理阶段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BPETokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;test_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;the quick brown fox&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 编码与解码&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;reconstructed_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;test_string&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;reconstructed_string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;原始字符串: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Token ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="24-可交互-python-环境"&gt;2.4 可交互 python 环境&lt;/h3&gt;
&lt;p&gt;该交互环境仅为测试代码和想法使用，并未安装很多依赖包(e.g. torch)，建议不要执行很重的代码！&lt;/p&gt;
&lt;style&gt;
.py-ide-wrapper {
--ide-font: var(--global-code-font, monospace);
--ide-size: var(--global-code-size, 14.5px);
--ide-lh: var(--global-line-height, 1.6);
--ide-bg: #ffffff;
--ide-fg: #24292e;
--ide-border: #d0d7de;
--ide-header-bg: #f6f8fa;
--ide-btn-run: #2da44e;
--ide-btn-text: #ffffff;
--ide-shadow: 0 4px 12px rgba(0,0,0,0.08);
--tree-key: #005cc5;
--tree-val: #032f62;
--tree-type: #6a737d;
}
:root[data-theme="dark"] .py-ide-wrapper,
html.dark .py-ide-wrapper,
body.dark .py-ide-wrapper {
--ide-bg: #161b22;
--ide-fg: #d4d4d4;
--ide-border: #30363d;
--ide-header-bg: #0d1117;
--ide-btn-run: #238636;
--ide-btn-text: #ffffff;
--ide-shadow: 0 8px 24px rgba(0,0,0,0.5);
--tree-key: #79c0ff;
--tree-val: #a5d6ff;
--tree-type: #8b949e;
}
.py-ide-container {
background-color: var(--ide-bg);
color: var(--ide-fg);
border: 1px solid var(--ide-border);
border-radius: 8px;
margin: 2rem 0;
overflow: hidden;
display: flex; flex-wrap: wrap; width: 100%;
box-shadow: var(--ide-shadow);
transition: all 0.3s ease;
}
.py-header {
height: 40px; padding: 0 12px; display: flex; justify-content: space-between; align-items: center;
background-color: var(--ide-header-bg); border-bottom: 1px solid var(--ide-border);
font-family: -apple-system, sans-serif; font-size: 12px; font-weight: 600; color: var(--ide-fg);
}
.py-editor {
min-height: 200px; padding: 15px;
font-family: var(--ide-font) !important;
font-size: var(--ide-size) !important;
line-height: var(--ide-lh) !important;
background-color: transparent; color: inherit; outline: none; white-space: pre; overflow: auto;
}
.py-output {
padding: 10px 15px; border-top: 1px solid var(--ide-border);
background-color: rgba(127,127,127,0.05); color: var(--ide-fg);
font-family: var(--ide-font) !important;
font-size: 13px;
white-space: pre-wrap; max-height: 250px; overflow-y: auto;
}
.py-main-col { flex: 1; min-width: 300px; display: flex; flex-direction: column; border-right: 1px solid var(--ide-border); }
.py-sidebar-col { width: 260px; border-left: 1px solid var(--ide-border); display: flex; flex-direction: column; font-size: 13px; font-family: var(--ide-font); }
.py-btn-group { display: flex; gap: 8px; align-items: center; }
.py-run-btn {
background: var(--ide-btn-run) !important; color: var(--ide-btn-text) !important;
border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer;
font-weight: 600; font-size: 11px; box-shadow: 0 1px 2px rgba(0,0,0,0.1);
transition: transform 0.1s, opacity 0.2s;
}
.py-run-btn:active { transform: translateY(1px); }
.py-icon-btn {
background: transparent; color: var(--ide-fg); border: 1px solid var(--ide-border);
padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 11px;
}
.py-tree-container { padding: 10px; flex: 1; overflow-y: auto; line-height: 1.5; }
.t-node { margin-left: 10px; }
.t-key { color: var(--tree-key); font-weight: bold; }
.t-val { color: var(--tree-val); }
.t-type { color: var(--tree-type); font-size: 0.85em; margin-left: 5px; font-style: italic; }
.t-arrow { display: inline-block; width: 12px; cursor: pointer; opacity: 0.7; font-size: 10px; user-select: none; }
.t-children { display: none; border-left: 1px solid var(--ide-border); margin-left: 5px; padding-left: 5px; }
.t-children.open { display: block; }
.py-plot-container { background: white; padding: 15px; text-align: center; border-top: 1px solid var(--ide-border); display: none; }
&lt;/style&gt;
&lt;div class="py-ide-wrapper"&gt;
&lt;div class="py-ide-container"&gt;
&lt;div class="py-main-col"&gt;
&lt;div class="py-header"&gt;
&lt;span&gt;TERMINAL&lt;/span&gt;
&lt;div class="py-btn-group"&gt;
&lt;button id="copy-code-0" class="py-icon-btn" onclick="copyIdeCode('0')"&gt;COPY&lt;/button&gt;
&lt;button class="py-icon-btn" py-click="reset_code_0"&gt;RESET&lt;/button&gt;
&lt;button id="btn-0" class="py-run-btn" py-click="run_code_0"&gt;▶ RUN&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="editor-0" class="py-editor language-python" data-code="IyDov5nmmK/kuIDkuKrlj6/nvJbovpHnmoQgUHl0aG9uIOeOr&amp;#43;Wigwpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBkZWZhdWx0ZGljdAoKY2xhc3MgQlBFVG9rZW5pemVyUGFyYW1zOgogICAgIiIiQWxsIHlvdSBuZWVkIHRvIHNwZWNpZnkgYSBCUEVUb2tlbml6ZXIuIiIiCiAgICBkZWYgX19pbml0X18oc2VsZiwgdm9jYWIsIG1lcmdlcyk6CiAgICAgICAgc2VsZi52b2NhYiA9IHZvY2FiCiAgICAgICAgc2VsZi5tZXJnZXMgPSBtZXJnZXMKCiMg6L6F5Yqp5Ye95pWwIG1lcmdlIOmcgOimgeWFiOWumuS5ie&amp;#43;8jOaIluiAheaYr&amp;#43;aUvuWcqOexu&amp;#43;WklumdogpkZWYgbWVyZ2UoaW5kaWNlczogbGlzdFtpbnRdLCBwYWlyOiB0dXBsZVtpbnQsIGludF0sIG5ld19pbmRleDogaW50KSAtPiBsaXN0W2ludF06CiAgICBuZXdfaW5kaWNlcyA9IFtdCiAgICBpID0gMAogICAgd2hpbGUgaSA8IGxlbihpbmRpY2VzKToKICAgICAgICAjIOajgOafpeaYr&amp;#43;WQpuWMuemFjSBwYWlyCiAgICAgICAgaWYgaSArIDEgPCBsZW4oaW5kaWNlcykgYW5kIGluZGljZXNbaV0gPT0gcGFpclswXSBhbmQgaW5kaWNlc1tpKzFdID09IHBhaXJbMV06CiAgICAgICAgICAgIG5ld19pbmRpY2VzLmFwcGVuZChuZXdfaW5kZXgpCiAgICAgICAgICAgIGkgKz0gMgogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG5ld19pbmRpY2VzLmFwcGVuZChpbmRpY2VzW2ldKQogICAgICAgICAgICBpICs9IDEKICAgIHJldHVybiBuZXdfaW5kaWNlcwoKZGVmIHRyYWluX2JwZShzdHJpbmc6IHN0ciwgbnVtX21lcmdlczogaW50KSAtPiBCUEVUb2tlbml6ZXJQYXJhbXM6CiAgICAjIOWwhuWtl&amp;#43;espuS4sui9rOS4uiBVVEYtOCDlrZfoioLmlbTmlbDliJfooagKICAgIGluZGljZXMgPSBsaXN0KG1hcChpbnQsIHN0cmluZy5lbmNvZGUoInV0Zi04IikpKQogICAgbWVyZ2VzID0ge30gCiAgICB2b2NhYiA9IHt4OiBieXRlcyhbeF0pIGZvciB4IGluIHJhbmdlKDI1Nil9CiAgICAKICAgIGZvciBpIGluIHJhbmdlKG51bV9tZXJnZXMpOgogICAgICAgICMg57uf6K6h5q&amp;#43;P5a&amp;#43;5IHRva2VucyDnmoTlh7rnjrDmrKHmlbAKICAgICAgICBjb3VudHMgPSBkZWZhdWx0ZGljdChpbnQpCiAgICAgICAgCiAgICAgICAgIyDjgJDplJnor6/kv67mraMgMeOAkei/memHjOS5i&amp;#43;WJjeWGmeaIkOS6hiBpbmRpY2VbMTpd77yM5bqU6K&amp;#43;l5pivIGluZGljZXNbMTpdCiAgICAgICAgZm9yIGluZGV4MSwgaW5kZXgyIGluIHppcChpbmRpY2VzLCBpbmRpY2VzWzE6XSk6IAogICAgICAgICAgICBjb3VudHNbKGluZGV4MSwgaW5kZXgyKV0gKz0gMQogICAgICAgICAgICAKICAgICAgICBpZiBub3QgY291bnRzOgogICAgICAgICAgICBicmVhawogICAgICAgICAgICAKICAgICAgICAjIOaJvuWIsOWHuueOsOasoeaVsOacgOWkmueahCBwYWlyCiAgICAgICAgcGFpciA9IG1heChjb3VudHMsIGtleT1jb3VudHMuZ2V0KQogICAgICAgIGluZGV4MSwgaW5kZXgyID0gcGFpcgogICAgICAgIAogICAgICAgICMg6J6N5ZCIIFBhaXIg77yM5paw5aKeIFRva2VuCiAgICAgICAgIyDjgJDplJnor6/kv67mraMgMuOAkei/memHjOS5i&amp;#43;WJjeWumuS5ieS6hiBuZXdfcGFpcu&amp;#43;8jOS9huWQjumdoueUqOeahOaYryBuZXdfaW5kZXgKICAgICAgICBuZXdfaW5kZXggPSAyNTYgKyBpCiAgICAgICAgCiAgICAgICAgbWVyZ2VzW3BhaXJdID0gbmV3X2luZGV4CiAgICAgICAgdm9jYWJbbmV3X2luZGV4XSA9IHZvY2FiW2luZGV4MV0gKyB2b2NhYltpbmRleDJdCiAgICAgICAgCiAgICAgICAgIyDosIPnlKggbWVyZ2Ug5pu05pawIGluZGljZXMKICAgICAgICBpbmRpY2VzID0gbWVyZ2UoaW5kaWNlcywgcGFpciwgbmV3X2luZGV4KQogICAgICAgIAogICAgcmV0dXJuIEJQRVRva2VuaXplclBhcmFtcyh2b2NhYj12b2NhYiwgbWVyZ2VzPW1lcmdlcykKCiMg5rWL6K&amp;#43;V6L&amp;#43;Q6KGMCnN0cmluZyA9ICJ0aGUgY2F0IGluIHRoZSBoYXQiCnBhcmFtcyA9IHRyYWluX2JwZShzdHJpbmcsIG51bV9tZXJnZXM9MykKCnByaW50KCJUcmFpbmluZyBjb21wbGV0ZSEiKQpwcmludCgiTWVyZ2VzOiIsIHBhcmFtcy5tZXJnZXMpCnByaW50KCJWb2NhYiBzaXplOiIsIGxlbihwYXJhbXMudm9jYWIpKQ=="&gt;&lt;/div&gt;
&lt;div class="py-output" id="output-0"&gt;&gt; Ready.&lt;/div&gt;
&lt;div id="plot-header-0" class="py-header" style="display: none; justify-content: flex-end; border-top: 1px solid var(--ide-border);"&gt;
&lt;button id="copy-png-btn-0" class="py-icon-btn"&gt;Save PNG&lt;/button&gt;
&lt;/div&gt;
&lt;div class="py-plot-container" id="plot-0"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="py-sidebar-col"&gt;
&lt;div class="py-header"&gt;VARIABLES&lt;/div&gt;
&lt;div id="tree-0" class="py-tree-container"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;script type="module"&gt;
import { CodeJar } from 'https://cdn.jsdelivr.net/npm/codejar@3.7.0/codejar.min.js';
window.renderJsonTree = function(id, jsonStr) {
const container = document.getElementById(id);
if(!container) return;
let data = {};
try { data = JSON.parse(jsonStr); } catch(e) { return; }
if (Object.keys(data).length === 0) {
container.innerHTML = '&lt;div style="color:var(--tree-type); padding:5px;"&gt;(Empty)&lt;/div&gt;';
return;
}
const buildTree = (obj) =&gt; {
let html = '';
for (const [key, info] of Object.entries(obj)) {
const hasChild = info.children !== null;
const arrow = hasChild ? '▶' : ' ';
const val = info.val;
const type = info.type;
const childHtml = hasChild ? `&lt;div class="t-children"&gt;${buildTree(info.children)}&lt;/div&gt;` : '';
html += `
&lt;div class="t-node"&gt;
&lt;span class="t-arrow" onclick="toggleTree(this)"&gt;${arrow}&lt;/span&gt;
&lt;span class="t-key"&gt;${key}&lt;/span&gt;:
&lt;span class="t-val"&gt;${val}&lt;/span&gt;
&lt;span class="t-type"&gt;${type}&lt;/span&gt;
${childHtml}
&lt;/div&gt;`;
}
return html;
};
container.innerHTML = buildTree(data);
};
window.toggleTree = function(el) {
const children = el.parentElement.querySelector('.t-children');
if (children) {
const isOpen = children.classList.toggle('open');
el.innerText = isOpen ? '▼' : '▶';
}
};
window.copyIdeCode = function(ordinal) {
const el = document.getElementById(`editor-${ordinal}`);
if(el) {
navigator.clipboard.writeText(el.innerText).then(() =&gt; {
const btn = document.getElementById(`copy-code-${ordinal}`);
const old = btn.innerText; btn.innerText = "✓";
setTimeout(() =&gt; { btn.innerText = old; }, 1500);
});
}
};
document.addEventListener("DOMContentLoaded", () =&gt; {
const editorEl = document.getElementById("editor-0");
if (!editorEl) return;
const highlight = (editor) =&gt; {
if (window.Prism &amp;&amp; Prism.languages.python) {
editor.innerHTML = Prism.highlight(editor.textContent, Prism.languages.python, 'python');
}
};
const jar = CodeJar(editorEl, highlight, { tab: ' ' });
try {
const raw = editorEl.getAttribute("data-code");
const decoded = decodeURIComponent(escape(window.atob(raw)));
jar.updateCode(decoded);
} catch (e) { console.error(e); }
editorEl.addEventListener('keydown', (e) =&gt; {
if ((e.ctrlKey || e.metaKey) &amp;&amp; e.key === 'Enter') {
e.preventDefault();
document.getElementById("btn-0").click();
}
});
const copyBtn = document.getElementById("copy-png-btn-0");
if(copyBtn) {
copyBtn.onclick = () =&gt; {
const plot = document.getElementById("plot-0");
const svg = plot.querySelector('svg');
if(!svg) return;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
const data = (new XMLSerializer()).serializeToString(svg);
img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(data);
img.onload = () =&gt; {
canvas.width = svg.viewBox.baseVal.width * 2;
canvas.height = svg.viewBox.baseVal.height * 2;
ctx.fillStyle = 'white'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img, 0,0,canvas.width,canvas.height);
canvas.toBlob(b =&gt; navigator.clipboard.write([new ClipboardItem({'image/png':b})]));
alert("Plot copied!");
};
};
}
});
&lt;/script&gt;
&lt;script type="py" config='{"packages": ["micropip", "numpy", "matplotlib"]}'&gt;
from pyscript import window, document
import sys, io, micropip, asyncio, html, json, types
if 'kernels' not in globals(): kernels = {}
kernels[0] = {}
class DOMStream:
def __init__(self, element_id): self.id = element_id
def write(self, text):
el = document.getElementById(self.id)
if el: el.innerText += text; el.scrollTop = el.scrollHeight
def flush(self): pass
# 2. 变量树生成器 (恢复 Python 逻辑)
def get_scope_tree(scope):
tree_data = {}
def safe_str(v):
try: s = str(v)
except: s = "&lt;error&gt;"
return html.escape(s[:50] + "..." if len(s) &gt; 50 else s)
for k, v in scope.items():
# 过滤系统变量
if k.startswith("_") or k in ['open', 'exit', 'quit', 'In', 'Out', 'get_ipython', 'show_plot', 'matplotlib', 'plt', 'micropip', 'fm', 'pyfetch', 'os', 'sys', 'io', 'html', 'json', 'types', 'asyncio', 'DOMStream', 'kernels', 'get_scope_tree']: continue
if isinstance(v, (types.ModuleType, types.FunctionType, type)): continue
val_type = type(v).__name__
children = None
display_val = safe_str(v)
# 处理 List/Dict 嵌套
if isinstance(v, dict):
children = {}
display_val = f"dict[{len(v)}]"
for sk, sv in list(v.items())[:20]:
children[str(sk)] = {"val": safe_str(sv), "type": type(sv).__name__, "children": None}
elif isinstance(v, (list, tuple)):
children = {}
display_val = f"{val_type}[{len(v)}]"
for i, sv in enumerate(v[:20]):
children[str(i)] = {"val": safe_str(sv), "type": type(sv).__name__, "children": None}
tree_data[k] = {"val": display_val, "type": val_type, "children": children}
return json.dumps(tree_data)
def _show_plot():
import matplotlib.pyplot as plt
div = document.getElementById("plot-0")
hdr = document.getElementById("plot-header-0")
buf = io.StringIO()
plt.savefig(buf, format='svg', bbox_inches='tight')
div.innerHTML = buf.getvalue()
div.style.display = "block"
hdr.style.display = "flex"
plt.clf()
def reset_code_0(e):
kernels[0] = {"show_plot": _show_plot}
document.getElementById("output-0").innerText = "&gt; Reset.\n"
window.renderJsonTree("tree-0", "{}")
document.getElementById("plot-0").style.display = "none"
document.getElementById("plot-header-0").style.display = "none"
async def run_code_0(e):
btn = document.getElementById("btn-0")
out = document.getElementById("output-0")
editor = document.getElementById("editor-0")
btn.disabled = True
btn.innerText = "..."
out.innerText = ""
# 获取代码
code = window.getSelection().toString()
if not code.strip(): code = editor.textContent
# --- 优化后的安装包逻辑 ---
# 扫描代码中的所有行，寻找以 # install: 开头的行
lines = code.split('\n')
for line in lines:
line = line.strip()
if line.startswith("# install:"):
pkgs = [p.strip() for p in line.replace("# install:", "").split(",") if p.strip()]
if pkgs:
out.innerText += f"&gt; 正在安装依赖: {', '.join(pkgs)}...\n"
try:
await micropip.install(pkgs)
out.innerText += "&gt; 安装成功！\n"
except Exception as err:
out.innerText += f"&gt; 安装失败: {err}\n"
# -----------------------
scope = kernels[0]
if "show_plot" not in scope: scope["show_plot"] = _show_plot
stream = DOMStream("output-0")
sys.stdout = stream
sys.stderr = stream
try:
await asyncio.sleep(0.1)
# 执行代码
exec(code, scope, scope)
except Exception as err:
print(f"Error: {err}")
finally:
sys.stdout = sys.__stdout__
# 渲染变量树
try:
tree = get_scope_tree(scope)
window.renderJsonTree("tree-0", tree)
except: pass
btn.disabled = False
btn.innerText = "▶ RUN"
&lt;/script&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;分词是一种“必要的恶（Necessary Evil）”。虽然理想情况下我们希望模型能直接理解原始字节，但 BPE 是目前平衡词表大小、序列长度和信息密度的最有效手段。&lt;/p&gt;
&lt;p&gt;在实际的大模型训练（如 GPT-2/3/4）中，BPE 的实现会更加复杂，包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;预分词（Pre-tokenization）&lt;/strong&gt;：先用正则将文本切分为单词块，再在块内进行 BPE，防止跨单词合并。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊 Token&lt;/strong&gt;：处理 &amp;lt;|endoftext|&amp;gt; 等控制符。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：编码过程需要极度优化，不能像上述代码那样遍历所有合并规则。&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Pipeline of CS336</title><link>https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/</link><pubDate>Sun, 14 Dec 2025 00:00:00 +0800</pubDate><guid>https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/</guid><description>&lt;h1 id="课程组成"&gt;课程组成&lt;/h1&gt;
&lt;h2 id="所有的一切都关乎效率"&gt;所有的一切都关乎效率&lt;/h2&gt;
&lt;p&gt;资源：数据+硬件（计算，内存，计算带宽）
给定一组受限的资源，你会如何训练一个最佳的模型？
举例：给定Common Crawl 的语料和两周 32 卡的 H100s 使用权，你将会如何行动？&lt;/p&gt;
&lt;p&gt;课程组成大纲：
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Design decisions"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/design-decisions_hu_b596ed99084b6016.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/design-decisions_hu_71672ffeb422e21e.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/design-decisions_hu_1fb9e5df297db27d.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/design-decisions_hu_b596ed99084b6016.webp"
width="760"
height="145"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="效率驱动着设计决策"&gt;效率驱动着设计决策&lt;/h2&gt;
&lt;p&gt;如今，我们受限于计算资源，因此设计决策将着力于充分利用给定的硬件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据处理：避免浪费宝贵的计算资源去更新不良或无关的数据&lt;/li&gt;
&lt;li&gt;分词：直接处理原始字节虽然简洁，但在当今的模型架构下计算效率低下。&lt;/li&gt;
&lt;li&gt;模型架构：许多改动旨在减少内存占用或浮点运算次数（例如，共享键值缓存、滑动窗口注意力机制）&lt;/li&gt;
&lt;li&gt;训练：我们只需一个 epoch 就能完成训练！&lt;/li&gt;
&lt;li&gt;缩放定律：在较小的模型上使用更少的计算资源来进行超参数调优&lt;/li&gt;
&lt;li&gt;对齐：如果针对特定用例对模型进行更多调优，那么所需的基础模型会更小
未来，我们将受限于数据……&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="section-1--基础概念--任务"&gt;Section-1 : 基础概念 &amp;amp; 任务&lt;/h2&gt;
&lt;p&gt;目标：对整个工作流程有一个基本的概念认知并感知对应的课程任务设计&lt;/p&gt;
&lt;p&gt;组成：分词，模型架构，训练&lt;/p&gt;
&lt;h3 id="分词"&gt;分词&lt;/h3&gt;
&lt;p&gt;一个分词器的抽象代码实现如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Tokenizer(ABC):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Abstract interface for a tokenizer&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def encode(self, string: str) -&amp;gt; list[int]:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; raise NotImplementedError
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def decode(self, indices: list[int]) -&amp;gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; raise NotImplementedError
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;分词器可以在strings 和 整数序列(tokens)两个相互转换。
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="tokenized-example"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/tokenized-example_hu_aac08fa4f591c86a.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/tokenized-example_hu_2bfc1dbc1bb34629.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/tokenized-example_hu_2d0b621aa53f602f.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/tokenized-example_hu_aac08fa4f591c86a.webp"
width="760"
height="312"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
直觉来看：将字符串拆分为常见片段。&lt;/p&gt;
&lt;p&gt;本次课程将要讲授的方法是
, 当然目前也在一些 Tokenizer-free的方法，
,
,
,
。直接实用 bytes 的方法看起来非常有前景，但是目前还没有被大规模应用到前沿模型。&lt;/p&gt;
&lt;h3 id="架构"&gt;架构&lt;/h3&gt;
&lt;p&gt;所有的起点：原始 Transformer
&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="transformers"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/transformer-architecture_hu_d75ad342d1e22387.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/transformer-architecture_hu_da6abdfebe92f4b.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/transformer-architecture_hu_7da8b424450315aa.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/transformer-architecture_hu_d75ad342d1e22387.webp"
width="760"
height="659"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="变体"&gt;变体：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;激活函数（Activation functions）：ReLU,
&lt;/li&gt;
&lt;li&gt;位置编码（Positional encodings）：Sinusoidal,
&lt;/li&gt;
&lt;li&gt;正则化（Normalization）LayerNorm
， RMSNorm
&lt;/li&gt;
&lt;li&gt;正则化的替代项 : pre-norm 与
&lt;/li&gt;
&lt;li&gt;感知机（MLP）：dense，Mixture of experts
&lt;/li&gt;
&lt;li&gt;注意力机制（Attention）：full，sliding windows，linear
，
&lt;/li&gt;
&lt;li&gt;低维注意力（lower-dimensional attention）：分组查询注意力（group-query attention, aka GQA），多头潜在注意力（multi-head latent attention, aka MLA）&lt;/li&gt;
&lt;li&gt;状态空间模型 （state-space model，aka SSM）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="训练"&gt;训练&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;优化器（Optimizer） 例如
，
，
，
&lt;/li&gt;
&lt;li&gt;学习率调度器（learning rate schedule），例如
,
&lt;/li&gt;
&lt;li&gt;批大小（batch size），例如
&lt;/li&gt;
&lt;li&gt;正则化（regularization），例如 dropout, weight decay&lt;/li&gt;
&lt;li&gt;超参数设置（hyperparameters），例如注意力的头数量，隐藏层维度：用网格搜索完成探索。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="任务1"&gt;任务1&lt;/h3&gt;
&lt;p&gt;Stanford CS336原版：
，
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实现一个 BPE 分词器&lt;/li&gt;
&lt;li&gt;实现 transformers，交叉熵（Cross-entropy loss），AdamW 优化器，训练循环&lt;/li&gt;
&lt;li&gt;在 TinyStories 和 OpenWebText上进行训练&lt;/li&gt;
&lt;li&gt;榜单：给定 90 分钟的 H100 训练时间，最小化 OpenWebText 的困厚度。
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="section-2-系统设计--任务"&gt;Section-2 系统设计 &amp;amp; 任务&lt;/h2&gt;
&lt;p&gt;目标：将硬件设备的性能都压榨出来&lt;/p&gt;
&lt;p&gt;组成： 核（kernel），并行化 （parallelism），推理（inference）&lt;/p&gt;
&lt;h3 id="核-kernels"&gt;核（ kernels）&lt;/h3&gt;
&lt;p&gt;GPU 的内部结果如下所示 ，以 A100 为例：&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="A100-architech"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/A100-architechture_hu_4bf2b3f48f2297ed.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/A100-architechture_hu_6fa0b401a2088e5d.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/A100-architechture_hu_834d3e26dd3a3681.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/A100-architechture_hu_4bf2b3f48f2297ed.webp"
width="760"
height="341"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;类比：仓库 : 动态随机存取存储器(DRAM) :: 工厂 : 静态随机存取存储器(SRAM)&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="analogy-gpu-process"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/analogy_gpu_process_hu_5475498e2182ff71.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/analogy_gpu_process_hu_66da992891c66472.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/analogy_gpu_process_hu_400e1cffc0c34c12.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/analogy_gpu_process_hu_5475498e2182ff71.webp"
width="760"
height="760"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;技巧：通过减少数据移动来组织计算，以最大限度地提高GPU的利用率,使用CUDA/Triton/CUTLASS/ThunderKittens编写内核程序。&lt;/p&gt;
&lt;h3 id="并行度parallelism-"&gt;并行度（Parallelism ）&lt;/h3&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="parallelism_framework"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/parallelism_framework_hu_2e2a9712cd0399a3.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/parallelism_framework_hu_4cbd27f66056e12d.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/parallelism_framework_hu_35dfef0f6afaaf09.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/parallelism_framework_hu_2e2a9712cd0399a3.webp"
width="760"
height="384"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;GPU之间的数据移动甚至更慢，但“最小化数据移动”这一原则仍然适用。
使用集合操作，例如，收集（gather）、归约（reduce）、全归约（all-reduce）。
在GPU之间分片，例如，参数、激活值、梯度、优化器状态）。
如何拆分计算：{数据（data-para）、张量（tensor-para）、流水线（pipe-para）、序列（seq-para）}并行性。&lt;/p&gt;
&lt;h3 id="推理-inference"&gt;推理 （inference）&lt;/h3&gt;
&lt;p&gt;生成令牌为模型实际使用时的必需操作；&lt;/p&gt;
&lt;p&gt;推理对强化学习、测试时计算、评估均不可或缺；&lt;/p&gt;
&lt;p&gt;全球范围内，推理计算（每次使用）成本已超过训练计算（一次性成本）；&lt;/p&gt;
&lt;p&gt;推理包含预填充、解码两个阶段。如下所示：
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="prefill-decode"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/prefill-decode_hu_c96951fa8f54a44d.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/prefill-decode_hu_7e37f2da0fbde845.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/prefill-decode_hu_38183f384500a998.webp 647w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/prefill-decode_hu_c96951fa8f54a44d.webp"
width="647"
height="248"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;预填充（类似于训练）：给定标记，可以一次性处理所有标记（计算受限）&lt;/p&gt;
&lt;p&gt;解码：需要一次生成一个标记（内存受限）&lt;/p&gt;
&lt;p&gt;加速解码的方法：
• 使用更轻量的模型（通过模型剪枝、量化、蒸馏）
• 推测性解码：使用更轻量的“草稿”模型生成多个标记，然后使用完整模型并行评分（精确解码！）
• 系统优化：KV缓存、批处理。&lt;/p&gt;
&lt;h3 id="任务-2"&gt;任务 2&lt;/h3&gt;
&lt;p&gt;Stanford CS336原版：
，
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在Triton中实现融合的RMSNorm内核。&lt;/li&gt;
&lt;li&gt;实现分布式数据并行训练。&lt;/li&gt;
&lt;li&gt;实现优化器状态分片。&lt;/li&gt;
&lt;li&gt;对实现进行基准测试和性能分析。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="section-3-scaling-定律--任务"&gt;Section-3 Scaling 定律 &amp;amp; 任务&lt;/h2&gt;
&lt;p&gt;目标：进行小规模实验，预测大规模下的超参数/损失。&lt;/p&gt;
&lt;p&gt;问题：给定一个浮点运算次数预算（$C$），是使用更大的模型（$N$）还是在更多的标记（$D$）上训练？
Compute-optimal scaling laws:
,
&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="scaling_laws"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/chinchilla-isoflop_hu_831b683921ead218.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/chinchilla-isoflop_hu_713b6f4e29063c0e.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/chinchilla-isoflop_hu_a0e8ea57a2359ab1.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/chinchilla-isoflop_hu_831b683921ead218.webp"
width="760"
height="228"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;TL;DR：$D^* = 20 N^*$（例如，14亿参数的模型应该在280亿个token上进行训练), 但这并没有考虑到推理成本！&lt;/p&gt;
&lt;details&gt;
&lt;summary style="cursor: pointer; color: #0366d6;"&gt;&lt;strong&gt;👉 点击展开：为什么说它没有考虑“推理成本”？&lt;/strong&gt;&lt;/summary&gt;
&lt;p&gt;这里有一个关键的反转：Chinchilla 定律 ($D^*=20N^*$) 追求的是 &lt;strong&gt;训练成本最低&lt;/strong&gt;（Compute-optimal）。&lt;/p&gt;
&lt;p&gt;但在实际应用中，模型训练只有一次，而 &lt;strong&gt;推理(被用户使用)&lt;/strong&gt; 会有无数次。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果严格遵守 20倍定律，我们会得到一个参数很大、但训练数据适中的模型。虽然训练省钱，但因为它太大了，&lt;strong&gt;每次运行都很贵且慢&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;现代模型（如 Llama 3）通常会&lt;strong&gt;打破这个定律&lt;/strong&gt;，用远超 20 倍的数据（比如 100 倍）去“过度训练”一个小模型。这样虽然训练时多花了钱，但得到的小模型在未来使用时&lt;strong&gt;速度快、成本低&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h3 id="任务-3"&gt;任务 3&lt;/h3&gt;
&lt;p&gt;Stanford CS336 原版：
,
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我们基于之前的运行定义了一个训练API（超参数→损失）&lt;/li&gt;
&lt;li&gt;在FLOPs预算下提交“训练任务”并收集数据点&lt;/li&gt;
&lt;li&gt;为数据点拟合缩放定律&lt;/li&gt;
&lt;li&gt;提交按比例放大的超参数的预测&lt;/li&gt;
&lt;li&gt;排行榜：在给定的FLOPs预算下最小化损失&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="section-4-数据--任务"&gt;Section-4 数据 &amp;amp; 任务&lt;/h2&gt;
&lt;p&gt;问题：我们希望模型有什么样子的能力？多语言？代码？数学？&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="data-framework"
srcset="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/data_framework_hu_ed8ed15559d34d95.webp 320w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/data_framework_hu_4d05533a8c729ac.webp 480w, https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/data_framework_hu_291e8f36edf04567.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://lin-shang.github.io/courses/stanford-cs-336/pipeline_of_cs336/data_framework_hu_ed8ed15559d34d95.webp"
width="760"
height="470"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="困惑度语言模型的标准评估"&gt;困惑度：语言模型的标准评估&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;标准化测试（例如，MMLU、HellaSwag、GSM8K）&lt;/li&gt;
&lt;li&gt;指令遵循（例如，AlpacaEval、IFEval、WildBench）&lt;/li&gt;
&lt;li&gt;缩放测试时计算：思维链、集成&lt;/li&gt;
&lt;li&gt;以语言模型作为评判者：评估生成任务&lt;/li&gt;
&lt;li&gt;完整系统：检索增强生成（RAG）、智能体&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="数据整理"&gt;数据整理&lt;/h3&gt;
&lt;p&gt;• 数据并非凭空而来。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;look_at_web_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_common_crawl_urls&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# @inspect urls&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_common_crawl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdownify_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;write_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;var/sample-documents.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# urls=[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://data.commoncrawl.org/crawl-data/CC-MAIN-2024-18/segments/1712296815919.75/warc/CC-MAIN-20240412101354-20240412131354-00000.warc.gz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://data.commoncrawl.org/crawl-data/CC-MAIN-2024-18/segments/1712296815919.75/warc/CC-MAIN-20240412101354-20240412131354-00001.warc.gz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://data.commoncrawl.org/crawl-data/CC-MAIN-2024-18/segments/1712296815919.75/warc/CC-MAIN-20240412101354-20240412131354-00002.warc.gz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;来源：从互联网爬取的网页、书籍、arXiv论文、GitHub代码等。&lt;/li&gt;
&lt;li&gt;诉诸合理使用来使用版权数据进行训练？(
)&lt;/li&gt;
&lt;li&gt;可能需要授权数据（例如，
)&lt;/li&gt;
&lt;li&gt;格式：HTML、PDF、目录（而非文本！）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="数据处理"&gt;数据处理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;转换：将HTML/PDF转换为文本（保留内容、部分结构、重写）。&lt;/li&gt;
&lt;li&gt;过滤：保留高质量数据，移除有害内容（通过分类器）。&lt;/li&gt;
&lt;li&gt;去重：节省计算资源，避免记忆；使用布隆过滤器或最小哈希。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="任务4"&gt;任务4&lt;/h3&gt;
&lt;p&gt;Stanford 原版：
,
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将通用爬虫（Common Crawl）的HTML转换为文本&lt;/li&gt;
&lt;li&gt;训练分类器以过滤出高质量内容和有害内容&lt;/li&gt;
&lt;li&gt;使用最小哈希进行去重&lt;/li&gt;
&lt;li&gt;排行榜：在给定的令牌预算下最小化困惑度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="section-5-对齐----任务"&gt;Section-5 对齐 &amp;amp; 任务&lt;/h2&gt;
&lt;p&gt;到目前为止，基础模型只是原始的潜力，非常擅长完成下一个标记。对齐能让模型真正变得有用。&lt;/p&gt;
&lt;p&gt;对齐的目标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;让语言模型遵循指令。&lt;/li&gt;
&lt;li&gt;调整风格（格式、长度、语气等）。&lt;/li&gt;
&lt;li&gt;融入安全性（例如，拒绝回答有害问题）。&lt;/li&gt;
&lt;li&gt;两个阶段：
&lt;ul&gt;
&lt;li&gt;supervised_finetuning(）&lt;/li&gt;
&lt;li&gt;learning_from_feedback()&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="有监督微调-supervised_finetuning"&gt;有监督微调 supervised_finetuning&lt;/h3&gt;
&lt;p&gt;指令数据格式：（prompt，response）pair&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sft_data = list[chatExample] = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ChatExample(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; turns = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Turn(role=&amp;#34;system&amp;#34;, content=&amp;#34;You are a helpful assistant.&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Turn(role=&amp;#34;user&amp;#34;, content=&amp;#34;what is 1 + 1?),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Turn(role=&amp;#34;assistant&amp;#34;, content=&amp;#34;The answer is 2.&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;数据通常涉及人工标注。&lt;/p&gt;
&lt;p&gt;直觉：基础模型已经具备相关技能，只需要几个例子就能展现出来。
&lt;/p&gt;
&lt;p&gt;监督学习：微调模型以最大化 Prob(response | prompt）。&lt;/p&gt;
&lt;h3 id="从反馈中学习"&gt;从反馈中学习&lt;/h3&gt;
&lt;p&gt;现在我们有一个初步的指令遵循模型。让我们在不进行昂贵标注的情况下改进它。&lt;/p&gt;
&lt;p&gt;反馈学习的重点是偏好数据 ，验证器，和算法。&lt;/p&gt;
&lt;p&gt;一个偏好的数据的格式如下：&lt;/p&gt;
&lt;p&gt;使用模型针对给定提示生成多个响应（例如，[A、B]）。用户给出偏好（例如，A &amp;lt; B 或 A &amp;gt; B）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;preference_data: list[PreferenceExample] = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PreferenceExample(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; history=[
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Turn(role=&amp;#34;system&amp;#34;, content=&amp;#34;You are a helpful assistant.&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Turn(role=&amp;#34;user&amp;#34;, content=&amp;#34;What is the best way to train a language model?&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; response_a=&amp;#34;You should use a large dataset and train for a long time.&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; response_b=&amp;#34;You should use a small dataset and train for a short time.&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; chosen=&amp;#34;a&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="验证器-verifiers"&gt;验证器 （Verifiers）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;形式化验证器（例如，用于代码、数学的验证器）&lt;/li&gt;
&lt;li&gt;习得验证器：针对作为评判者的大语言模型进行训练&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="算法algorithm"&gt;算法（Algorithm）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Proximal Policy Optimization (PPO) from reinforcement learning 
,
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Direct Policy Optimization (DPO): for preference data, simpler 
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Group Relative Preference Optimization (GRPO): remove value function 
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="任务-5"&gt;任务 5&lt;/h3&gt;
&lt;p&gt;Stanford 官方链接：
，
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实现有监督微调&lt;/li&gt;
&lt;li&gt;实现直接偏好优化（DPO）&lt;/li&gt;
&lt;li&gt;实现群相对偏好优化 （GRPO）&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>