#二、Tokenizer、Embedding、位置编码与上下文窗口
#从文本到 token:为什么不是按词切
大模型不能直接处理字符串,它先把文本切成离散 ID,再查表得到向量。Tokenizer 的核心目标不是“符合人类语言学直觉”,而是在固定词表大小下,让常见片段切得短、罕见片段仍然可表示、不同语言和符号系统都能稳定覆盖。如果按“词”切,英文还勉强可以依赖空格,中文没有天然空格,代码里有 snake_case、缩进、括号和 API 名,表情、数字、URL 又不属于传统词汇;更严重的是新词和拼写变体会造成大量 OOV。所以主流 LLM 使用子词或字节级 tokenization:高频词可以是一个 token,低频词被拆成更小片段,最终保证任何输入都能落到有限词表里。
面试里不要把 tokenization 当成“预处理细节”。它会直接改变训练分布、上下文长度、推理成本、不同语言的公平性,以及系统上线后的缓存、计费和检索切片策略。同一句话切成 20 个 token 还是 45 个 token,意味着注意力计算、KV cache、价格和可放入的历史内容都不同。
BPE、WordPiece、SentencePiece 的直觉
| 方法 | 核心直觉 | 典型差异 | 面试表达 |
|---|---|---|---|
| BPE | 从字符或字节开始,反复合并最常见的相邻片段 | 偏工程、实现简单,GPT 系列常见字节级 BPE 变体 | “频繁一起出现的片段就变成更大的 token” |
| WordPiece | 也做子词合并,但更强调让训练语料似然提升 | BERT 系列常见,常用 ## 标记非词首片段 | “选择能更好解释语料的子词单元” |
| SentencePiece | 把文本当原始字符流处理,不强依赖空格预分词 | 常用于多语言,空格可被编码成特殊符号 | “语言无关,先把句子整体当作可学习切分对象” |
三者的共同点是避免纯词级词表爆炸,也避免纯字符级序列太长;差异主要在“合并准则”和“是否依赖预分词”。BPE 更像统计频次驱动的压缩,WordPiece 更像概率模型驱动的子词选择,SentencePiece 则把空格也视为普通符号的一部分,因此对中文、日文、混合文本更自然。
#token 粒度、Embedding 与语义表示
token 粒度越粗,序列越短,计算更省,但词表更大、罕见词更难覆盖;粒度越细,覆盖能力更强,词表可控,但同样内容会占更多上下文。英文常见词可能一个 token 表示,中文经常一字或多字成 token,代码中的长变量名可能被拆成许多片段。于是同一个 8K window,对英文说明、中文论文、JSON 日志和 Python 代码的实际信息容量并不一样。成本也随之变化:多数 API 按 token 计费,训练和推理的注意力复杂度又近似随长度平方增长,粗略可写作 \(O(n^2 d)\)。
Static embedding 与 contextual embedding
Embedding 是把 token ID 映射到连续向量的表,最初的输入表示通常来自一个矩阵 \(E \in \mathbb{R}^{V \times d}\),其中 \(V\) 是词表大小,\(d\) 是隐藏维度。Word2Vec、GloVe 这类 static embedding 给每个词一个固定向量,“bank” 无论出现在河岸还是银行语境里,初始含义都一样。Transformer 的 contextual embedding 则不同:输入层查表得到的是起点,经过多层 self-attention 后,同一个 token 的最终表示会融合上下文信息。因此更准确的说法是:LLM 的 embedding table 本身仍是静态参数,但模型输出的 hidden state 是上下文化表示。
- Tokenizer 决定“有哪些离散符号进入模型”,embedding 决定“每个符号初始落在向量空间的哪里”。
- 上下文化不是 tokenizer 完成的,而是 attention 在不同上下文中重新组合信息的结果。
- 更换 tokenizer 通常不能只换词表,还要重训或适配 embedding,尤其会影响中英文混合、代码和专业术语。
#位置编码、RoPE 与上下文窗口
Self-attention 本身对输入顺序不敏感:如果不加位置信息,它只看到一组 token,而不知道谁在前谁在后。位置编码就是给模型补上“第几个 token”和“两个 token 相距多远”的线索。绝对位置编码给每个位置一个固定或可学习向量,再与 token embedding 相加;优点简单,缺点是训练长度之外的位置没有充分学习。相对位置编码不只关心位置编号,而更关心 token 之间的距离,适合表达“前一个词”“相隔 10 个 token”这类关系。
RoPE 的直觉是:不要把位置当作额外向量相加,而是在 query/key 向量的二维子空间里按位置角度旋转。位置越靠后,旋转角越大;两个 token 做点积时,结果自然包含相对位移信息。可以把它理解为:每个 token 的内容向量被挂到一个随位置变化的钟表指针上,attention 比较两个指针时,不只比较内容,也比较两者的相对角度。形式上常写成 \(q_m^\top k_n\) 经过旋转后只依赖相对距离 \(m-n\),这就是 RoPE 适合自回归模型和相对位置建模的原因。
Context window 是模型一次能接收的最大 token 数。窗口变大后,问题不只是“能塞更多文本”:训练阶段要让模型见过足够长的依赖,推理阶段 prefill 更慢,注意力和 KV cache 更贵,RAG 里还会出现“Lost in the Middle”,即模型对开头和结尾更敏感,对中间证据利用较差。RoPE 虽然有一定外推潜力,但训练长度外的高频/低频旋转角分布会变化,直接从 8K 拉到 128K 往往需要 position interpolation、NTK-aware scaling、YaRN、LongRoPE 等修正,或者继续长上下文训练。
KV cache 成本如何估算
自回归生成时,每生成一个新 token 都要读之前 token 的 key/value。KV cache 保存每层、每个 head 的 K 和 V,避免重复计算历史。粗略估算为:layers * 2 * seq_len * hidden_size * bytes,其中 2 表示 K 和 V。窗口从 8K 增到 128K 是 16 倍长度,KV cache 近似也涨 16 倍;如果还做多并发服务,显存压力会很快超过权重本身。因此长上下文工程常配合 paged attention、prefix caching、KV cache 量化、滑动窗口或稀疏注意力。
#常见误区与追问链路
- 误区一:token 等于词。 token 可能是词、子词、字节、标点、空格片段或代码符号;不同 tokenizer 下 token 数不可直接比较。
- 误区二:embedding 就是最终语义。 输入 embedding 只是查表向量,最终语义来自多层 attention 和 MLP 之后的 contextual hidden state。
- 误区三:RoPE 天然无限外推。 RoPE 提供相对位置信号,但长于训练窗口后仍会遇到角度分布、注意力分辨率和训练数据覆盖问题。
- 误区四:上下文越长越好。 长窗口提高召回空间,但也带来成本、延迟、噪声干扰和中间信息遗忘;RAG 仍需要检索、重排和证据压缩。
一个稳定的追问链路可以这样回答:
- 先说明 tokenizer 的目标:有限词表、全覆盖、压缩序列长度,并解释为什么不按词切。
- 再比较 BPE、WordPiece、SentencePiece:共同点是子词化,差异在合并准则和预分词假设。
- 接着讲 token 粒度影响:中英文、代码、成本、窗口容量和迁移风险。
- 然后区分 embedding table 与 contextual representation,避免把查表向量当作最终语义。
- 最后落到位置和长上下文:绝对/相对位置编码,RoPE 的旋转直觉,外推困难,以及 KV cache 的线性显存增长。
如果面试官继续追问“如何把 8K 模型扩到 128K”,不要只说“改配置”。更完整的答案是:先处理位置编码外推,再补长上下文数据训练或微调;推理侧评估 prefill 延迟、KV cache 显存和并发吞吐;应用侧重新设计 RAG 切片、重排和摘要压缩;最后用 needle-in-a-haystack、长文问答、代码仓库问答等任务验证模型是否真的使用了远距离信息。