Skip to content

十、垂直领域与系统设计 / 十一、大模型进阶与前沿 / 十二、大厂面经真题 / 十三、AI 应用开发 ​

本主题题目目录
点击题号跳转;右侧目录会高亮你正在阅读的题目
236. 如何从零训练开源大模型的完整技术栈选型?237. 如何设计多租户 LLM 服务的计费策略?238. LLM 服务的监控指标体系如何设计?239. 如何设计高并发 AI 服务架构?240. 如何搭建 RAG 知识库系统?完整链路是什么?241. 如何设计 LLM 服务的备用模型降级策略?242. 大模型服务如何做灰度发布和 A/B 测试?243. 金融场景的大模型合规设计有哪些要点?246. 通用大模型 vs 专业领域大模型,如何权衡选型?247. SSE 和 WebSocket 有什么区别?AI 应用为什么用 SSE?248. 用 Spring Boot 如何实现 SSE 流式输出?249. 前端如何接收 SSE 流式响应?250. 多用户聊天会话如何隔离?251. DeepSeek-R1 是什么?推理模型和普通模型有什么本质区别?253. NAS(神经架构搜索)在大模型设计中有什么应用?254. 稀疏化训练和 MoE 有什么区别?255. DyT(Dynamic Tanh)是什么?为什么可以替代 LayerNorm?258. 如何让大模型支持更长的上下文?257. 主流开源模型体系有哪些?如何对比和选型?261. 预训练数据如何收集和清洗?大规模爬虫怎么选型?263. 合成数据对模型能力有什么影响?有什么风险?266. Qwen2-72B 的上下文长度是多少?YaRN 是怎么扩展的?268. Transformer 中的 Attention:为什么用 softmax?为什么用点积而不是余弦?269. 训练大模型时如何优化显存占用?276. KV Cache 的加速体现在哪里?277. 大模型分布式训练的并行策略和矩阵切分怎么理解?278. 请完整讲解一遍 Transformer 架构。280. 没有强化学习经验,如何快速入手 AI 智能体开发?282. Spring AI 是什么?有哪些核心特性?285. 如何实现 AI 多轮对话?如何解决对话记忆的持久化问题?286. Spring AI 的结构化输出如何实现?287. Spring AI 中如何构建 RAG 知识库系统?291. 如何用 Spring AI 实现联网搜索工具?295. 分层智能体架构是什么?怎么设计?296. AI 应用为什么选择 Serverless 部署?有什么限制?300. 如何保证 AI 应用的性能和稳定性?

覆盖系统设计、垂直领域、SSE 流式、进阶前沿(DeepSeek-R1/MoE/DyT)、大厂真题、Spring AI 应用开发,共 65 题(236-300)。


十、垂直领域与系统设计(236-250) ​


236. 如何从零训练开源大模型的完整技术栈选型? ​

👔面试官:如果让你从零训练一个开源大模型,技术栈怎么选?

🙋‍♂️我:用 PyTorch 训练,然后部署到服务器上就行了。

👔面试官:PyTorch 只是基础框架。几百亿参数的模型,一张卡塞不下,你怎么分布式训练?数据 TB 级别,怎么清洗和管理?损失 spike 了怎么发现和处理?这些你有考虑过吗?

🙋‍♂️我:那……用多卡训练,数据用 pandas 处理一下?

👔面试官:TB 级数据用 pandas?单机内存放不下,你觉得 pandas 能处理吗?分布式训练里张量并行、流水线并行、数据并行分别解决什么问题,你说说?

被问住了吧。从零训练大模型不是在自己电脑上跑个脚本,涉及的技术栈比想象中复杂得多。下面我来把整个技术栈从头到尾捋一遍。

💡 简要回答 ​

从零训练一个大模型,技术栈可以按五个维度来选:

  • 分布式训练框架:Megatron-LM + DeepSpeed,前者负责模型并行(张量并行 + 流水线并行),后者负责 ZeRO 显存优化
  • 训练基础框架:PyTorch,混合精度用 BF16
  • 数据处理:Apache Spark 或 Ray Data,处理 TB 级语料的清洗、去重、质量过滤
  • 训练监控:Weights & Biases(W&B)实时跟踪 loss 曲线和梯度范数
  • 推理部署:vLLM 或 TGI,训练完之后的服务化

每个维度不是随便选的,背后都有具体的工程理由,下面挨个讲。

📝 详细解析 ​

训练大模型为什么要这么多工具? ​

先建立一个直觉:训练一个 7B 参数的模型,光存参数就需要 14GB 显存(FP16 下每个参数 2 字节),加上激活值、梯度、优化器状态,实际需要 60~80GB 显存。一张 A100 80GB 刚好能塞下,但稍微大点的 13B、70B,单卡根本装不下。你不能因为模型变大就每次都升级更贵的卡,工程上的解法是多卡分布式训练。

这就是为什么从零训练 LLM 不是一个「装好 PyTorch 就能跑」的事情,它本质上是一个分布式系统工程问题。

第一步:数据工程(最耗时,决定上限) ​

训练大模型,数据质量 > 数据量,这是圈子里公认的第一原则。GPT-4 技术报告里一句话说得好:「训练数据的质量是模型能力的天花板」。

数据工程的流程大概是这样的:

数据收集:主要来源是 Common Crawl(互联网爬虫数据集,TB 级),直接拿来用比自己爬划算得多,另外还会补充 GitHub 代码、学术论文、书籍等高质量来源。

质量过滤:这一步最耗工程时间。具体包括:语言检测(只保留目标语言)、最短长度过滤(去掉几十个字的碎片)、URL 去重(同一页面被多次爬取)、规则过滤(去掉充斥广告、乱码的垃圾内容)、质量评分(训练一个小分类器判断文本质量)。

为什么不用 pandas:TB 级数据单机内存根本放不下,必须用分布式数据处理框架。Apache Spark 是经典选择(Java 生态友好),PySpark 写起来也方便;Ray Data 是更 Python 原生的替代方案。

Tokenizer 训练:用 SentencePiece 训练 BPE 分词器,词表大小一般 32K~150K,中文场景需要适当扩大词表覆盖常用汉字。

数据格式化:最终把文本转成 .bin + .idx 格式(内存映射文件),训练时直接 mmap 读取,避免把所有数据加载进内存。

第二步:模型架构(直接参考 LLaMA-3) ​

现在从零训练不需要从头设计架构,直接照着 LLaMA-3 的配置来就行。现代 LLM 的标准配置是:

  • 位置编码:RoPE(相对位置编码,外推性更好)
  • 注意力:GQA(Grouped Query Attention,比标准 MHA 省显存,比 MQA 效果好)
  • 前馈层:SwiGLU(比 ReLU 效果更好的激活函数)
  • 归一化:RMSNorm + Pre-LayerNorm(训练更稳定)

这套配置是社区反复验证过的,不需要自己折腾。

第三步:分布式训练 ​

这是技术栈里最核心的部分。大模型训练需要同时用到三种并行方式:

数据并行(DP):最简单直觉,每张卡跑一份完整模型,但 batch 数据不一样,算完梯度之后 AllReduce 同步。问题是模型太大时单卡还是装不下。

张量并行(TP):把单个矩阵按行或列切分到多张卡上,多卡联合完成一次矩阵乘法。Megatron-LM 负责这个,它对 Transformer 的 MHA 和 FFN 做了专门的切分优化,通信量最小。

流水线并行(PP):把模型的层切分到多张卡,第 1~4 层在卡 0,第 5~8 层在卡 1,数据像流水线一样流过。问题是有流水线气泡(有的卡在等上一级),需要做 micro-batch 切分来填满流水线。

实际工程里这三种并行组合使用,以 7B 模型、8×A100 为例,典型配置是 TP=2, PP=1, DP=4。

DeepSpeed 的 ZeRO(Zero Redundancy Optimizer)则是解决优化器状态的显存问题。Adam 优化器需要存参数、梯度、一阶矩、二阶矩,实际占用是参数量的 4 倍。ZeRO-3 把这些分片到所有卡上,每张卡只存 1/N,显存需求大幅降低。

训练超参的典型配置(7B,8×A100):

并行配置:TP=2, PP=1, DP=4
global_batch_size = 4M tokens
学习率:3e-4,cosine decay,warmup=2000 steps
混合精度:BF16(比 FP16 数值范围更大,训练更稳定)

第四步:训练监控 ​

训练过程中最怕两件事:loss 飙升(loss spike)和悄悄崩了自己不知道。W&B 是目前用得最多的监控工具,实时可视化以下指标:

  • Loss 曲线:应该平稳下降,出现 spike 要立刻回滚到上一个 checkpoint
  • 梯度范数(grad norm):过大说明训练不稳定(可能需要调小学习率或加 gradient clipping),过小说明学习停滞
  • GPU 利用率:低于 70% 说明通信成了瓶颈,需要调整并行配置或减少跨节点通信

Loss spike 的应对:保存足够频繁的 checkpoint(每几百步一个),发现 spike 立刻回滚,然后排查是数据里混进了脏数据还是学习率过大。

第五步:后训练对齐 ​

预训练出来的基础模型还不能直接给用户用,需要经过对齐阶段:

SFT(监督微调)→ 奖励模型训练 → PPO/DPO → 安全对齐

这部分的技术栈相对轻量,SFT 和 DPO 都可以在单卡或几卡上完成,不需要再用完整的分布式训练框架。

🎯 面试总结 ​

从零训练 LLM,面试官想听到的不是「用 PyTorch 训练」,而是你对五个阶段的系统认知:

数据工程(最耗时,决定上限)→ 架构设计(照 LLaMA-3 来,RoPE+GQA+SwiGLU+RMSNorm)→ 分布式训练(Megatron 做模型并行,DeepSpeed ZeRO 压显存)→ 训练监控(W&B 跟踪 loss/梯度/GPU 利用率,loss spike 立刻回滚)→ 后训练对齐(SFT → RM → PPO/DPO)。

记住一句话:数据质量 > 数据量,这是从零训练大模型的第一原则,说出来面试官就知道你真正理解过这个问题。


237. 如何设计多租户 LLM 服务的计费策略? ​

👔面试官:你们的 AI 服务要对外提供多租户 SaaS,计费策略怎么设计?

🙋‍♂️我:按调用次数收费呗,每次请求收一毛钱。

👔面试官:按次数?用户发了一条「你好」和一条几千字的分析请求,LLM 的计算成本差了几十倍,你都收一毛钱?

🙋‍♂️我:那按字符数?

👔面试官:LLM 内部是按 token 处理的,不是按字符。而且输入 token 和输出 token 的成本不一样——生成每个 token 都需要做一次完整的前向推理,但输入只是 KV Cache 一次计算。你这些都没考虑到,还有多租户之间的资源隔离呢?

按次收费是最直觉的方案,但放到 LLM 服务里完全行不通。下面来讲正确的设计思路。

💡 简要回答 ​

LLM 服务计费的标准做法是按 token 计费,输入和输出分开定价,因为两者的计算成本结构不同。同时需要做资源隔离,每个租户有独立的速率限制(QPM + TPM),防止大租户打垮小租户的服务质量。

📝 详细解析 ​

为什么要输入输出分开计价? ​

LLM 处理一次请求,输入 token 和输出 token 的成本不对等。输入部分可以通过 KV Cache 做批处理,相对廉价;输出部分是自回归生成,每生成一个 token 都需要完整走一遍前向传播,而且还要维护 KV Cache 的内存,成本更高。所以 OpenAI 的定价体系里,输出 token 通常是输入 token 的 2~4 倍。

计费模型核心逻辑:

python
# LLM 服务计费:按 Token 计费(输入 + 输出分别计价)
class UsageTracker:
    # 不同模型不同单价(能力越强价格越高)
    PRICING = {
        "gpt4_level":  {"input": 0.03, "output": 0.06},   # 每千 token 价格(美元)
        "gpt35_level": {"input": 0.001, "output": 0.002},
    }

    def record_usage(self, tenant_id, model, input_tokens, output_tokens):
        price = self.PRICING[model]
        cost = (input_tokens / 1000) * price["input"] \
             + (output_tokens / 1000) * price["output"]
        # 写入计费数据库,按天聚合
        self.billing_db.append(tenant_id, cost)

多租户资源隔离 ​

多租户最怕的是「一个大客户把资源打满,导致小客户服务降级」。解决方案是给每个租户配置独立的限流配额,至少两个维度:

  • QPM(每分钟请求数):防止某个租户疯狂并发,打满 API Gateway
  • TPM(每分钟 token 数):防止某个租户发超长 prompt,打满 LLM worker 的 GPU

配额可以按套餐分级(免费版/专业版/企业版),企业客户可以协商专属配额。超出配额时返回 429 Too Many Requests,让客户端做退避重试。

🎯 面试总结 ​

LLM 计费设计三个要点:输入输出分开计价(生成成本高于输入)、不同模型分级定价(能力越强越贵)、多租户资源隔离(QPM + TPM 双维度限流,防大租户打垮小租户)。说出这三点,面试官就知道你理解了 LLM 服务的成本结构。


238. LLM 服务的监控指标体系如何设计? ​

👔面试官:你们上线了一个 AI 服务,监控体系怎么建?

🙋‍♂️我:监控 CPU、内存、请求成功率就行了,跟普通后端服务一样。

👔面试官:LLM 服务的核心链路是 GPU 推理,CPU 内存监控能反映什么问题?用户问了一个问题,模型回答了但是答得一塌糊涂,这在你的监控里能发现吗?还有,用户第一个 token 要等多久,你的监控关注了吗?

AI 服务的监控跟普通 Web 服务差异很大。普通服务你关心的是请求延迟和成功率,但 LLM 服务还需要关注生成质量——它不只是「能不能返回」,而是「返回得对不对、好不好、快不快」。

💡 简要回答 ​

LLM 服务监控分四个层次:业务层(用户是否满意)、性能层(延迟和吞吐)、质量层(答案对不对)、成本层(烧了多少钱)。四层缺一不可,只看性能层而忽视质量层是最常见的监控盲区。

📝 详细解析 ​

性能层:LLM 专属的延迟指标 ​

LLM 的延迟跟普通 API 不一样,它是流式生成的,用户感知的延迟有两个维度:

  • TTFT(Time To First Token,首 token 延迟):用户发完问题到看到第一个字的时间。这个指标决定了用户「感觉快不快」,TTFT < 1s 用户基本不觉得慢。
  • TPOT(Time Per Output Token,每 token 生成时间):后续 token 的生成速度,决定了「打字速度够不够快」。

P50/P99 两个分位数都要监控,P99 代表了 1% 最慢请求的体验,往往能暴露异常。

性能层指标:
  TTFT P50 / P99(首 token 延迟,< 1s 为佳)
  TPOT P50 / P99(生成速度,> 30 tokens/s 流畅)
  系统吞吐量(tokens/s)
  GPU 利用率(< 70% 说明有瓶颈或资源浪费)

质量层:最容易被忽视的监控盲区 ​

很多团队只监控性能,忽视质量,结果模型一直在回答错误的内容,用户流失了也不知道原因。质量层至少要监控:

  • 用户满意度:👍/👎 反馈率,最直接的质量信号
  • 幻觉率:定期对一批标准问题做自动评估(或人工抽样)
  • RAG 场景:知识库命中率,命中率低说明检索有问题
质量层指标:
  用户满意度(👍率 / 👎率)
  幻觉率(定期抽样,人工或自动评估)
  Guardrail 触发率(安全过滤命中率,过高说明用户在「攻击」)
  知识库命中率(RAG 场景专属)

业务层 + 成本层 ​

业务层指标:
  DAU / MAU(活跃用户规模)
  对话完成率(用户是否完成了整个任务,中途放弃 = 体验差)

成本层指标:
  Token 单价 × 月用量 = 模型调用成本
  GPU 小时数 × 单价 = 自建算力成本
  成本/DAU(每个活跃用户的成本,控成本的核心指标)

🎯 面试总结 ​

LLM 监控的核心认知:比普通后端多出两个维度——质量层和 LLM 专属性能指标(TTFT/TPOT)。面试时把四层说全(业务/性能/质量/成本),并能说清楚 TTFT 和 TPOT 的区别,面试官就知道你真正做过 AI 服务,而不只是会调 API。


239. 如何设计高并发 AI 服务架构? ​

👔面试官:你们的 AI 服务要支撑高并发,架构怎么设计?

🙋‍♂️我:多部署几台服务器,前面加个负载均衡,跟普通 Web 服务一样就行了。

👔面试官:LLM 推理单次请求耗时 1~30 秒,比普通接口慢了几十倍,直接同步等待会怎样?

🙋‍♂️我:那……加个线程池做异步?

👔面试官:LLM 推理是 GPU 密集型,瓶颈在 GPU 不在线程。同一个 GPU 同时跑多个请求怎么最大化利用率?用户问了一模一样的问题你每次都调 LLM 吗?GPU 故障了怎么保服务可用性?你有没有想过这些问题?

高并发 AI 服务架构跟普通 Web 服务的差异远比你想象的大,核心矛盾是 LLM 推理慢、成本高、有状态(流式输出),需要专门的设计。下面来讲清楚。

💡 简要回答 ​

高并发 AI 服务的架构核心是五层设计:API Gateway 限流认证 → 消息队列削峰 → LLM Worker Pool(Continuous Batching) → 语义缓存命中 → 降级策略兜底。

与普通 Web 服务最本质的不同:LLM 是有状态的流式生成,单次请求耗时长(1~30s),必须用异步队列解耦,同时靠 Continuous Batching 把 GPU 吃满。

📝 详细解析 ​

整体架构 ​

用户请求
    ↓
API Gateway(限流 + 认证 + 路由)
    ↓
语义缓存层(相似问题直接命中,跳过后续)
    ↓
消息队列(Redis/Kafka,请求缓冲 + 削峰填谷)
    ↓
LLM Worker Pool(多个 vLLM 实例,Continuous Batching)
    ↓
SSE 流式推送结果给用户

为什么要用消息队列? ​

LLM 单次推理耗时 1~30 秒,流量突增时如果直接同步转发到 LLM Worker,Worker 立刻过载,所有请求都超时。消息队列做的事是削峰填谷:请求进来先入队,立刻返回「请求已受理」,Worker 按自己的处理速度从队列里取任务,即使峰值流量很大,Worker 也不会被打垮。

结果通过 SSE 推流还是 Webhook 回调取决于场景:用户直接在网页等结果用 SSE,后台异步任务用 Webhook。

Continuous Batching:GPU 利用率的关键 ​

朴素的 LLM 推理是一个请求跑完再跑下一个,GPU 大部分时间在等待,利用率很低。Continuous Batching(vLLM 的核心特性)的思路是:不同请求处于不同的生成阶段(有的刚开始,有的已经生成了几十个 token),把它们放进同一个 batch 一起推理,GPU 始终满负荷运行。效果是相同硬件下吞吐量提升 5~10 倍。

语义缓存(Semantic Cache) ​

很多用户会问相似甚至完全一样的问题,每次都调 LLM 很浪费。语义缓存的思路:把问题向量化,新问题进来先查缓存,相似度超过阈值直接返回缓存答案,不用经过 LLM:

python
def semantic_cache_lookup(query, threshold=0.95):
    query_emb = embed(query)
    results = cache_vectorstore.similarity_search(query_emb, k=1)
    if results and cosine_sim(query_emb, results[0].embedding) > threshold:
        return results[0].metadata["cached_answer"]  # 缓存命中,直接返回
    return None  # 未命中,走 LLM

注意阈值要设高(0.95+),因为问题细节不同答案可能完全不一样,太低的阈值会返回错误的缓存。

降级策略 ​

GPU 故障或流量远超预期时,如果直接返回 500,用户体验崩溃。降级策略:高负载时自动切换到更小更快的模型(质量略降但服务不中断),或者返回一个「稍后再试」的友好提示,而不是报错。

自动扩缩容:监控队列深度,队列积压超过阈值(如 50 条)自动拉起新的 LLM Worker;GPU 利用率低于阈值(如 40%)时自动缩减 Worker,节省算力成本。

🎯 面试总结 ​

高并发 AI 服务架构面试的加分点在于:能说清楚和普通 Web 服务的本质差异——LLM 单次请求耗时长、GPU 是瓶颈、有状态流式输出。然后把五层架构讲出来:限流 → 语义缓存 → 消息队列削峰 → Continuous Batching 压榨 GPU → 降级兜底。能说清楚 Continuous Batching 比朴素批处理提升在哪,会很加分。


240. 如何搭建 RAG 知识库系统?完整链路是什么? ​

👔面试官:如果让你从零搭建一个企业知识库问答系统,完整链路怎么设计?

🙋‍♂️我:就是把公司文档上传进去,用户提问的时候检索一下,把结果给大模型回答。

👔面试官:文档上传进去之前要做什么处理?文档不是直接能检索的,格式转换、切块、向量化这些你跳过了。检索出来之后直接给 LLM 吗?结果精不精准你怎么保证?答案来源怎么追溯?

🙋‍♂️我:那就……先向量化再检索?

👔面试官:只用向量检索?关键词完全匹配的场景怎么办?向量检索和 BM25 各有优势你知道吗?检索完之后 Rerank 是什么,为什么要做?

被问住了吧。RAG 系统看起来简单,其实有一套完整的工程链路,每个环节都有讲究。下面我把完整架构拆开说清楚。

💡 简要回答 ​

RAG 知识库系统分两条线:离线 ETL 管道和在线服务链路,两者分工明确,离线负责建库,在线负责检索和生成。

离线:文档解析 → 文本清洗 → 语义切块 → Embedding 向量化 → 写入向量库

在线:查询重写 → 混合检索(Dense + BM25)→ Reranking 精排 → 上下文压缩 → LLM 生成 → 引用附加

📝 详细解析 ​

整体架构 ​

┌──────────────────┬──────────────────────────────────────┐
│   离线 ETL 管道   │             在线服务                   │
│                  │                                        │
│ 文档上传          │  用户提问                               │
│   ↓              │    ↓                                   │
│ 文档解析          │  查询重写(口语化 → 检索友好)              │
│ (PDF/Word/HTML)  │    ↓                                   │
│   ↓              │  混合检索(Dense Embedding + BM25)      │
│ 文本清洗          │    ↓                                   │
│   ↓              │  Reranking(Cross-Encoder 精排)         │
│ 语义切块          │    ↓                                   │
│ (500~1000 token)│  上下文压缩(可选,token 太多时)           │
│   ↓              │    ↓                                   │
│ 元信息标注        │  Prompt 组装                            │
│   ↓              │    ↓                                   │
│ Embedding 向量化  │  LLM 生成(流式输出)                    │
│   ↓              │    ↓                                   │
│ 写入向量库(Milvus)│  来源引用附加(「来自:XXX 文档第3页」)      │
└──────────────────┴──────────────────────────────────────┘

离线 ETL 管道的关键细节 ​

文档解析:不同格式的文档需要不同的解析器。PDF 要处理多列、表格、图片(可选 OCR);Word 要保留标题层级;HTML 要去掉 JS/CSS 标签留下正文。

文本清洗:去掉页眉页脚、乱码字符、重复换行,保留文档的语义结构。

语义切块:这一步最容易被忽视。chunk 太大(2000 token)信息混杂,检索不精准;太小(50 token)语义不完整。推荐 500~1000 token,带 100 token 的重叠窗口,防止语义在边界被切断。有标题结构的文档按 Markdown 标题切,比固定大小效果好。

元信息标注:每个 chunk 存文件名、页码、章节、关键词等 metadata,检索时可以按 metadata 过滤(「只搜 HR 部门文档」),也用于最终答案的来源引用。

在线服务链路的关键细节 ​

查询重写:用户的提问往往口语化或者依赖上下文,比如「上次说的那个政策呢」,直接拿这句话去检索什么都找不到。查询重写让 LLM 把用户问题改写成独立的、检索友好的形式。

混合检索(Hybrid Search):向量检索(Dense)擅长语义相似,能处理「iPhone 截图」≈「苹果手机截屏」这样的同义表达;BM25 擅长关键词精确匹配,对专有名词(人名、产品型号)更准。两者互补,混合使用比单用任何一种都好,最后用 RRF(Reciprocal Rank Fusion)把两路结果合并排序。

Reranking 精排:向量检索召回的 Top-20 结果里难免有「看起来相似但实际不相关」的噪音。Rerank 模型(Cross-Encoder 结构)把问题和每个 chunk 拼在一起深度理解相关性,重新排序,最终只保留 Top-3~5。可以理解为:向量检索是海选,Reranking 是复试。

引用附加:最终答案里附上「来源:XXX 文档,第3页」,让用户可以自己去原文核实,大幅提升可信度,也是 RAG 相比纯 LLM 最核心的优势之一。

🎯 面试总结 ​

RAG 系统架构面试,说清楚两条线就够了:离线 ETL(解析→清洗→切块→向量化→存库)和在线服务(查询重写→混合检索→精排→生成→引用附加)。能说出混合检索(Dense + BM25)和 Reranking 这两个点,说明你理解了检索质量才是 RAG 系统的核心瓶颈,不是换更强的 LLM,而是把检索做准。


241. 如何设计 LLM 服务的备用模型降级策略? ​

👔面试官:你们的主模型 GPT-4 突然调用失败了,服务怎么保证不中断?

🙋‍♂️我:重试几次就行了。

👔面试官:重试几次如果还是失败呢?而且重试期间用户一直在等,超时了怎么办?假如主模型每隔几分钟就抖动一次,你难道要一直靠重试来撑?

🙋‍♂️我:那就……换个模型?

👔面试官:怎么换?什么时候触发换?换回来的时机怎么判断?主模型恢复了你还在用备用模型,怎么自动切回?这套熔断和恢复的逻辑你设计过吗?

靠重试是最常见的错误思路。生产环境中,正确的做法是熔断器模式,下面来讲。

💡 简要回答 ​

LLM 服务的降级策略核心是熔断器模式(Circuit Breaker):正常时走主模型,主模型连续失败超过阈值时「熔断」,自动切到备用模型;经过一段冷却时间后,尝试放行少量流量给主模型探测是否恢复,恢复了就自动关闭熔断器切回主模型。

📝 详细解析 ​

熔断器有三个状态:Closed(正常)、Open(熔断,走备用)、Half-Open(探测恢复中)。

python
class ModelRouter:
    """主模型 → 备用模型,熔断器模式"""
    def __init__(self):
        self.primary = GPT4Client()
        self.fallback = GPT35Client()       # 备用(更便宜/更快,质量略低)
        self.circuit_breaker = CircuitBreaker(failure_threshold=5)

    async def generate(self, prompt):
        if self.circuit_breaker.is_open():  # 熔断状态:主模型已故障
            return await self.fallback.generate(prompt)

        try:
            result = await asyncio.wait_for(
                self.primary.generate(prompt),
                timeout=10.0  # 超时也算失败,触发熔断计数
            )
            self.circuit_breaker.record_success()
            return result
        except (TimeoutError, RateLimitError, ServiceError):
            self.circuit_breaker.record_failure()
            return await self.fallback.generate(prompt)  # 本次降级到备用

超时是降级触发的关键:设置合理的 timeout(10s)比无限等待重要,超时直接降级,不让用户傻等。

熔断恢复:熔断触发后,经过冷却时间(比如 30s)进入 Half-Open 状态,放行 10% 流量探测主模型,成功则关闭熔断器切回主模型,失败则重置冷却计时器继续熔断。

🎯 面试总结 ​

LLM 降级的关键认知:不是重试,而是熔断器模式。核心三状态(Closed/Open/Half-Open)加超时触发降级,面试说出来就超过大多数人了。


242. 大模型服务如何做灰度发布和 A/B 测试? ​

👔面试官:你们有一个新版本模型想上线,怎么安全地做灰度发布?

🙋‍♂️我:先在测试环境验证,没问题了全量切流量过去。

👔面试官:直接全量切?新模型在生产环境的表现未必和测试环境一样,出了问题你怎么快速回滚?而且你怎么证明新模型比旧模型好?只看 benchmark 够吗?真实用户满意度你怎么量化?

直接全量上线是最危险的做法,工程上的正解是灰度 + A/B 测试。

💡 简要回答 ​

模型上线走灰度发布:先放 5~10% 流量给新模型(实验组),其余保持旧模型(对照组),同时收集 TTFT、满意度、成本等关键指标,统计显著后再逐步扩大流量直到全量。同一用户始终路由到同一模型,避免同一个用户在两个模型之间跳来跳去体验割裂。

📝 详细解析 ​

python
def route_to_model(user_id: str, new_model_traffic: float = 0.1) -> str:
    # 基于 user_id 哈希,保证同一用户始终用同一模型
    bucket = hash(user_id) % 100
    if bucket < new_model_traffic * 100:
        return "new_model"        # 实验组(10%)
    return "production_model"     # 对照组(90%)

def evaluate_ab_test(experiment_id: str) -> dict:
    control = get_metrics(group="control", experiment_id=experiment_id)
    treatment = get_metrics(group="treatment", experiment_id=experiment_id)
    return {
        "ttft_improvement":   (control["ttft"] - treatment["ttft"]) / control["ttft"],
        "satisfaction_delta": treatment["thumbs_up_rate"] - control["thumbs_up_rate"],
        "cost_delta":         treatment["cost_per_query"] - control["cost_per_query"],
    }

什么时候全量:实验组 vs 对照组的满意度提升统计显著(p < 0.05),且成本不超预算,才全量切换。

什么时候回滚:实验组错误率或满意度👎率显著高于对照组,立即把实验组流量降回 0%。

🎯 面试总结 ​

灰度发布两个要点:用 user_id 哈希做流量分割(保证同一用户体验一致),用业务指标(满意度/成本/延迟)而非 benchmark 来决策全量。说出来面试官就知道你理解生产环境的复杂性。


243. 金融场景的大模型合规设计有哪些要点? ​

👔面试官:你要给一个银行做 AI 辅助审批系统,合规设计上有哪些特殊要求?

🙋‍♂️我:就是加个风控过滤,防止模型输出不合适的内容。

👔面试官:风控过滤只是输出层的一个点。数据合规呢?客户个人信息进了 LLM,怎么保证不被记忆?监管要求 AI 决策可解释,你的模型能解释「为什么拒贷」吗?AI 做了一个错误的审批决策,你能追溯是哪条数据影响了它吗?

金融场景的合规要求比普通 AI 应用严苛得多,下面来系统讲。

💡 简要回答 ​

金融场景大模型合规设计有四个关键维度:数据隐私(客户数据不能被模型记忆)、可解释性(监管要求每个决策可追溯)、审计追踪(所有 AI 辅助决策必须留有完整日志)、人工复核(高风险决策 AI 只提供建议,人工最终拍板)。

📝 详细解析 ​

数据隐私:客户的身份证号、流水数据进入 LLM 时必须做去标识化处理,防止模型「记住」客户隐私。如果用的是调用 API 的方式,需要确认服务商不会用你的数据训练模型(企业版 API 通常有这个保证)。自部署模型在推理时不更新参数,天然不会「记住」单次请求的数据,但日志也要脱敏存储。

可解释性:监管机构要求「AI 为什么做这个决策」。纯黑盒 LLM 的决策本身很难解释,但可以在 Prompt 中要求模型给出决策依据(「请列出影响本次审批结论的3个主要因素」),以 RAG 驱动的决策还能追溯到是哪条具体规则影响了输出。SHAP 等传统可解释 AI 工具也可以搭配小型分类模型使用。

审计追踪:每次 AI 参与的决策必须留日志:输入的原始数据(脱敏)、模型输出、使用的模型版本、时间戳、经手人工审核的结果。监管抽查时要能完整还原决策链路。

人工复核:贷款审批、风险评级这类高风险决策,AI 只能给「建议」,最终结论必须由有资质的人来签字。这既是监管要求,也是出错后责任归属的保障。

🎯 面试总结 ​

金融合规四个维度:数据隐私(去标识化)+ 可解释性(决策有依据)+ 审计追踪(全程留日志)+ 人工复核(高风险决策 AI 只建议)。面试时说出「人工复核」这个点,说明你理解金融场景的风险边界。


246. 通用大模型 vs 专业领域大模型,如何权衡选型? ​

👔面试官:医疗场景,你会选通用大模型还是专门训练一个医疗大模型?

🙋‍♂️我:专业领域用专业模型,当然训练一个医疗大模型啊。

👔面试官:你知道训练一个医疗大模型要多少成本吗?数据怎么来?训完之后通用推理能力会不会退化?如果通用模型 + 医疗知识库能达到 90% 的效果,你还有必要花几百万训一个专业模型吗?

从零训练专业大模型听起来高大上,但在大多数场景里是个「用锤子杀苍蝇」的方案。

💡 简要回答 ​

大多数专业领域场景,通用大模型 + RAG(领域知识库)+ 轻量微调(领域格式/术语) 是性价比最高的方案,不需要从零训练专业大模型。只有在「数据高度保密无法上云」或「通用模型确实达不到所需专业深度」的极少数情况下,才考虑专业模型。

📝 详细解析 ​

通用大模型的优势被低估 ​

通用大模型(GPT-4/LLaMA-3/Qwen)已经在大量医疗、法律、金融文本上预训练过,基础的专业知识是有的。它的推理能力、指令遵循能力、对话能力是几千亿 token 喂出来的,专业模型从头训很难超越这个基础。

从零训练专业模型的代价 ​

专业领域数据难获取(医疗数据有隐私保护,法律文书有版权)、标注成本极高、训练耗时(几周到几个月)、维护成本持续产生(数据更新要重训)、而且训完之后通用能力往往退化(过拟合领域数据)。

推荐方案:组合使用 ​

通用大模型(GPT-4/LLaMA-3)
    +
RAG(领域知识库:医学指南、药典、病历模板)→ 解决知识深度问题
    +
轻量微调 SFT(领域术语、输出格式、对话风格)→ 解决「怎么说」的问题
    ↓
= 兼顾通用推理能力和领域专业性
  成本远低于从零训练专业模型(差 100~1000 倍)

RAG 解决「知识不够深」,微调解决「说话方式不对」,两者结合基本能覆盖 90% 的专业场景需求。

什么时候才需要专业模型? ​

  • 数据极度敏感,无法送到第三方 LLM 推理(比如军事、核心金融数据)
  • 监管明确要求使用本地部署的专有模型
  • 通用模型 + RAG + 微调组合确实无法达到所需精度(需要先验证,不要假设)

🎯 面试总结 ​

专业领域选型面试的核心观点:先组合,再专训。通用大模型 + RAG + 轻微调,性价比最高,先跑通这个路线再说。从零训专业模型是最后的手段,不是第一选择。说出这个观点,面试官就知道你有工程实践的成本意识,而不只是在纸上谈兵。


247. SSE 和 WebSocket 有什么区别?AI 应用为什么用 SSE? ​

👔面试官:ChatGPT 那种打字机效果是怎么实现的?为什么用 SSE 而不是 WebSocket?

🙋‍♂️我:就是服务器一个字一个字地推给客户端嘛,WebSocket 双向通信,SSE 单向,AI 生成用 SSE 就行。

👔面试官:你只说对了结论。SSE 和 WebSocket 的协议层有什么不同?SSE 是 HTTP 协议,WebSocket 是独立协议,这个差别在生产环境里会带来什么影响?SSE 的自动重连机制是什么原理?

能说出选 SSE 的原因,但说不清楚背后的技术差异,面试官知道你只是背了答案。

💡 简要回答 ​

SSE(Server-Sent Events)和 WebSocket 的本质区别是方向性和协议层:SSE 是基于 HTTP 的单向推流(服务器→客户端),WebSocket 是独立协议的全双工通信。

AI 生成选 SSE 有三个理由:AI 输出天然单向、SSE 对 HTTP 代理/CDN/负载均衡友好(不需要特殊配置)、SSE 内置自动重连机制(客户端断线自动恢复)。

📝 详细解析 ​

SSE vs WebSocket 对比 ​

维度SSE(Server-Sent Events)WebSocket
方向单向(服务器→客户端)双向
协议纯 HTTP(长连接)WS(独立握手协议)
重连内置自动重连需手动实现
中间件兼容性好(HTTP 代理、CDN 天然支持)部分代理不支持 WS 升级
适用场景AI 流式输出(单向推)实时聊天(双向收发)

SSE 的自动重连机制:服务器在每个事件里可以带 id 字段,客户端断线重连时会在请求头带上 Last-Event-ID,服务器从这个 id 之后继续推,不用从头开始。这对 AI 生成中途断线的场景很友好。

为什么 AI 应用偏好 SSE? ​

AI 生成是严格单向的——模型输出 token 流式推给用户,用户不需要在生成过程中往服务器发消息。这种单向场景用 WebSocket 是「大炮打蚊子」。

更关键的是部署层面:WebSocket 需要 Nginx/CDN 做特殊配置才能透传,有些老旧的反向代理和企业防火墙根本不支持 WS 协议升级,会把连接直接断掉。SSE 基于 HTTP,什么中间件都能透传,运维友好度高很多。

🎯 面试总结 ​

SSE vs WebSocket 核心记三点:单向 vs 双向、HTTP 协议 vs 独立协议(中间件兼容性)、内置重连 vs 手动实现。AI 应用选 SSE 是因为语义匹配(单向生成)+ 部署友好,不是随便选的。


248. 用 Spring Boot 如何实现 SSE 流式输出? ​

👔面试官:Spring Boot 里 SSE 怎么实现,有什么坑?

🙋‍♂️我:用 SseEmitter,在 Controller 里创建一个 emitter,然后往里 send 数据就行。

👔面试官:SseEmitter 默认超时是多少?LLM 生成可能要 30s 甚至更长,默认超时够用吗?多个并发连接,SseEmitter 放在内存里线程安全吗?生成结束了客户端怎么知道?

SseEmitter 的用法看起来简单,但生产环境里有好几个坑,下面来讲。

💡 简要回答 ​

Spring Boot 用 SseEmitter 实现流式输出,关键点有三:设置足够长的超时(LLM 生成慢,默认 30s 不够)、用异步线程处理(不能阻塞请求线程)、发送明确的结束标志(让前端知道什么时候关闭连接)。

📝 详细解析 ​

java
// Spring Boot SSE 流式响应(完整实现)
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamResponse(@RequestParam String question) {
    SseEmitter emitter = new SseEmitter(60_000L);  // 60s 超时,比 LLM 生成时间留余量

    // 必须用异步线程,否则会阻塞 Tomcat 的请求线程池
    executor.submit(() -> {
        try {
            chatClient.prompt()
                .user(question)
                .stream()
                .chatResponse()
                .doOnNext(response -> {
                    try {
                        String chunk = response.getResult().getOutput().getContent();
                        emitter.send(SseEmitter.event().data(chunk));  // 逐 token 推送
                    } catch (IOException e) {
                        emitter.completeWithError(e);  // 客户端断开连接时会抛 IOException
                    }
                })
                .doOnComplete(() -> {
                    try {
                        emitter.send(SseEmitter.event().data("[DONE]"));  // 结束标志
                        emitter.complete();
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }
                })
                .subscribe();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });
    return emitter;  // 立即返回 emitter,HTTP 长连接保持打开
}

三个关键细节:

  1. new SseEmitter(60_000L) 超时要比 LLM 最长生成时间更长,否则还没生成完就超时断开
  2. 必须放到异步线程(executor.submit),否则阻塞 Tomcat 线程池,几十个并发就把服务打满
  3. [DONE] 结束标志是约定,前端收到后关闭 EventSource

249. 前端如何接收 SSE 流式响应? ​

💡 简要回答 ​

前端用原生 EventSource API 接收 SSE,监听 onmessage 事件逐 token 追加到界面,收到 [DONE] 后关闭连接。

javascript
const eventSource = new EventSource(`/api/stream?question=${encodeURIComponent(question)}`);

eventSource.onmessage = (event) => {
    if (event.data === "[DONE]") {
        eventSource.close();  // 收到结束标志,关闭连接
        return;
    }
    // 逐 token 追加,产生「打字机」效果
    document.getElementById("response").textContent += event.data;
};

eventSource.onerror = (error) => {
    console.error("SSE 连接错误", error);
    eventSource.close();
};

注意:EventSource 不支持 POST 请求,query 参数如果很长(比如带上下文)可能超 URL 限制,生产环境通常改用 fetch + ReadableStream 实现 POST 流式请求。


250. 多用户聊天会话如何隔离? ​

👔面试官:你的 AI 聊天服务有多个用户同时在线,怎么保证每个用户的对话历史互不干扰?

🙋‍♂️我:用 sessionId 区分呗,不同用户不同 sessionId。

👔面试官:sessionId 存在哪?内存里?服务重启了用户历史不就没了?用户一直在线但长时间没发消息,这段历史一直占着内存,怎么回收?

会话隔离不只是「不同 sessionId 存不同 Map」,还有持久化和内存管理两个问题。

💡 简要回答 ​

多用户会话隔离的核心是:每个 sessionId 对应独立的对话历史存储。内存存储简单但重启丢失,生产环境应存 Redis,并配合定期清理不活跃会话避免内存泄漏。

📝 详细解析 ​

java
@Component
public class ChatSessionManager {
    // ConcurrentHashMap 保证多线程并发安全
    private final Map<String, ChatMemory> sessions = new ConcurrentHashMap<>();

    public ChatMemory getOrCreateSession(String sessionId) {
        // computeIfAbsent:如果 sessionId 不存在,创建新的独立 Memory
        return sessions.computeIfAbsent(sessionId,
            id -> new InMemoryChatMemory());
    }

    // 定期清理:避免不活跃会话无限堆积占用内存
    @Scheduled(fixedDelay = 3600_000)  // 每小时执行一次
    public void cleanInactiveSessions() {
        sessions.entrySet().removeIf(entry -> isInactive(entry.getKey()));
    }
}

生产环境升级:把 InMemoryChatMemory 替换为 RedisChatMemory(Spring AI 内置支持),对话历史持久化到 Redis,服务重启不丢失,且 Redis 的 TTL 机制天然解决了「不活跃会话清理」的问题。

🎯 面试总结 ​

会话隔离三步走:sessionId 区分(基础)→ 持久化到 Redis(生产要求)→ TTL 清理不活跃会话(防内存泄漏)。三步说全,面试官就知道你想到了生产环境的实际问题。


十一、大模型进阶与前沿(251-260) ​


251. DeepSeek-R1 是什么?推理模型和普通模型有什么本质区别? ​

👔面试官:你了解 DeepSeek-R1 吗?推理模型(o1/R1)和普通 LLM 的本质区别是什么?

🙋‍♂️我:DeepSeek-R1 是深度求索出的一个开源模型,比较厉害,推理能力很强。

👔面试官:「比较厉害」不是技术回答。推理模型为什么在数学推理上比普通模型强得多?它多生成了什么东西?这「多生成的东西」是怎么帮助它提升推理能力的?

🙋‍♂️我:好像是会有一个思考过程?

👔面试官:思考过程是怎么来的?是人工写进去的吗?还是训练出来的?R1 的训练方式和 o1 有什么不同?GRPO 是什么?

被问住了吧。推理模型是 2025 年最重要的技术趋势之一,回答好这道题,面试官会对你印象深刻。

💡 简要回答 ​

推理模型(o1/R1)的本质是:在生成最终答案之前,先生成大量「思考过程」token。这些中间推理步骤让模型有机会发现错误、自我纠正,就像人类做题先在草稿纸上演算,而不是一步直接写答案。

DeepSeek-R1 的创新在于训练方法:用纯强化学习(GRPO),不需要大量 SFT 数据,模型自己「摸索」出了长链推理能力。

📝 详细解析 ​

为什么思考过程能提升推理能力? ​

普通 LLM 的生成是线性的:给了输入,直接生成输出。每一步的 token 只能基于已有的上下文,没有回头修改的机会。这就像让你做复杂数学题,不允许用草稿纸,脑子里一步算到底——出错了自己也察觉不到。

推理模型的做法是:在最终答案之前,先生成 <thinking> 内容,也就是显式的推理链。这段思考过程可能有几百甚至几千个 token,模型在这里可以一步步演算、发现矛盾、推翻之前的假设、重新来过。最终答案是基于这整个推理过程得出的,准确率自然大幅提升。

普通模型 vs 推理模型 ​

维度普通 LLM(GPT-4/LLaMA)推理模型(o1/R1)
回答方式直接生成答案先大量「思考」再给答案
思考过程隐式(CoT 需要在 prompt 里提示)显式(自动生成 <thinking> 内容)
延迟低高(思考过程可能消耗数千 token)
复杂推理中等强(错了会自我纠错)
适用任务通用对话、写作数学/代码/逻辑推理

DeepSeek-R1 的训练创新:GRPO ​

o1 的训练方式 OpenAI 没有公开,外界只知道用了大规模强化学习。DeepSeek-R1 则完整公开了技术方案,核心创新是 GRPO(Group Relative Policy Optimization)。

传统 RLHF 需要一个单独训练的「奖励模型」来打分,成本高且奖励信号不够精确。GRPO 的思路更直接:对同一道题,让模型生成一组答案(比如 8 个),可以自动验证的任务(数学题、代码题)直接判断对错,组内相对排名就是奖励信号:

python
def grpo_reward(question, responses):
    """组内相对奖励:不需要单独的奖励模型"""
    rewards = []
    for response in responses:
        if is_correct(response, question):
            rewards.append(+1.0)   # 正确
        elif has_valid_format(response):
            rewards.append(0.0)    # 格式对但答案错
        else:
            rewards.append(-1.0)   # 格式错误
    # 组内归一化:让奖励信号相对化,训练更稳定
    mean_reward = sum(rewards) / len(rewards)
    return [r - mean_reward for r in rewards]

不依赖 SFT 数据的优势:传统方法需要大量人工标注的「示范推理过程」来做 SFT,成本极高。GRPO 只需要能自动判断答案对错的题目(数学、代码),不需要人工标注推理过程,大规模扩展容易得多。

「顿悟时刻」(Aha Moment):DeepSeek 团队在训练中意外发现,在某个训练阶段,模型突然自发学会了一种行为:在推理中途发现错误时,会主动「停一下,让我重新想想」——这种自我反思能力完全是涌现出来的,没有人教它,是 RL 训练中自然涌现的能力,这在 AI 研究圈引起了广泛关注。

🎯 面试总结 ​

推理模型的核心逻辑:更多思考 token = 更多自我纠错机会 = 更高推理准确率,本质是用计算成本(更多 token 消耗、更高延迟)换精度。DeepSeek-R1 的技术贡献:GRPO 去掉了奖励模型,只靠答案正确性驱动强化学习,大幅降低了训练推理模型的门槛。代价也很明确:延迟高、token 消耗大,适合精度优先的场景(数学、代码),不适合追求低延迟的对话应用。


253. NAS(神经架构搜索)在大模型设计中有什么应用? ​

💡 简要回答 ​

NAS(Neural Architecture Search,神经架构搜索)在 LLM 里主要用于自动搜索超参配置,包括:MoE 的专家数量和 Top-K 激活比例、注意力头数与层数的配比、FFN 的维度缩放比例。

但 LLM 的 NAS 成本极高——每次搜索都需要一定规模的训练,不可能像 CV 里那样跑几千次搜索。实践中通常用代理任务(用小模型、小数据集做短期训练)来预测大规模训练的性能,用代理结果筛选候选架构,再对最优候选做完整训练验证。

📝 详细解析 ​

为什么 LLM 里 NAS 用得少? ​

LLM 的参数量大、训练成本高,一次 7B 模型的完整训练就要几十万美元,根本不允许「暴力搜索」几百种架构配置。

现实中大多数 LLM 团队的做法是:参考已发表论文里验证过的架构配置(比如 LLaMA-3 的 RoPE+GQA+SwiGLU 组合),然后在少数关键超参(层数、头数、FFN 维度)上用代理任务做有限次搜索,不会做全面的 NAS。

NAS 在 LLM 里真正有价值的场景是 MoE 架构设计:专家数量、每次激活的 Top-K 数、不同层的专家分配,这些超参对性能影响显著,且相对适合用代理任务预搜索。

🎯 面试总结 ​

NAS 在 LLM 里是「听起来很美但用得很克制」的技术。核心原因是成本太高,实践中更多靠经验配置 + 代理任务局部搜索,而不是全面的架构搜索。


254. 稀疏化训练和 MoE 有什么区别? ​

💡 简要回答 ​

两者都涉及「稀疏」,但稀疏发生的位置完全不同:

  • MoE(混合专家):架构上稀疏,前向传播时只激活 Top-K 个专家(其他专家不参与计算),是推理时的计算稀疏
  • 稀疏化训练(Sparse Training):训练上稀疏,反向传播时让大多数参数的梯度为零(大多数权重不更新),是训练时的梯度稀疏

📝 详细解析 ​

MoE 的稀疏性(架构稀疏):
  前向传播:输入 token → Router → 只选 Top-2 专家计算 → 输出
  作用:减少每次推理的计算量(激活参数只占总参数的 1/N)
  代表:Mixtral-8x7B(总参数 47B,激活参数约 13B)

稀疏化训练(训练稀疏):
  反向传播:计算梯度 → 只更新梯度最大的 K% 参数
  作用:减少训练时的通信和计算开销
  代表:SparseGPT(剪枝时用到类似思想)

两者不矛盾,可以结合:用稀疏训练方法来训练一个 MoE 模型

一句话记住区别:MoE 是「用哪些专家」的稀疏(推理时),稀疏化训练是「更新哪些权重」的稀疏(训练时)。

🎯 面试总结 ​

面试被问到稀疏相关问题,先确认考官问的是哪种稀疏,然后分别解释 MoE 的计算稀疏(前向)和稀疏训练的梯度稀疏(反向),说清楚两者的区别和可以结合使用,这道题就答全了。


255. DyT(Dynamic Tanh)是什么?为什么可以替代 LayerNorm? ​

💡 简要回答 ​

DyT(Dynamic Tanh,Zhu et al., 2025)是一种用可学习的 Tanh 函数替换 LayerNorm 的方法。核心洞察是:LayerNorm 的功能本质上是把激活值「压缩到合理范围」防止梯度爆炸,而 Tanh 是有界函数(输出范围 -1~1),天然有这个效果——还不需要计算均值和方差。

📝 详细解析 ​

LayerNorm 的瓶颈 ​

LayerNorm 的计算步骤是:计算当前 token 的激活均值 → 计算方差 → 用均值和方差归一化 → 乘以可学习的 gamma/beta。

其中均值和方差的计算是需要遍历整个向量的,这在 GPU 上是串行约束,对超长序列(比如 128K token 的输入)会成为明显的计算瓶颈。

DyT 的设计 ​

python
class DyT(nn.Module):
    def __init__(self, num_features):
        super().__init__()
        self.alpha = nn.Parameter(torch.ones(1))        # 可学习的输入缩放系数
        self.gamma = nn.Parameter(torch.ones(num_features))
        self.beta = nn.Parameter(torch.zeros(num_features))

    def forward(self, x):
        # tanh 把激活值压缩到 (-1, 1) 范围内,防止过大激活导致训练不稳定
        x = torch.tanh(self.alpha * x)
        return self.gamma * x + self.beta  # 和 LayerNorm 一样保留仿射变换

alpha 是可学习的,让模型自己决定「压缩力度」,不同层可以有不同的缩放行为。

实验结果:DyT 与 LayerNorm 在大多数任务上效果相当,但在超长序列训练上更稳定,且训练速度有一定提升(避免了统计量计算的串行约束)。

🎯 面试总结 ​

DyT 的价值点:用 element-wise 的 Tanh 操作替代 LayerNorm 的均值/方差统计,去掉了串行计算约束,对长序列训练更友好。目前还是研究阶段,但代表了「用更简单的操作替代归一化」的方向,面试时说出「去掉统计量计算的串行约束」这个点,面试官会知道你理解了它的工程价值。


258. 如何让大模型支持更长的上下文? ​

👔面试官:模型训练时上下文是 4K,但我们场景需要处理 128K 的长文档,怎么办?

🙋‍♂️我:换一个支持 128K 上下文的模型?

👔面试官:如果只有这个 4K 的模型,不换模型,怎么让它处理长文本?而且就算有 128K 的模型,它为什么能支持这么长?位置编码的问题是怎么解决的?

长上下文是大模型工程里的经典难题,背后有好几条技术路线。

💡 简要回答 ​

让模型支持更长上下文有四种技术路线,各自针对不同的问题:

技术解决的问题代表实现
RoPE 外推(YaRN)位置编码超出训练长度后性能下降Qwen2-72B 32K → 128K
Ring Attention单卡显存装不下超长序列支持 1M+ token
Sparse Attention全注意力 O(n²) 复杂度太高Longformer/BigBird
压缩记忆历史 token 太多但可以摘要MemGPT

📝 详细解析 ​

为什么原生模型处理不了长上下文? ​

最根本的问题是位置编码的外推性。RoPE 给每个 token 位置赋一个旋转角度,训练时见过的位置是 0~4095(4K),推理时遇到第 100000 个 token,这个位置的旋转角度模型从没见过,行为不可预测,性能急剧下降。

YaRN(Yet another RoPE extensioN):核心洞察是 RoPE 的不同频率维度对长度外推的敏感度不同。低频维度(对应长程依赖)用插值(把位置 0~128K 压缩映射到训练时的 0~32K 范围);高频维度(对应局部依赖)保持原始频率不动。分开处理比统一缩放效果好很多,Qwen2-72B 就是用这个方法从 32K 扩展到 128K 的。

Ring Attention:突破显存限制 ​

即使位置编码解决了,128K token 的 KV Cache 也会占满单卡显存(128K × 8192 维度 × 2(KV)× BF16 ≈ 32GB,一张卡已经顶到上限了)。

Ring Attention 的思路:把超长序列切分到多张卡,每张卡只存一段序列的 KV Cache,多卡之间用 Ring AllReduce 的方式互相传递注意力计算结果,组合出完整的注意力。这样理论上可以把序列长度扩展到 1M+ token,只需要加更多卡。

Sparse Attention:降低计算复杂度 ​

标准注意力的计算复杂度是 O(n²),序列长度翻倍,计算量翻 4 倍。128K token 的全注意力计算量是天文数字。

Sparse Attention 的思路:不是每个 token 都和所有其他 token 做注意力,而是用「局部窗口 + 全局 token」的稀疏模式。每个 token 只和附近的 token 做局部注意力(捕捉局部依赖),再加上少数「全局 token」负责长程信息聚合。Longformer 和 BigBird 是经典实现,把复杂度从 O(n²) 降到 O(n)。

🎯 面试总结 ​

长上下文技术路线面试的加分点:把问题拆成两个层面——位置编码能不能外推(YaRN 解决)和显存/计算能不能装下(Ring Attention / Sparse Attention 解决)。能说出 YaRN 「低频插值、高频不动」的核心洞察,面试官会认为你真的理解了而不是背的结论。


257. 主流开源模型体系有哪些?如何对比和选型? ​

👔面试官:现在开源大模型这么多,你怎么根据业务场景选模型?

🙋‍♂️我:用 LLaMA 就行了,开源生态最好,社区支持多。

👔面试官:中文场景你也用 LLaMA?LLaMA 中文语料比例很低,处理中文远不如 Qwen。你怎么在成本、能力、中文支持、部署资源之间权衡?

模型选型不是「哪个热门用哪个」,要对齐业务场景。

💡 简要回答 ​

系列机构特点
LLaMA-3Meta开源生态最广,70B 英文能力达到 GPT-4 水平
Qwen-2.5阿里中文最强开源系列,128K 长上下文,指令遵循优秀
DeepSeek-V3深度求索MoE 架构,671B 总参数但激活仅 37B,API 成本极低
Mixtral-8x7BMistral AI欧洲最强开源,MoE 架构,推理效率高
GLM-4智谱 AI国内最早商用,工具调用能力强

📝 详细解析 ​

按场景选型 ​

中文场景:首选 Qwen 系列(Qwen2.5-7B/14B/72B),阿里在中文预训练语料上投入大,中文理解和生成远超同参数量的 LLaMA。

代码 / 数学推理:DeepSeek-Coder-V2 或 DeepSeek-R1,前者代码能力顶尖,后者数学推理首屈一指。

资源受限的本地部署:Qwen2.5-7B(中文场景)或 LLaMA-3-8B(英文场景),8GB 显存的消费级 GPU 量化后即可运行。

云端高性价比:DeepSeek-V3 API,相比 GPT-4 便宜 10~20 倍,能力接近。

对比评估维度 ​

维度评估方式
中文能力C-Eval / CMMLU 得分
代码能力HumanEval / MBPP 得分
推理能力GSM8K / MATH 得分
推理效率激活参数量 / 总参数量(MoE 场景)
部署成本API 价格 / 自部署所需最低显存

DeepSeek-V3 的 MoE 成本优势 ​

DeepSeek-V3 有 671B 总参数,但每次推理只激活约 37B(MoE Top-K 路由),实际计算量相当于一个 37B 的 Dense 模型。这是它 API 价格能如此低廉的根本原因——相同 GPU 算力,MoE 能处理更多请求。

🎯 面试总结 ​

模型选型核心框架:中文 → Qwen、代码/数学 → DeepSeek、本地轻量 → 7B 量化、高性价比云端 → DeepSeek-V3 API。能说出 DeepSeek-V3 为什么便宜(MoE 激活参数少),以及 LLaMA 中文能力不如 Qwen 的原因(训练语料差异),面试官就知道你真的做过模型评估,不是纸上谈兵。


十二、大模型面经真题(大厂实录)(261-280) ​


261. 预训练数据如何收集和清洗?大规模爬虫怎么选型? ​

👔面试官:你来设计预训练数据的收集和清洗方案,怎么做?

🙋‍♂️我:写个爬虫爬网页,然后把文本提取出来就行了。

👔面试官:从零开始爬互联网,需要几个月甚至几年。业界是怎么做的?爬下来的数据有大量重复、垃圾、低质量内容,用 pandas 能处理 TB 级数据吗?

🙋‍♂️我:那就用现成的数据集?比如……Wikipedia?

👔面试官:Wikipedia 高质量,但才几十 GB,训练 LLM 需要的是 TB 级。数据从哪来,怎么清洗,你有没有系统考虑过?

💡 简要回答 ​

预训练数据的工程路线是:直接使用 Common Crawl 等公开数据集(而不是从头爬)+ 用 PySpark 做大规模分布式清洗(而不是 pandas)。清洗步骤:语言过滤 → 长度过滤 → 去重 → 垃圾内容过滤 → 质量评分。

📝 详细解析 ​

数据来源:不要从零爬 ​

业界标准做法是直接用 Common Crawl——一个持续运行十几年的互联网爬虫项目,每月爬取几十 TB 的网页数据并公开发布。LLaMA、Qwen、DeepSeek 都用了它作为主要数据来源,直接下载比自己爬划算 100 倍。

高质量补充来源:GitHub 代码(代码能力)、ArXiv 论文(科学推理)、Wikipedia(事实性知识)、Books3(长文写作)。按质量加权混合。

大规模清洗:必须用分布式框架 ​

TB 级数据单机内存放不下,必须用 PySpark(或 Ray Data)分布式处理:

python
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, length

spark = SparkSession.builder.master("spark://master:7077").appName("DataCleaning").getOrCreate()

df = spark.read.parquet("s3://common-crawl/...")

df_cleaned = (df
    .filter(col("language") == "zh")                              # 语言过滤
    .filter(length(col("text")) > 100)                            # 最短长度过滤
    .dropDuplicates(["url"])                                      # URL 级去重
    .filter(~col("text").rlike("(广告|buy now|click here)"))       # 垃圾内容过滤
    .withColumn("quality_score", quality_model_udf(col("text")))  # 质量模型评分
    .filter(col("quality_score") > 0.7)                           # 低质量过滤
)

爆内存的应对:用 repartition() 控制每个 executor 处理的数据量;中间结果用 persist(StorageLevel.DISK_ONLY) 落盘而不是占用内存。

语义去重(比 URL 去重更彻底):用 MinHash LSH 对文档做模糊去重,把语义相似的文档(哪怕 URL 不同)也过滤掉,这步能减少 30~50% 的冗余数据,对训练效果提升很明显。

🎯 面试总结 ​

预训练数据两个关键认知:数据来源用 Common Crawl(不从头爬)、清洗用 PySpark 分布式(不用 pandas)。能说出 MinHash LSH 语义去重这个点,说明你理解了数据质量的深层问题,超出大多数候选人的回答。


263. 合成数据对模型能力有什么影响?有什么风险? ​

💡 简要回答 ​

合成数据(用强模型生成训练数据)是提升特定能力的有效手段,但有「错误放大」和「多样性退化」两大风险。最佳实践是作为补充(占 10~30%),而非完全替代真实数据。

📝 详细解析 ​

类型效果风险
数学推理合成数据(MetaMath/Numina)显著提升数学能力合成数据有系统性错误会被放大学习
代码合成数据显著提升代码能力错误代码会让模型学坏
对话合成数据(Self-Instruct)适量有效过多导致多样性下降,模型「说套话」
知识蒸馏数据(从 GPT-4 获取)有效,但受使用限制可能传递强模型的幻觉和偏见

核心风险:合成数据的质量上限是生成它的模型,生成模型本身的错误会被「放大再训练」,导致能力衰减。这就是 Model Collapse 现象:模型持续用自己生成的数据训练,质量越来越差。

解决方案:严格的质量过滤(人工抽样验证合成数据质量)+ 保持真实数据为主体。

🎯 面试总结 ​

合成数据的核心认知:有效但有风险。提升特定能力很好用(数学、代码),完全替代真实数据会导致 Model Collapse。面试时说出「Model Collapse」和「错误放大」两个风险点,面试官会认为你了解这个领域的坑。


266. Qwen2-72B 的上下文长度是多少?YaRN 是怎么扩展的? ​

👔面试官:Qwen2-72B 原生支持多长的上下文?为什么 YaRN 之后能扩展到 128K?

🙋‍♂️我:Qwen2-72B 支持 128K 上下文。

👔面试官:你说的是 YaRN 扩展后的。原生训练时的 seq_len 是多少?YaRN 究竟改了什么东西才让原来 32K 的模型能处理 128K 的输入?

细节题,说对了就是加分项。

💡 简要回答 ​

  • Qwen2-72B 原生 seq_len:32,768 tokens(32K)
  • YaRN 扩展后:支持 128K tokens(扩展 4 倍)

YaRN 的核心操作是修改 RoPE 的旋转频率:低频维度(对应长程依赖)用插值缩放(把 128K 的位置压缩映射到 32K 的训练范围内),高频维度(对应局部依赖)保持原始频率不动。分开处理比统一缩放效果好得多。

📝 详细解析 ​

为什么要分频率维度处理? ​

RoPE 有 d/2 个频率维度(d 是 embedding 维度),每个维度对应不同频率的位置编码。低频维度的旋转角度变化缓慢,对应的是句子/段落级的长程依赖,对位置超出训练范围很敏感——超出了就崩;高频维度的旋转角度变化很快,对应的是词级别的局部关系,本身外推性就好,不需要干预。

YaRN 的洞察:对低频维度做线性插值(让模型「误以为」位置 128K 其实是位置 32K),对高频维度直接保持原样。两者分别处理,比统一缩放(naive RoPE scaling)的效果好很多,用很少的额外微调(几百步)就能激活长上下文能力。

实际用的公式是:在两种极端(纯插值 / 完全不变)之间按频率做平滑过渡,不同频率的维度使用不同比例的插值量。

🎯 面试总结 ​

Qwen2-72B 原生 32K,YaRN 扩展到 128K。YaRN 的精髓:低频插值、高频不动,分频率维度差异化处理,而不是对所有维度统一缩放。这道题说出「低频维度对长度外推敏感,高频维度外推性好」,就比背结论更深一层了。


268. Transformer 中的 Attention:为什么用 softmax?为什么用点积而不是余弦? ​

👔面试官:Attention 计算里为什么用 softmax 归一化?为什么用点积而不是余弦相似度?

🙋‍♂️我:softmax 把分数变成概率,点积计算简单。

👔面试官:sigmoid 也能把分数变成概率,为什么不用 sigmoid?点积计算简单是实现细节,背后的数学原因是什么?还有为什么要除以 √d_k?不除会怎样?

回答停留在「概率」「简单」这种层面,说明没理解 Attention 的数学设计。

💡 简要回答 ​

  • 为什么 softmax 不用 sigmoid:softmax 强制所有 key 的权重总和为 1,天然表达「注意力竞争」(A 多一点,B 就少一点)。sigmoid 独立计算每个 key,总和不为 1,无法表达互斥竞争关系
  • 为什么点积不用余弦:余弦需要额外计算向量模长(多两次 norm 操作),而 Q/K 在训练中会自适应调整模长,加上 √d_k 缩放后效果已经接近余弦,没必要多这个开销
  • 为什么除以 √d_k:防止高维点积方差过大,让 softmax 工作在有效梯度区间

📝 详细解析 ​

softmax vs sigmoid:注意力竞争 ​

python
scores = [2.0, 1.0, 0.1]  # 3 个 key 的原始分数

# sigmoid:各自独立归一化,总和不为 1
sigmoid_weights = [0.88, 0.73, 0.52]  # 0.88 + 0.73 + 0.52 = 2.13,不是概率分布

# softmax:总和严格为 1,天然表达竞争关系
softmax_weights = [0.65, 0.24, 0.11]  # 0.65 + 0.24 + 0.11 = 1.00

直觉理解:注意力是一种「资源分配」——把 100% 的注意力分配给不同的 key,softmax 保证「给 A 多一点就要给 B 少一点」,这种互斥竞争才是注意力的本质。sigmoid 给了 A 88% 再给 B 73%,总量超过 100%,语义上不对。

为什么要除以 √d_k? ​

假设 Q、K 的每个维度服从均值为 0、方差为 1 的分布,那么点积 Q·K = Σ(q_i × k_i) 的方差是 d_k(维度数)。d_k = 64 时,点积的标准差是 8,分数差异很大,softmax 会进入饱和区:

python
# 不除 √d_k:高维时点积过大,softmax 饱和
scores_large = [100, 10, 1]    # 方差很大
softmax_large = [≈1.0, ≈0, ≈0]  # 近似 one-hot,梯度接近 0

# 除以 √64 = 8:点积缩放到合理范围
scores_scaled = [12.5, 1.25, 0.125]
softmax_scaled = [0.65, 0.24, 0.11]   # 梯度正常,训练稳定

softmax 饱和就意味着梯度接近 0,参数无法更新,训练卡死。除以 √d_k 是把方差从 d_k 归一化回 1,让 softmax 始终在有效的梯度区间工作。

🎯 面试总结 ​

Attention 设计三问三答:

  • softmax 而非 sigmoid:注意力是竞争分配,需要总和为 1 的概率分布
  • 点积而非余弦:Q/K 模长可由训练自调节,√d_k 缩放效果已近似余弦,不需要额外算 norm
  • 除以 √d_k:防止高维点积方差过大导致 softmax 饱和、梯度消失

说清楚「√d_k 是为了归一化方差、防止 softmax 饱和」,大多数面试官就满意了。


269. 训练大模型时如何优化显存占用? ​

👔面试官:训练一个 13B 的模型,单卡 80GB A100 都放不下,你有哪些优化显存的方法?

🙋‍♂️我:换更大的卡?

👔面试官:更大的卡是硬件方案,不是工程方案。软件层面你能做什么?

大模型训练显存优化是高频考点,系统说出五个方向才算全面。

💡 简要回答 ​

大模型训练显存优化五板斧:混合精度 → 梯度检查点 → ZeRO-3 分片 → LoRA/QLoRA → Batch + 梯度累积。五个方向合用,可以把 70B 模型压缩到消费级 GPU 上微调。

📝 详细解析 ​

1. 混合精度训练(BF16):把参数和激活值从 FP32 改成 BF16,每个参数占用减半(4 字节 → 2 字节)。BF16 比 FP16 数值范围更大(更接近 FP32),训练更稳定,是目前的标配。

2. 梯度检查点(Gradient Checkpointing):正常前向传播会保存所有中间激活值用于反向传播,这部分显存占用跟序列长度成正比。梯度检查点的思路是:前向时只保存少数关键节点的激活值,反向时用到哪层就重新算一遍。用约 30% 的额外计算量换取激活值显存从 O(n) 降到 O(√n)。

3. ZeRO-3(Zero Redundancy Optimizer):Adam 优化器除了参数本身,还要存梯度、一阶矩、二阶矩,总显存是参数量的 16 倍(FP32)。ZeRO-3 把参数、梯度、优化器状态三者都按卡数 N 切片存储,每张卡只存 1/N,需要时通过 AllGather 拉取其他卡的分片。可以把显存需求降低到 1/N,代价是通信量增加。

4. LoRA / QLoRA(参数高效微调):不训练全量参数,只给每个注意力矩阵加两个低秩矩阵(r=8 时新增参数仅为原来的 0.1%)。QLoRA 进一步把基础模型量化到 4-bit 存储(每个参数只占 0.5 字节),只有 LoRA 的新增参数保持 BF16 精度参与训练。用 QLoRA 可以在单张 24GB 显存的 GPU 上微调 65B 的模型。

5. 减小 Batch Size + 梯度累积:batch size 越大,激活值越多,显存越高。减小 batch size 降低显存,用梯度累积(跑 K 个小 batch 再更新一次参数)保持等效的大 batch 训练效果,不影响收敛质量。

🎯 面试总结 ​

显存优化五板斧,从省到多:混合精度(参数减半)→ 梯度检查点(激活值减少)→ ZeRO-3(优化器状态分片)→ LoRA/QLoRA(只训练少量参数)→ 小 batch + 梯度累积。面试时能说出每个方案省的是哪部分显存(参数/激活/优化器状态),说明你真的理解了显存的构成。


276. KV Cache 的加速体现在哪里? ​

👔面试官:KV Cache 是怎么加速推理的?

🙋‍♂️我:把 KV 值缓存起来,不用重复计算。

👔面试官:具体省掉了什么计算?没有 KV Cache 时每步要做什么,有了之后每步只做什么?这个加速比是多少?

💡 简要回答 ​

LLM 自回归生成时,每生成一个新 token,都需要计算它与所有历史 token 的注意力。没有 KV Cache 时,每步都要重新计算所有历史 token 的 K、V 向量(O(n²) 总计算量);有了 KV Cache 后,历史 token 的 K、V 已缓存,每步只需计算新 token 的 Q、K、V 并追加到缓存,计算量降到 O(n)。

📝 详细解析 ​

自回归生成的本质:输入前 n 个 token,预测第 n+1 个;再用前 n+1 个预测第 n+2……每步都要做一次注意力计算。

没有 KV Cache:第 n 步要计算所有 n 个 token 的 K、V 并做完整注意力,第 n+1 步再算 n+1 个……总计算量是 O(1 + 2 + … + n) = O(n²)。

有了 KV Cache:第一步正常算,把 n 个 token 的 K、V 全部存下来。后续每步只需计算新 token 的 Q、K、V,然后把新 token 的 K、V 追加到缓存,再做一次新 Q 与全部 K 的注意力(只有一个新 Q)。每步计算量是 O(n),总计算量降到 O(n²) → O(n × n_gen),其中 n_gen 是生成的 token 数,通常远小于 n。

代价:KV Cache 占显存。每层每个 token 的 KV 缓存大小 = 2 × num_heads × head_dim × 2字节(BF16)。128K 上下文的 72B 模型,KV Cache 可能占 30~60GB 显存,这也是推理服务显存紧张的主要原因。

🎯 面试总结 ​

KV Cache 的加速本质:把历史 token 的 K、V 重复计算变成一次性计算,每步只计算新 token。代价是显存换时间,显存占用与上下文长度成正比。说出「O(n²) → O(n)」和「代价是显存」,面试官就知道你理解了 KV Cache 的权衡。


277. 大模型分布式训练的并行策略和矩阵切分怎么理解? ​

💡 简要回答 ​

大模型分布式训练有三种主要并行策略,解决不同的问题:

  • 数据并行(DP):每卡一份完整模型,按 batch 切分数据,梯度 AllReduce 同步。解决数据吞吐量问题,不解决单卡装不下的问题
  • 张量并行(TP):把单个矩阵按行/列切到多卡,多卡协作完成一次矩阵乘法。解决单层参数太大装不下的问题
  • 流水线并行(PP):把模型的层切到不同卡,数据流水线式通过。解决层数太多装不下的问题

📝 详细解析 ​

张量并行的矩阵切分 ​

Megatron-LM 对 Transformer 的 MHA 和 FFN 做了专门的切分,以 FFN(两个矩阵 W1、W2)为例:

  • 列切分 W1:把 W1 按列切成 N 份,每张卡算自己的那份,不需要通信
  • 行切分 W2:W2 按行切成 N 份,每张卡算完之后需要一次 AllReduce 汇总

整个 FFN 只需要一次 AllReduce,通信量最小化。MHA 同理,Q/K/V 投影矩阵列切,输出投影矩阵行切。

流水线并行的气泡问题 ​

流水线并行把第 1~4 层放卡 0,第 5~8 层放卡 1。数据流水线式流动,但存在流水线「气泡」(卡 0 处理完传给卡 1 后,卡 0 要等下一个 batch,期间 GPU 闲置)。解决方案是 micro-batch:把一个大 batch 切成多个 micro-batch,让流水线保持饱满,减少气泡比例。

三种并行组合使用(3D 并行),典型配置:TP=4,PP=2,DP=4(共 32 张卡)。

🎯 面试总结 ​

三种并行策略各解决不同维度的问题:DP 解决数据吞吐、TP 解决单层过大、PP 解决层数过多。矩阵切分的关键认知:列切不需要通信,行切需要 AllReduce,合理安排减少通信次数是 Megatron 的核心工程优化。


278. 请完整讲解一遍 Transformer 架构。 ​

💡 简要回答 ​

Transformer 解码器(LLM 架构)的完整数据流:

输入 → Token Embedding + RoPE 位置编码 → N 层 Transformer Block → RMSNorm → LM Head → 输出词表概率

每个 Transformer Block 内部:

x → Pre-RMSNorm → Multi-Head Attention(GQA)→ 残差连接
  → Pre-RMSNorm → Feed-Forward Network(SwiGLU)→ 残差连接

📝 详细解析 ​

每个组件为什么这么设计 ​

RoPE(旋转位置编码):把位置信息编码进 Q、K 的旋转角度,而不是加在 embedding 上。优点是相对位置关系更自然,且外推性好(可以用 YaRN 等方法扩展到更长的上下文)。

GQA(分组查询注意力):标准多头注意力(MHA)的每个头都有独立的 Q、K、V,GQA 是多个 Q 头共享一组 K、V 头。比 MHA 省 KV Cache 显存,比 MQA(所有 Q 共享一组 KV)质量好,是目前 LLM 的标配。

Pre-RMSNorm:归一化放在注意力和 FFN 之前(Pre-Norm),比放在后面(Post-Norm)训练更稳定,不容易梯度爆炸。用 RMSNorm 而非 LayerNorm 是因为 RMSNorm 只算方差不算均值,计算量更小,效果相当。

SwiGLU:FFN 的激活函数改用 SwiGLU(Swish + 门控机制),比 ReLU 表达能力更强。代价是 FFN 变成三个矩阵(W1、W2、W3),计算量略高,但效果提升明显,LLaMA/Qwen/DeepSeek 都在用。

残差连接:每个子层(注意力、FFN)都有残差,保证梯度在深层网络中顺畅流动,是 Transformer 能训练几百层的关键。

🎯 面试总结 ​

Transformer 架构面试的完整回答思路:先说数据流(Embedding → N 层 Block → LM Head),再说每个组件的设计选择(RoPE/GQA/Pre-RMSNorm/SwiGLU/残差)和为什么这么选。能说出「GQA 是 MHA 和 MQA 的折中」「Pre-Norm 比 Post-Norm 训练稳定」,面试官就知道你真的理解了现代 LLM 的架构,而不是背了一个旧版 Transformer。


280. 没有强化学习经验,如何快速入手 AI 智能体开发? ​

👔面试官:你说你对 AI 智能体感兴趣,但没有 RL 经验,你打算怎么做智能体?

🙋‍♂️我:我会先学 Q-learning,然后学 Actor-Critic,再学 PPO……

👔面试官:你知道现在 LLM 智能体主要用 Prompt Engineering + Tool Calling 实现,不需要经典 RL 吗?你绕了一个大弯。

大多数人以为做 AI Agent 一定要懂强化学习,其实错了。

💡 简要回答 ​

现代 LLM Agent(比如 AutoGPT、OpenManus、各类 Copilot)主要靠 Prompt Engineering + Tool Calling 实现,核心是 ReAct 循环(思考→行动→观察→再思考),不需要经典 RL(Q-learning/Actor-Critic)的知识。没有 RL 经验从这里入手,两周就能跑起来一个有用的 Agent。

📝 详细解析 ​

从 LLM Agent 框架入手(第一周) ​

直接用 LangGraph 或 Spring AI 的 Agent 支持,这些框架已经封装好了 ReAct 循环。你只需要:

  1. 定义 Tools(用 @Tool 注解或 @tool 装饰器描述工具功能)
  2. 给 Agent 设置 System Prompt(说明它的角色和任务)
  3. 运行循环(框架自动处理:LLM 思考 → 调用工具 → 把结果给 LLM → 继续思考)

先把一个可以上网搜索、可以读写文件、可以执行代码的 Agent 跑起来,这是最好的学习路径。

真的需要 RL 时从哪里学 ​

如果岗位确实需要 RL(比如训练推理模型、做 RLHF 对齐),学习路径:PPO 基础概念(策略梯度、Actor-Critic、优势函数)→ RLHF 的具体实现(奖励模型 + PPO 微调 LLM)→ DPO 作为简化替代(DPO 直接用人类偏好数据优化,不需要单独的奖励模型,实现简单得多)。

现有学习资源:OpenManus(GitHub 开源,完整的多 Agent 实现)的源码是最好的实战教材。

🎯 面试总结 ​

没有 RL 经验做 Agent,核心认知:现代 LLM Agent 靠 Prompt + Tool Calling,不靠经典 RL。面试时说出这个认知,再说出学习路径(LangGraph 上手 → ReAct 循环 → 需要的话再补 DPO/PPO),面试官会认为你务实、能快速落地,而不是在纸上搭积木。


十三、AI 应用开发项目高频题(281-300) ​


282. Spring AI 是什么?有哪些核心特性? ​

👔面试官:你们 Java 技术栈的 AI 项目用什么框架?Spring AI 了解吗?

🙋‍♂️我:就是用 Spring Boot 调 OpenAI 的 SDK 呗。

👔面试官:直接调 SDK 有什么问题?如果要从 OpenAI 切换到国内的模型,业务代码要改多少地方?Spring AI 解决的核心问题是什么?

直接调 SDK 没毛病,但业务代码和具体的 LLM 提供商强耦合,换个模型就得大改。Spring AI 的价值是统一抽象。

💡 简要回答 ​

Spring AI 是 Spring 生态的 LLM 应用框架,核心价值是统一不同 LLM 提供商的接口,换模型只改配置,业务代码不动。同时提供了开箱即用的 Tool Calling、RAG、多轮对话记忆、结构化输出等能力。

📝 详细解析 ​

核心价值:接口统一,换模型无感 ​

java
// 统一接口:只改这一行配置就能切换模型
@Bean
public ChatModel chatModel() {
    return new OpenAiChatModel(new OpenAiApi(apiKey));   // OpenAI
    // return new AnthropicChatModel(...);                // Anthropic
    // return new ZhiPuAiChatModel(...);                  // 智谱 AI
}

// 业务代码完全不需要动
ChatClient chatClient = ChatClient.builder(chatModel).build();
String answer = chatClient.prompt().user("你好").call().content();

五大核心特性 ​

Tool Calling:用 @Tool 注解声明工具,Spring AI 自动处理工具调用的完整流程(生成 JSON Schema、执行工具、把结果回传 LLM)。

RAG 集成:内置 DocumentReader(支持 PDF/Word/HTML 解析)、TextSplitter(自动切块)、VectorStore(对接 Milvus/Chroma/PGVector)、QuestionAnswerAdvisor(自动检索注入)。

Advisor 机制:类似 Spring AOP 的拦截器链,可以在 LLM 调用前后注入逻辑。MessageChatMemoryAdvisor 自动管理对话历史,SafeGuardAdvisor 自动做安全过滤。

结构化输出:BeanOutputConverter 自动生成 JSON Schema 提示词,自动把 LLM 返回的 JSON 反序列化成 Java 对象。

多模态:统一支持图片、音频等多模态输入(需要底层模型支持)。

🎯 面试总结 ​

Spring AI 的核心价值三句话:统一接口(换模型不改代码)、内置 RAG 全链路、Advisor 拦截器链(内存/安全/日志无侵入注入)。Java 技术栈做 AI 项目,Spring AI 是目前最成熟的选择。


285. 如何实现 AI 多轮对话?如何解决对话记忆的持久化问题? ​

👔面试官:你的 AI 对话系统怎么实现多轮记忆的?用户说完「我叫张三」,下一轮问「我叫什么」,AI 能回答吗?

🙋‍♂️我:每次把聊天历史拼到 Prompt 里就行了。

👔面试官:历史存在哪?服务器内存里?重启了用户历史不就没了?有没有上下文长度限制?历史太长了怎么办?

多轮对话不只是「拼 Prompt」,还有状态持久化和上下文窗口管理两个工程问题。

💡 简要回答 ​

多轮对话的实现原理:每次请求时把历史对话消息列表注入 Prompt,让 LLM 感知上下文。Spring AI 的 MessageChatMemoryAdvisor 自动管理这个过程,配合 RedisChatMemory 实现历史持久化,避免重启丢失。

📝 详细解析 ​

java
@Bean
public ChatMemory chatMemory(RedisTemplate<String, Object> redisTemplate) {
    return new RedisChatMemory(redisTemplate);  // 持久化到 Redis
}

@PostMapping("/chat")
public String chat(@RequestParam String sessionId, @RequestParam String message) {
    return chatClient.prompt()
        .user(message)
        .advisors(a -> a
            .param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId)
            .param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))  // 只取最近10轮
        .call()
        .content();
    // Advisor 自动:
    // 1. 从 Redis 加载该 sessionId 的历史(最近10轮)
    // 2. 将历史注入 Prompt(作为 messages 列表)
    // 3. 将本轮新对话追加存入 Redis
}

为什么只取最近10轮:LLM 有上下文窗口限制,历史越长消耗 token 越多(成本升高、TTFT 变慢)。取最近 N 轮是平衡记忆效果和成本的常用策略。对于重要的长期信息(用户偏好、关键事实),可以提取出来单独存到用户 Profile,而不是放在对话历史里。

为什么用 Redis 而不是内存:服务有多实例(水平扩展),同一用户的不同请求可能打到不同实例,必须用共享存储。Redis 还支持 TTL,不活跃会话自动过期清理,不需要额外的清理逻辑。

🎯 面试总结 ​

多轮对话三个工程问题:历史注入(每次请求把 messages 列表带上)→ 持久化(Redis 存历史,不用内存)→ 窗口管理(只取最近 N 轮,控 token 消耗)。面试说出这三点,说明你真的工程实践过,而不只是会调 API。


286. Spring AI 的结构化输出如何实现? ​

💡 简要回答 ​

Spring AI 的 BeanOutputConverter 自动将 Java 类生成 JSON Schema 提示词注入 Prompt,并将 LLM 返回的 JSON 自动反序列化成 Java 对象,业务代码无需手动解析字符串。

📝 详细解析 ​

java
// 定义目标数据结构
record PersonInfo(String name, int age, List<String> skills) {}

BeanOutputConverter<PersonInfo> converter = new BeanOutputConverter<>(PersonInfo.class);
String formatInstructions = converter.getFormat();  // 自动生成 JSON Schema 说明

String response = chatClient.prompt()
    .user("从以下文本提取人物信息:张三,25岁,擅长 Java 和 Python\n" + formatInstructions)
    .call()
    .content();

PersonInfo person = converter.convert(response);
// person.name() == "张三"
// person.age()  == 25
// person.skills() == ["Java", "Python"]

为什么不直接让 LLM 输出 JSON:不加约束的 LLM 可能在 JSON 外面加 markdown 代码块(```json ... ```)或者加额外的解释文字,导致 JSON.parse 失败。BeanOutputConverter 生成的 formatInstructions 里明确告知 LLM「只输出纯 JSON,不要其他内容」,大幅提高解析成功率。

🎯 面试总结 ​

结构化输出的核心价值:告别字符串解析,LLM 输出直接映射成 Java 对象。关键点是 getFormat() 把 JSON Schema 注入 Prompt,约束 LLM 输出格式;convert() 做反序列化并处理边界情况。面试时说出「不加约束 LLM 可能加 Markdown 包裹导致解析失败」,说明你踩过这个坑。


287. Spring AI 中如何构建 RAG 知识库系统? ​

💡 简要回答 ​

Spring AI 提供了完整的 RAG 工具链:文档解析(TikaDocumentReader)→ 切块(TokenTextSplitter)→ 元信息增强(KeywordMetadataEnricher)→ 向量化存储(VectorStore)→ 检索注入(QuestionAnswerAdvisor)。

📝 详细解析 ​

java
// 离线:文档索引流水线
@PostMapping("/index")
public void indexDocuments(@RequestParam MultipartFile file) throws IOException {
    // 1. 解析文档(支持 PDF/Word/HTML/TXT)
    List<Document> docs = new TikaDocumentReader(file.getResource()).get();

    // 2. 切块(512 token,重叠 64 token 防止语义截断)
    List<Document> chunks = new TokenTextSplitter(512, 64).apply(docs);

    // 3. 关键词元信息增强(让模型提取关键词,方便后续过滤)
    new KeywordMetadataEnricher(chatModel, 5).apply(chunks);

    // 4. 批量向量化并存入向量库
    vectorStore.add(chunks);
}

// 在线:检索增强问答
@PostMapping("/ask")
public String ask(@RequestParam String question) {
    return chatClient.prompt()
        .user(question)
        .advisors(new QuestionAnswerAdvisor(vectorStore,
            SearchRequest.defaults().withTopK(5)))  // 检索 Top-5 相关 chunk
        .call()
        .content();
}

批量 Embedding 优化:上传大量文档时,逐条调 Embedding API 很慢(每次都有网络 RTT)。Spring AI 的 vectorStore.add(chunks) 内部自动做批处理(一次 API 调用处理多条文本),比逐条快 10~50 倍。

多领域知识库隔离:企业往往有多个部门的文档(HR/法务/产品),混在一起检索会「串台」。方案是为不同领域的文档打上 domain metadata 标签,检索时加 filter expression,只在对应领域的 chunk 里搜索:

java
SearchRequest.query("").withFilterExpression("domain == 'HR'")

🎯 面试总结 ​

Spring AI RAG 系统的关键工程点:切块参数(512 token + 64 重叠)、批量 Embedding(不要逐条调 API)、metadata 过滤(多领域知识库隔离)。说出 TokenTextSplitter 的参数含义,说明你真的用过而不是只看过文档。


291. 如何用 Spring AI 实现联网搜索工具? ​

💡 简要回答 ​

用 @Tool 注解声明联网搜索工具,Spring AI 自动处理工具调用的 JSON Schema 生成和结果回传。工具描述要写清楚「什么时候调用」,否则 LLM 不知道什么场景该用这个工具。

📝 详细解析 ​

java
@Component
public class WebSearchTool {

    @Tool(description = "搜索互联网获取最新信息,适用于:实时新闻、最新数据、近期发生的事件。"
                      + "不适用于:历史事实、常识性问题。")
    public String searchWeb(
        @ToolParam(description = "搜索关键词,尽量简短精准") String query
    ) {
        return tavilyClient.search(query).getResults().stream()
            .limit(3)
            .map(r -> r.getTitle() + "\n" + r.getContent())
            .collect(Collectors.joining("\n\n---\n\n"));
    }
}

工具描述是关键:LLM 依赖 description 判断何时调用工具。「搜索互联网」太模糊,要明确写「适用于实时新闻、最新数据」,并且告诉它「不适用于历史事实、常识性问题」,防止模型滥用搜索工具(每次都去搜,增加延迟和成本)。


295. 分层智能体架构是什么?怎么设计? ​

👔面试官:如果要做一个能完成复杂任务的 AI Agent,比如「帮我调研竞品并写一份分析报告」,单个 Agent 够用吗?

🙋‍♂️我:一个 Agent 给它足够多的工具,让它自己来就行了。

👔面试官:单 Agent 处理复杂任务时,上下文会变得很长(调用了几十次工具),LLM 的注意力会分散,容易在中途忘记目标或做出错误决策。这个问题你有考虑过吗?

单 Agent 处理复杂任务有上下文污染和注意力分散的问题,分层 Agent 是更好的方案。

💡 简要回答 ​

分层智能体架构的核心思路:主 Agent(Orchestrator)负责任务拆解和协调,子 Agent 各自专注一个细分职责。每个 Agent 的上下文保持干净(只看到自己职责范围内的信息),避免单 Agent 上下文过长导致的注意力分散问题。

📝 详细解析 ​

java
@Component
public class OrchestratorAgent {
    private final ResearchAgent researchAgent;  // 专门负责信息搜集
    private final WritingAgent writingAgent;    // 专门负责文本写作
    private final ReviewAgent reviewAgent;      // 专门负责质量审核

    public String executeComplexTask(String task) {
        // 1. 主 Agent 拆解任务(只做规划,不执行细节)
        TaskPlan plan = llm.decompose(task);

        // 2. 委托子 Agent 并行或串行执行
        String research = researchAgent.research(plan.getResearchTask());
        String draft    = writingAgent.write(plan.getWritingTask(), research);
        String result   = reviewAgent.review(draft);

        return result;
    }
}

为什么分层比单 Agent 好:

  • 每个子 Agent 只看到自己任务的上下文(短而干净),LLM 注意力集中
  • 子 Agent 可以并行执行(研究和写作在某些步骤可以并行)
  • 某个子 Agent 失败时,只重试那个子任务,不用从头开始
  • 子 Agent 可以有不同的 System Prompt(研究 Agent 更偏搜索,写作 Agent 更偏创作)

何时用分层 vs 单 Agent:简单任务(单轮工具调用、几步就能完成)用单 Agent 足够;复杂任务(多个阶段、需要多种专业能力、预期调用工具 10 次以上)才引入分层架构,避免过度设计。

🎯 面试总结 ​

分层 Agent 的核心价值:隔离上下文,专注职责。单 Agent 处理复杂任务时「上下文污染」是核心问题,分层是解法。面试时说出「上下文污染会导致注意力分散」这个问题根因,再说出分层的好处,面试官会认为你真的思考过 Agent 工程的痛点。


296. AI 应用为什么选择 Serverless 部署?有什么限制? ​

👔面试官:你们的 AI 应用部署在 Serverless 上,为什么不选常驻服务器?

🙋‍♂️我:因为 Serverless 便宜,按量付费。

👔面试官:便宜是表象,根本原因是什么?Serverless 有什么具体的限制,对 AI 应用影响最大的是哪个?

💡 简要回答 ​

AI 应用初期选 Serverless(阿里云函数计算 / AWS Lambda)的核心原因是流量不稳定 + 初期低流量。常驻服务器要 24 小时付钱,但 AI 应用初期可能一天只有几百次调用,Serverless 按实际调用量付费,成本差了 10~100 倍。

📝 详细解析 ​

选 Serverless 的三个理由 ​

按量付费:AI 应用初期(尤其是 2B 企业应用)流量小、不稳定,常驻服务器大量时间在空转浪费。Serverless 只在调用时收费,0 调用 0 成本。

自动扩容:某次营销活动或大用户涌入时,Serverless 自动拉起新实例,不需要提前预估容量、手动扩容。

冷启动可接受:LLM 推理本身延迟就高(几秒到几十秒),Serverless 的几百毫秒冷启动在这个背景下几乎可以忽略,不像普通 Web API 那么敏感。

Serverless 对 AI 应用的限制 ​

执行时间限制(最影响 AI 的点):大多数平台限制单次执行最长 15 分钟甚至更短。LLM 生成长文本可能超过这个时间。解决方案:用 SSE 流式输出,让函数持续「有输出」来维持连接,或者把长任务拆成异步队列。

显存限制:Serverless 通常没有 GPU,无法运行本地大模型,只能调用第三方 API(OpenAI / 阿里云 百炼 / 腾讯混元)。这也意味着 Serverless 的 AI 应用必须是「API 消费型」而非「模型自托管型」。

网络冷启动:VPC 内的 Serverless 函数可能有额外的网络初始化延迟,需要提前配好 VPC 绑定或用 HTTP 直接出公网。

🎯 面试总结 ​

Serverless 选型核心:流量小/不稳定 → Serverless,流量稳定/需要 GPU → 常驻实例。限制里最需要注意的是执行时间(用 SSE 绕过)和无 GPU(只能调 API)。说出这两个限制,面试官就知道你真的部署过 AI 应用,不只是知道「Serverless 便宜」。


300. 如何保证 AI 应用的性能和稳定性? ​

👔面试官:你的 AI 应用上线了,怎么保证它高性能、稳定,出问题了能快速发现和恢复?

🙋‍♂️我:做好监控,出问题了重启服务。

👔面试官:监控监什么?用户感觉慢的时候你的监控能发现吗?模型质量变差了,你的监控能发现吗?出问题了只能重启吗?没有更快的恢复机制?

「做好监控」是废话,说出具体的六个保障措施才是答案。

💡 简要回答 ​

AI 应用性能和稳定性保障六个维度:缓存(减少重复请求)→ 流式输出(降低感知延迟)→ 熔断降级(主模型故障自动切换)→ 限流(保护后端)→ 监控告警(TTFT/TPOT/满意度)→ 质量评估(防止模型悄悄变差)。

📝 详细解析 ​

① 缓存:两个层次。语义缓存:相似问题(余弦相似度 > 0.95)直接返回缓存答案,不调 LLM,相同问题秒答;前缀缓存(KV Cache Prefix Sharing):System Prompt 是每次请求都一样的,vLLM 和部分 LLM API 支持对相同 System Prompt 的 KV Cache 复用,减少重复计算。

② 流式输出(SSE):LLM 生成总时长可能 10~30s,但流式输出让用户看到第一个字只需 1s 以内(TTFT < 1s),主观感受「不慢」,比等待全部生成再返回体验好很多。

③ 熔断降级:主模型故障时自动切换到备用模型(见241题),不直接返回 500。宁可质量略降,服务不中断。

④ 限流(QPS + TPM):双维度限流保护后端。QPS 限制防止并发过高打垮 API Gateway;TPM(每分钟 Token 数)限制防止大 Prompt 打满 LLM Worker 的 GPU。单维度限流容易被超长 Prompt 绕过。

⑤ 监控告警:AI 服务专属指标——TTFT P99 异常升高(首字慢)、TPOT 异常升高(生成速度慢)、错误率飙升(模型/网络故障)、用户👎率升高(质量下降)。任何一个触发阈值,立即告警。

⑥ 质量评估:模型质量不会体现在错误率里(回答了但答得烂)。定期对一批标准问题做自动评估(RAGAs 框架计算相关性/忠实度等指标)+ 月度人工抽样,发现质量衰退及时干预。

🎯 面试总结(AI 项目类题目的万能框架) ​

保证 AI 应用稳定性,六个维度说全:缓存 → 流式 → 熔断 → 限流 → 监控 → 质量评估。

更重要的:面试中回答所有 AI 项目题,记住一个框架——问题背景(为什么要做)→ 技术选型(用了什么,为什么选)→ 关键挑战(遇到了什么坑)→ 解决方案(怎么解决的)→ 效果数据(提升了多少)。没有数据的回答是苍白的,面试前务必准备好自己项目的关键数字:延迟优化了多少、准确率提升了多少、成本降低了多少。


最后更新2026-04-26
觉得有帮助?把这个链接转给正在求职的朋友 · 用 Ctrl + K 全站搜索其它题