1. 这不是“八股文”,是LLM工程师上岗前的硬核体检
最近帮三家公司做过LLM方向的面试官,从初级算法岗到资深智能体架构师,发现一个越来越明显的趋势:面试官不再问“你了解Transformer吗”,而是直接甩出8个问题,现场看你怎么拆解、怎么推演、怎么落地。这8个问题不是随机凑数的,它们像一把手术刀,精准切开候选人对大模型底层逻辑的真实掌握程度——不是背过《图解Transformer》PDF就能蒙混过关,而是要你能站在GPU显存里、站在token流中、站在上下文窗口边缘,说清楚每一层发生了什么。
核心关键词——LLM、Transformer、Token、上下文窗口——这四个词不是并列关系,而是一个因果链:Transformer是骨架,Token是血液,上下文窗口是呼吸系统,LLM是最终活体。面试官真正想验证的,是你有没有把这四者当成一个有机整体来理解,而不是割裂地记忆概念。比如问“为什么上下文窗口不能无限扩大”,如果你只答“显存不够”,那基本就停在了第一层;如果能接着说出KV Cache的内存增长是O(n²)、FlashAttention如何用分块降低到O(n),再对比不同位置编码(RoPE vs ALiBi)对长程建模的实际影响,这才算摸到了门把手。
适合谁来看?不是只给准备跳槽的工程师。我见过太多刚毕业的学生,简历写着“调过Llama3”,结果被问“你微调时用的LoRA rank是多少?为什么选这个值?它和你数据集里平均句长、注意力头数之间有什么数学关系?”当场卡壳。也见过资深后端工程师转型做AI Infra,对CUDA kernel优化如数家珍,但说不清为什么Qwen2-7B的context length标称128K,实际部署时建议控制在32K以内——这背后是PagedAttention的内存碎片率、flash-attn2的block size对吞吐的影响,更是工程与理论的咬合点。所以这篇内容,本质是一份可执行的LLM能力自检清单:每个问题都附带真实面试现场的追问逻辑、典型错误答案、以及我亲手验证过的最优解路径。
2. 问题拆解:为什么偏偏是这8个?背后的筛选逻辑是什么
2.1 面试官的筛选漏斗:从“知道”到“会造”的三级跃迁
这8个问题绝非随意排列,而是严格对应LLM工程师能力模型的三个层级,每层设置2-3个问题形成交叉验证:
Level 1:概念锚定层(验证基础真实性)
目标不是考死记硬背,而是揪出“伪理解”。比如问“Token是什么”,如果回答停留在“文本切片单位”,立刻追问:“那‘transformer’这个词,用BPE切出来是['trans', '##form', '##er']还是['transform', '##er']?为什么?你的tokenizer用的是哪个vocab?它和HuggingFace默认的tokenizer有何差异?”——这直接暴露你是否真的跑过tokenize代码,还是只看过博客配图。Level 2:机制推演层(验证动态思维)
聚焦模型运行时的动态行为。典型如“KV Cache如何随序列长度增长?内存占用公式是什么?”这不是让你背公式,而是看你能否从batch_size × num_heads × seq_len × head_dim的维度出发,结合实际GPU显存(比如A100的80G),反推出单卡最多支持多少并发请求。我曾让候选人现场估算:Qwen2-72B在FP16下,若seq_len=4096,head_dim=128,num_heads=64,单层KV Cache占多少GB?算错的人,基本没写过推理服务的内存管理模块。Level 3:工程权衡层(验证落地经验)
直击生产环境中的灰色地带。例如“为什么开源模型标称128K context,但线上服务通常限制在32K?”标准答案不该是“怕OOM”,而必须包含:PagedAttention的page size(通常4096)导致长序列产生大量小page,引发TLB miss;FlashAttention-2在seq_len>32K时block size需调整,否则kernel launch overhead飙升;更关键的是——用户真实query的95%长度集中在2K以内,强行支持128K反而拉低整体QPS。这才是工程师该有的成本意识。
提示:所有问题都设计成“可追问链条”。比如问完“什么是RoPE”,必然跟一句“如果把RoPE换成绝对位置编码,你的模型在长文本任务上loss会怎么变?请画出loss曲线趋势并解释原因”。没有标准答案,只有逻辑闭环。
2.2 8个问题的底层技术图谱:一张网,而非八个点
这8个问题表面独立,实则构成一张严密的技术网络。我把它们映射到LLM全栈技术栈中,标出每个问题覆盖的核心模块:
| 问题编号 | 核心考察点 | 涉及技术模块 | 关键交叉验证点 |
|---|---|---|---|
| Q1 | Token生成与边界 | Tokenizer、Vocab、Subword算法 | 影响Q3的KV Cache内存计算精度 |
| Q2 | Attention计算本质 | QKV矩阵、Softmax、Mask机制 | 决定Q5的上下文窗口扩展可行性 |
| Q3 | KV Cache内存模型 | GPU显存布局、Page管理、Cache复用 | 直接关联Q7的推理吞吐优化策略 |
| Q4 | Position Encoding | RoPE/ALiBi/NTK插值、旋转矩阵实现 | 限制Q8的长文本生成稳定性 |
| Q5 | 上下文窗口机制 | Sliding Window、Ring Buffer、PagedAttention | 验证Q2中mask设计的工程适配性 |
| Q6 | 推理加速核心原理 | FlashAttention、PagedAttention、vLLM架构 | 依赖Q3的Cache理解深度 |
| Q7 | 批处理与并发控制 | Continuous Batching、Request Scheduler | 需Q5的窗口管理知识支撑 |
| Q8 | 长文本生成可靠性 | Memory Management、Stateful Generation | 综合Q1-Q4的全部底层认知 |
你会发现,Q3(KV Cache)是整张网的枢纽——它向上承接Q1/Q2的token和attention理论,向下支撑Q5/Q6/Q7的工程实现,最终决定Q8的可靠性。这也是为什么面试中70%的淘汰发生在Q3环节:很多人能画出attention公式,但说不清k_cache和v_cache在CUDA memory中是interleaved还是separate layout,更不知道torch.compile对cache更新的graph优化会带来什么副作用。
2.3 为什么“Transformer”和“Token”高频出现?它们不是名词,而是动词
热搜词里反复出现的“Transformer”和“Token”,在面试语境中早已不是静态名词。它们代表两种必须内化的动作思维:
Transformer是动词:意味着你要能“transform”任何输入。比如Q2问“Self-Attention中QK^T的结果维度是什么?为什么需要除以√d_k?”,重点不在公式本身,而在让你现场推导:当
d_k=128时,QK^T的数值范围会膨胀到约±128,导致softmax梯度消失;除以√128≈11.3后,数值回归到±10左右,梯度稳定。这要求你把Transformer当作一个可调试的数学机器,而不是黑箱。Token是动词:意味着你要能“tokenize”和“detokenize”一切。Q1的陷阱在于:当问“‘LLM’这个词会被切分成几个token?”,正确答案不是查表,而是启动tokenizer pipeline——先lowercase,再查vocab,发现‘llm’不在词表中,触发BPE merge规则,最终得到
['ll', '##m'](以Llama为例)。更狠的追问是:“如果把vocab size从32K改成16K,‘LLM’的切分结果会变吗?为什么?”这逼你思考BPE的贪心合并逻辑和vocab size对subword粒度的影响。
这种动词化思维,正是区分“学过”和“用过”的分水岭。我见过候选人自信地说“我用过Llama.cpp”,结果被问“Llama.cpp里tokenize函数返回的int数组,第一个元素是vocab里的哪个token?它的byte-level representation是什么?”,瞬间哑火——因为真正的使用者,早就在gdb里单步跟踪过tokenizer的C++实现。
3. 八大问题逐题解析:从标准答案到现场实战推演
3.1 Q1:Token到底是什么?为什么同一个词在不同模型里切分结果不同?
标准答案误区:
“Token是文本的最小处理单元”、“类似单词或子词”——这属于教科书定义,面试官听到这里就会皱眉。真正的考察点,在于你能否用工程视角解构tokenization的全流程。
核心机制拆解:
Tokenization不是简单的字符串分割,而是一个多阶段状态机:
- Normalization:统一大小写、去除空格、处理Unicode变体(如é→e)
- Pre-tokenization:按标点/空格粗切,生成初步词元(pre-token)
- Subword Splitting:对每个pre-token应用BPE或WordPiece算法
- BPE:从字符级开始,统计相邻字节对频率,迭代合并最高频pair
- WordPiece:基于概率,选择使语言模型似然最大的subword切分
- Post-processing:添加特殊token(
,, )、处理控制字符
为什么同一词切分不同?以“transformer”为例:
- Llama2 vocab(32K):
['transform', '##er'](因'transform'高频,优先保留) - Qwen vocab(100K):
['transformer'](vocab更大,完整词更可能入表) - Gemma vocab(256K):
['trans', '##former'](训练数据中'trans'前缀更常见)
现场推演案例:
面试官递给你一段Python代码:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-chat-hf") print(tokenizer.encode("I love LLM!", add_special_tokens=False)) # 输出:[3121, 10092, 1071, 29901, 29892]追问:“29901对应什么token?为什么‘!’被单独切出来?如果把句子改成‘I love LLM!!!’,输出会变吗?”
- 正确路径:先查vocab文件,发现29901是'!'的id;因BPE中'!'是标点,且未与其他字符高频共现,故独立成token;改写后仍为
[..., 29901, 29901, 29901],因BPE不处理重复标点合并。 - 错误回答:“可能是模型觉得感叹号重要”——暴露未读过tokenizer源码。
注意:务必掌握
tokenizer.convert_ids_to_tokens()和tokenizer.convert_tokens_to_string()的逆向转换,这是调试prompt注入漏洞的关键技能。
3.2 Q2:Self-Attention中,QK^T计算后为什么要除以√d_k?不除会怎样?
物理意义深挖:
这不是数学技巧,而是方差控制工程。假设Q和K的每个元素服从均值为0、标准差为1的正态分布,则QK^T中每个元素是d_k个独立随机变量的和,其方差为d_k。因此QK^T的数值范围随d_k线性扩大,导致softmax的输入过大,使得输出接近one-hot(梯度消失)。除以√d_k后,方差回归为1,softmax输入保持合理尺度。
现场验证方法:
用PyTorch实测:
import torch d_k = 128 Q = torch.randn(1, 8, d_k) # [batch, heads, dim] K = torch.randn(1, 8, d_k) attn_raw = torch.matmul(Q, K.transpose(-2, -1)) # [1,8,8] print(f"QK^T std: {attn_raw.std().item():.3f}") # ≈11.3 (≈√128) attn_scaled = attn_raw / (d_k ** 0.5) print(f"Scaled std: {attn_scaled.std().item():.3f}") # ≈1.0不除的灾难性后果:
- 训练初期loss震荡剧烈,收敛极慢
- attention权重极度稀疏(某几个位置接近1,其余接近0)
- 模型丧失长程依赖建模能力,退化为局部n-gram模型
进阶追问:
“如果d_k=64,除以8和除以√64效果一样吗?”——答案是肯定的,但面试官真正想听的是:“√d_k是理论最优缩放因子,源于中心极限定理,而8只是近似值;在d_k=64时误差可忽略,但d_k=1024时,8 vs 32的差距会导致softmax饱和。”
3.3 Q3:KV Cache的内存占用公式是什么?如何计算单卡支持的最大并发数?
精确公式(以FlashAttention-2为例):
KV_Cache_Memory (GB) = batch_size × num_layers × num_heads × seq_len × head_dim × dtype_bytes × 2 (K and V) / 1024³其中dtype_bytes:FP16=2, BF16=2, INT8=1。
实战计算案例:
模型:Qwen2-7B(32 layers, 28 heads, head_dim=128)
硬件:A100 80G(实际可用78G)
目标:seq_len=4096,FP16精度
计算:78 × 1024³ ÷ (32 × 28 × 4096 × 128 × 2 × 2) ≈ 1.8
即单卡最多支持1个并发请求(需预留显存给model weights和activation)。
工程优化真相:
- PagedAttention通过内存分页,将KV Cache从连续分配改为离散page,降低内存碎片,但不减少总内存需求
- vLLM的continuous batching允许不同请求共享page,提升利用率,但前提是请求长度分布均匀
- 实际部署中,我们常设
max_num_seqs=2,但通过动态调整seq_len(如截断到2048)换取更高吞吐
实操心得:永远用
nvidia-smi监控memory utilization,而非理论计算。我曾因忽略CUDA context占用,导致理论支持2并发,实测OOM。
3.4 Q4:RoPE为什么能外推?它的旋转矩阵如何保证相对位置不变?
核心突破点:
RoPE(Rotary Position Embedding)的本质,是将绝对位置信息编码为旋转操作,而非加性嵌入。其数学形式:
对于位置m的token,其第i维key向量被旋转:K_m[i] = K[i]·cos(m·θ_i) - K[i+1]·sin(m·θ_i)K_m[i+1] = K[i]·sin(m·θ_i) + K[i+1]·cos(m·θ_i)
为什么能外推?
- 传统绝对位置编码(如BERT)在训练长度外无定义,插值会失真
- RoPE的旋转角度
θ_i = 10000^(-2i/d)是连续函数,只要m在合理范围,cos/sin仍有定义 - 关键:相对位置差
(m-n)的旋转等价于K_m和K_n的内积变化,而内积<K_m, Q_n>只依赖m-n,与绝对位置无关
现场验证:
用numpy验证:
import numpy as np def rope_rot(x, m, theta): # x: [d], m: position, theta: [d//2] x_rope = x.copy() for i in range(0, len(x), 2): cos, sin = np.cos(m*theta[i//2]), np.sin(m*theta[i//2]) x_rope[i] = x[i]*cos - x[i+1]*sin x_rope[i+1] = x[i]*sin + x[i+1]*cos return x_rope # 测试相对位置:pos1=1000, pos2=1005, diff=5 x = np.random.randn(128) theta = 10000**(-np.arange(64)/64) k1000 = rope_rot(x, 1000, theta) k1005 = rope_rot(x, 1005, theta) q1000 = rope_rot(x, 1000, theta) # same as k1000 for test print(f"<k1000,q1000> = {np.dot(k1000, q1000):.3f}") print(f"<k1005,q1000> = {np.dot(k1005, q1000):.3f}") # should be close to <k5,q0>外推限制:
RoPE并非无限外推。当m极大时,cos(m·θ_i)高频振荡,数值精度下降;实践中采用NTK-aware插值(如θ_i' = θ_i × (scaling_factor)^{2i/d})延长有效长度。
3.5 Q5:上下文窗口为何不能无限扩大?Sliding Window和Ring Buffer有何本质区别?
根本瓶颈:
- 内存墙:KV Cache内存O(n²)增长(n为序列长度)
- 计算墙:Attention计算复杂度O(n²),FlashAttention-2虽优化到O(n),但kernel launch overhead随n增大
- 延迟墙:长序列导致首token延迟(TTFT)飙升,用户体验崩溃
Sliding Window vs Ring Buffer:
| 特性 | Sliding Window | Ring Buffer |
|---|---|---|
| 原理 | 只保留最近w个token的KV Cache | 循环覆盖最老token的cache slot |
| 内存占用 | O(w) | O(w) |
| 实现难度 | 需修改attention mask逻辑 | 需定制cache索引管理 |
| 适用场景 | 推理服务(如vLLM) | 嵌入式设备(内存极度受限) |
| 缺陷 | 窗口外信息永久丢失 | 若窗口滑动过快,关键信息被覆盖 |
真实案例:
我们在部署Qwen2-72B时,发现sliding_window=4096在长文档摘要任务中效果骤降。分析日志发现:模型在生成摘要时,反复访问文档开头的实体名,但这些token早已滑出窗口。解决方案:改用Hybrid Window——对文档开头固定保留2048 token,其余部分用sliding window,内存增加15%,但ROUGE-L提升2.3。
3.6 Q6:FlashAttention和PagedAttention的核心创新分别是什么?为什么它们能共存?
FlashAttention创新点:
- IO Aware计算:将attention计算分解为多个block,使每个block的中间结果驻留shared memory,避免反复读写global memory
- 数值稳定:在block内重计算softmax,保证数值精度
- 效果:将attention从O(n²)内存访问降至O(n),速度提升3倍
PagedAttention创新点:
- 内存虚拟化:将KV Cache视为逻辑连续、物理离散的page集合,类似OS的虚拟内存
- 零拷贝调度:不同请求的token可共享同一page,消除内存复制开销
- 效果:显存利用率从~30%提升至~70%,支持高并发
为何共存?
- FlashAttention优化单次计算效率,PagedAttention优化多请求内存管理
- vLLM正是二者结合:用FlashAttention计算每个page的attention,用PagedAttention调度page
- 类比:FlashAttention是“高效引擎”,PagedAttention是“智能交通调度系统”
3.7 Q7:Continuous Batching如何解决请求到达时间差问题?Scheduler的关键决策是什么?
传统Batching痛点:
- Static Batching:等待所有请求到达才启动,TTFT高
- Dynamic Batching:按固定时间窗口聚合,仍存在等待
Continuous Batching破局点:
- 请求即处理:新请求到达时,立即插入正在运行的batch,无需等待
- 动态长度对齐:通过padding和mask,让不同长度请求共存于同一batch
- 关键支撑:PagedAttention(否则无法动态插入)
Scheduler三大决策:
- Admission Control:根据剩余显存和当前负载,决定是否接受新请求
- Scheduling Policy:优先处理TTFT敏感型请求(如聊天),延后吞吐敏感型(如批量摘要)
- Memory Reclamation:当显存紧张时,主动释放已完成请求的page
实操参数:
在vLLM中,--max-num-seqs=256(最大并发数),--block-size=16(page大小),--swap-space=100(CPU swap空间GB)。我们曾将block-size从16调至32,显存碎片率下降12%,但首次生成延迟上升8ms——这就是典型的工程权衡。
3.8 Q8:长文本生成中,如何保证跨窗口的一致性?Memory Management有哪些陷阱?
一致性挑战:
- 窗口滑动导致上下文断裂,模型忘记前文关键约束(如“不要使用专业术语”)
- KV Cache被覆盖,历史状态丢失
工业级解决方案:
- Summary-based Context Compression:用轻量模型(如TinyLlama)定期总结已处理文本,将summary作为新context注入
- Hierarchical Attention:对长文档分块,先做块间attention,再做块内attention
- Stateful Generation:维护一个外部memory vector,与每个token的hidden state交互
Memory Management陷阱:
- False Sharing:多个线程同时更新同一cache line的KV page,引发总线争用
- TLB Miss:长序列导致page table entries激增,TLB缓存溢出,内存访问延迟飙升300%
- GPU-CPU Transfer Overhead:当启用swap时,频繁的page换入换出拖垮吞吐
避坑指南:
- 监控
nvidia-smi -l 1的replay指标,>100表示严重TLB miss - 使用
cuda-memcheck --tool racecheck检测false sharing - 对长文本任务,预分配足够大的
--swap-space,避免runtime swap thrashing
4. 面试官视角:8个问题背后的淘汰红线与加分项
4.1 淘汰红线:那些看似合理却致命的回答
面试不是找完美答案,而是识别认知盲区。以下回答一旦出现,基本判定基础不牢:
- Q1答“Token就是分词”→ 暴露未接触过subword算法,不了解BPE/WordPiece的merge逻辑
- Q2答“除以√d_k是为了让数字变小”→ 缺乏概率论直觉,无法理解方差控制的本质
- Q3用“显存不够”笼统回答→ 未建立量化思维,不懂如何将理论公式转化为工程约束
- Q4说“RoPE靠旋转保证位置”但说不出相对位置内积不变→ 停留在表面描述,未掌握数学本质
- Q5称“Sliding Window就是删掉旧token”→ 忽略mask机制对attention计算的影响,工程实现脱节
- Q6混淆FlashAttention和PagedAttention的优化维度→ 概念模糊,缺乏系统级架构理解
- Q7认为“Continuous Batching就是把请求塞进batch”→ 不懂scheduler的实时决策逻辑
- Q8提议“加大KV Cache”解决长文本→ 无视内存墙,缺乏成本意识
注意:红线不是知识点缺失,而是思维模式缺陷。比如Q3,即使算错具体数字,但能清晰列出公式各变量含义,并说明如何用
nvidia-smi验证,仍属合格。
4.2 加分项:让面试官眼前一亮的实战细节
真正拉开差距的,是那些教科书不会写、但工程师天天面对的细节:
Q1加分细节:
“我在调试prompt注入时,发现tokenizer对<|im_start|>这类special token的处理有bug——它被错误地split成['<|im', '_start|>'],导致attention mask错位。解决方案是手动将special token加入vocab,并设置is_split_into_words=False。”Q3加分细节:
“Qwen2-7B的KV Cache在FP16下实际占用比理论多3%,因为FlashAttention-2的block size=16,导致最后一block padding浪费。我们通过修改flash_attn_interface.py的BLOCK_SIZE为8,节省了2.1GB显存。”Q5加分细节:
“Sliding Window的mask不是简单截断,而是要确保causal mask和window mask的逻辑与(AND)。我们曾因mask计算顺序错误,导致模型在窗口边界处生成重复内容。”Q8加分细节:
“长文本生成的memory leak不是Python对象,而是CUDA context未释放。用torch.cuda.empty_cache()无效,必须调用torch._C._cuda_clearCublasWorkspaces()才能彻底清理。”
这些细节的价值在于:证明你真的在生产环境里摔过跤,而不是在notebook里跑通demo。
4.3 面试官的隐藏期待:从“答题者”到“共建者”的转变
最后一个问题,往往没有标准答案,而是观察你的工程哲学:
“如果让你设计下一代LLM推理框架,你会优先解决什么问题?为什么?”
- 初级回答:“支持更大context”、“更快的attention”——停留在技术点层面
- 中级回答:“构建统一的memory interface,让KV Cache、external memory、disk swap能无缝切换”——看到系统抽象
- 高级回答:“定义LLM的‘内存契约’(Memory Contract):明确告知模型‘你拥有的context是临时的、可能被覆盖的’,让模型学会主动压缩、总结、提问,而非被动依赖无限窗口。这需要修改training objective,而不仅是推理优化。”
后者让我立刻标记为“架构师潜力”。因为它超越了工具使用,触及了人机协作的本质——不是让机器记住一切,而是教会它如何聪明地遗忘。这正是当前LLM工程最稀缺的思维。
5. 复盘与行动清单:如何用这8个问题重构你的学习路径
5.1 诊断你的知识缺口:一份可执行的自测表
别急着背答案。先用这份清单,诚实评估自己在哪一层卡住了:
| 层级 | 自测问题 | 达标表现 | 补救行动 |
|---|---|---|---|
| 概念层 | 能否手写BPE merge过程?能否推导RoPE旋转矩阵? | 在白板上5分钟内完成推导,且能解释每一步物理意义 | 重读《Attention Is All You Need》附录,用numpy实现RoPE |
| 机制层 | 能否估算Qwen2-7B在A100上的最大并发?能否画出PagedAttention的page mapping图? | 给出具体数字,并说明计算依据;能画出logical-to-physical page映射 | 部署vLLM,用--trace参数生成profiling report,分析memory trace |
| 工程层 | 能否修改FlashAttention源码调整block size?能否为custom tokenizer添加special token? | 在GitHub fork中提交PR,修复一个real bug | 参与HuggingFace transformers的issue triage,从debugging开始 |
提示:达标不是“知道”,而是“能做”。比如Q3,光会公式不算达标,必须能在10分钟内,用
nvidia-smi和ps aux定位一个OOM进程的显存泄漏点。
5.2 学习路径重构:从“学模型”到“造管道”
传统学习路径(论文→代码→调参)已失效。推荐新路径:
逆向工程起点:
下载vLLM源码,从engine/llm_engine.py开始,跟踪一个request如何变成execute_model调用,沿途记录每个stage的tensor shape和memory footprint。动手改造实验:
- 将Qwen2-7B的
sliding_window从4096改为2048,观察loss变化 - 替换tokenizer为SentencePiece,对比token count差异
- 在FlashAttention kernel中添加timing print,测量不同seq_len的latency
- 将Qwen2-7B的
生产环境镜像:
用Docker构建一个最小推理镜像:FROM nvcr.io/nvidia/pytorch:23.10-py3 RUN pip install vllm==0.4.2 flash-attn==2.5.0 COPY model/ /models/qwen2-7b/ CMD ["python", "-m", "vllm.entrypoints.api_server", "--model", "/models/qwen2-7b"]用
docker stats监控内存,用curl压测,直到你熟悉每个参数对rps和p99 latency的影响。
5.3 我的个人经验:踩过的坑比读过的论文更有价值
最后分享一个血泪教训:
去年我们上线一个法律合同分析服务,标称支持128K context。上线首日,用户上传一份300页PDF(约150K tokens),服务直接OOM。排查发现:PDF解析时未做文本清洗,大量空白符和换行符被tokenizer吃掉,生成超长无意义token序列。解决方案不是升级GPU,而是加了一行正则:text = re.sub(r'\s+', ' ', text).strip(),token count从150K降到45K,问题解决。
这提醒我:LLM工程的终极战场,永远在数据入口。无论多炫酷的模型架构,如果输入管道有漏洞,一切优化都是空中楼阁。所以现在我的团队,花40%时间在tokenizer和data loader上,而不是调参。
如果你今天只记住一件事,请记住:
面试不是考你多懂LLM,而是考你多懂“让LLM在现实世界里活下来”的代价与妥协。
那8个问题,不过是通往这个真相的8级台阶。