最近DeepSeek不是在大规模扩招嘛,算法岗放出来不少HC。我寻思着试试呗,就投了一份简历过去。说实话没抱太大期望,毕竟现在大模型赛道卷成什么样大家都懂。结果简历居然过了,没过两天就收到了笔试邀约。

我当时还挺高兴,想着简历面能过说明基础还凑合,笔试应该不至于太难看吧。

然后我就去了。

说实话,走出考场那一刻我整个人是懵的。

不是题目难到完全无从下手那种懵。是每一道题我都觉得自己能说两句,但真要落笔写完整,又总觉得差一口气。

四道题,两个小时。手写Multi-Head Attention完整代码、DPO训练流程推导、MOE通信开销和负载均衡分析、vLLM推理加速原理。没有选择题,没有填空题,全是硬茬子。

这篇文章不聊面试技巧,就实打实复盘一下这四道题我是怎么答的、卡在哪了、事后想明白什么。

如果你也在准备大模型相关的技术岗,这篇应该能帮你省不少时间。

题目一:手写完整的Multi-Head Attention,不能只写框架

面试官原话是"不能只写框架"。什么意思?就是你光写一个MultiHeadAttention类、里面放几个nn.Linear、forward里调一下scaled_dot_product_attention——这种程度不够。

他要看的是你对维度变换的掌控力,以及对Attention背后数学的理解。

先从单头缩放点积注意力说起,这是最核心的公式:

公式

其中

公式

公式

公式

。除以

公式

是为了防止点积过大导致softmax梯度消失。

多头注意力的本质是把

公式

维的Q、K、V投影到h个子空间,每个子空间分别算注意力,最后拼回来:

公式

公式

投影矩阵的维度分别是:

公式

公式

公式

公式

。通常设置

公式

,这样输入输出维度一致。

我当时写的大致是这个思路(事后整理版):

import torch
import torch.nn as nn
import torch.nn.functional as F

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super().__init__()
        self.d_model = d_model
        self.num_heads = num_heads
        self.head_dim = d_model // num_heads
        
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)
        
    def forward(self, query, key, value, mask=None):
        batch_size = query.size(0)
        
        # Q, K, V 投影
        Q = self.W_q(query)
        K = self.W_k(key)
        V = self.W_v(value)
        
        # 拆分成多头: (batch, seq_len, d_model) -> (batch, num_heads, seq_len, head_dim)
        Q = Q.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
        K = K.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
        V = V.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
        
        # 缩放点积注意力
        scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)
        attn_weights = F.softmax(scores, dim=-1)
        
        # 加权求和
        attn_output = torch.matmul(attn_weights, V)
        
        # 合并多头: (batch, num_heads, seq_len, head_dim) -> (batch, seq_len, d_model)
        attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
        
        return self.W_o(attn_output)

关键点在哪?

第一,viewtranspose的顺序不能搞反。你得先view把最后一维拆成(num_heads, head_dim),再transposenum_heads挪到第二维。搞反了维度全乱。

第二,mask的shape要搞清楚。mask通常是(batch, seq_len)(batch, 1, seq_len, seq_len),广播机制要心里有数。

第三,也是面试官后来追问的——为什么需要多头? 单头注意力只能学一种注意力模式,多头让模型在不同子空间里捕捉不同特征。一个头关注语法,另一个关注语义,再一个关注位置关系。这不是玄学,是实验验证过的。

我在这题上花了大概25分钟。写代码本身不难,但要把每个维度变换的理由说清楚,挺耗时间的。

题目二:DPO的完整训练流程推导,从数据准备到梯度更新

说实话这道题是我答得最没底气的。

DPO全称Direct Preference Optimization,是2023年Rafailov他们提出的。它的核心思路很反直觉——把语言模型本身当成奖励模型来用

传统的RLHF要三步走:先训练一个奖励模型,再用PPO去优化策略。DPO直接跳过了奖励模型这一步,用偏好数据直接优化模型参数。

推导过程是这样的(我考后重新捋了一遍):

第一步:偏好建模
Bradley-Terry模型定义了人类偏好的概率分布:

公式

其中r(x,y)是奖励函数,σ是sigmoid函数

公式

。我们的目标是最大化这个概率。

第二步:用策略替代奖励
在RLHF的标准框架中,我们优化的目标是:

公式

这个KL散度约束

公式

可以看作一个正则项,防止模型偏离参考模型太远。

解这个约束优化问题,可以得到闭式解:

公式

其中

公式

是配分函数。

把这个式子倒过来,把奖励函数用策略表示:

公式

把上式代入Bradley-Terry模型,βlogZ(x)项会抵消掉,得到:

公式

第三步:构造损失函数
直接对偏好概率取负对数,得到DPO损失:

公式

展开写更清楚:

公式

这个式子看着吓人,拆开就懂了:

  • 括号里算的是“chosen和rejected在当前模型与参考模型之间的概率比差值”

  • β控制偏离参考模型的程度,β越大,模型越敢偏离参考模型去迎合偏好数据

  • sigmoid把它压成概率,损失就是最大化这个概率

第四步:梯度更新
对π求梯度,经过链式法则化简后可以得到:

公式

其中

公式

注意到这个梯度的权重是σ(β⋅Δ)。如果当前模型给rejected分配了过高的概率,Δ会变小甚至变负,权重就会变大,梯度就会更大力度地压低rejected、提升chosen。

实际训练时,我推导到损失函数那一步就有点乱了,公式里的β和log的位置在纸上写了两遍才理顺。

事后查资料才发现,DPO还有个坑——梯度不平衡。因为权重项主要由rejected决定,chosen样本的梯度贡献相对较小,容易导致模型过度惩罚bad response,影响生成多样性。

这道题我答得不算好,但面试官似乎更在意我能不能把“DPO为什么能绕过奖励模型”这个逻辑讲清楚。

题目三:MOE模型的通信开销计算和负载不均衡问题分析

MOE(Mixture of Experts)现在是大模型的标配了。DeepSeek-V3有6710亿参数,但每次推理只激活5.5%——这就是MOE的核心价值:参数可以很大,但计算量可控。

通信开销这块,核心是All-to-All通信

为什么是All-to-All?因为MOE里每个token只路由到top-k个专家,不同专家分布在不同GPU上。Token要发往对应的专家GPU,专家计算完结果要汇总回来——这一来一回就是两次All-to-All。

通信量怎么算?假设:

  • 专家数量:E

  • 每个专家在单个GPU上

  • 每个token的embedding维度:d

  • 每个GPU上的token数量:T

单次All-to-All的通信量大约是:

公式

更精确地,设第i个GPU分到的token数量为

公式

,那么通信量的完整表达式是:

公式

其中

公式

是从GPU i路由到GPU j上专家的token数量。如果是top-2路由,每个token要发给两个专家,通信量还要翻倍。

更关键的是,GPU的忙碌时间由“计算时间 + 通信时间”两部分构成:

公式

其中

公式

(一来一回)。通信和计算是串行的,GPU在等数据的时候啥也干不了。当专家数量E增大时,

公式

线性增长,

公式

因为每个GPU上专家数变少而下降,最优的E需要平衡这两部分。

更麻烦的是负载不均衡

路由机制是动态的,由Gating Network决定:

公式

,选择top-k个专家激活。这导致有些“热门专家”收到的token远超平均值,有些“冷门专家”闲得要命。

负载不均衡可以用变异系数来衡量:

公式

理想情况下

公式

,CV=0。实际上CV可能高达0.5以上,意味着热门专家的GPU算到冒烟,冷门专家的GPU在摸鱼,整个集群的算力利用率被拉低。

解决思路一般有几个方向:

  • 辅助损失(Auxiliary Loss) :在训练目标里加一项

    公式

    ,其中

    公式

    是专家i被选中的频率,

    公式

    是路由概率,惩罚分布不均匀

  • 专家并行优化:像NVIDIA的Hybrid-EP方案,优化All-to-All的通信模式

  • 动态调整专家容量:每个专家设定token上限

    公式

    ,超出的token重新路由:

    公式

这道题我答得相对顺,因为之前读过几篇MOE的论文。但面试官追问了一个细节——“DeepSeek-V3具体用了多少专家、激活多少?”——我卡住了。后来查了才知道是256个专家、激活8个。

题目四:推理加速的底层实现(vLLM的PagedAttention原理、投机解码的工程实现)

这道题考的是推理优化,两个点:PagedAttention和投机解码。

先说说PagedAttention

LLM推理最大的内存瓶颈在哪?KV Cache。对于Transformer的每一层,每个token需要存储

公式

维度的KV向量。总KV Cache大小是:

公式

其中L是层数,

公式

是序列总长度。对于70B参数的模型,

公式

公式

,单个token的KV Cache就约2MB,生成长序列时内存压力巨大。而且每个请求的KV Cache大小随序列长度增长,unpredictable——你没法提前知道用户会问多长的问题。

传统做法是预先分配一块连续内存,不够了就重新分配、拷贝。碎片化严重,利用率低。

vLLM的思路来自操作系统——虚拟内存的分页管理。把KV Cache切成固定大小的“块”(block),每个块存固定数量token的K和V。这些块在物理内存上可以不连续,按需分配。

设block大小为B(通常

公式

),则每个请求占用的block数为

公式

,内存浪费从连续分配的

公式

降到了O(B)级别。内存利用率从传统方案的约40%提升到接近100%,支持更大batch size,吞吐量大幅提升。

投机解码(Speculative Decoding) 的思路更巧妙。

大模型生成token是自回归的——一个一个往外蹦,每步都要等。设目标模型(大模型)生成一个token的时间为

公式

,自回归生成N个token的总时间是

公式

投机解码用一个小模型(draft model)先“猜”出接下来γ个token,然后大模型一次性验证这γ个token对不对。验证过程可以并行,大模型只需要算一次前向,就能同时验证多个token。

加速比怎么算?设draft模型生成γ个token的时间为

公式

,target模型验证一次的时间为

公式

。假设接受率为α(即平均有多少个token被接受),则生成

公式

个token的总时间为:

公式

而自回归方式生成同样数量token的时间是:

公式

理论加速比:

公式

如果

公式

,比如

公式

公式

,理论加速比约为:

公式

实际接受率α会影响平均有效生成长度,更精确的期望加速比公式要考虑拒绝后的回退。如果第一个token就被拒绝,需要回退到重新生成。

工程实现上的难点在于:

  • draft model的选择:太小了猜不准(α低),太大了

    公式

    高,加速效果不明显

  • 树形采样:每一步生成多个候选,形成树结构,提高命中率

  • 接受率α:实际生产中,投机解码的token接受率直接影响加速比,通常α在0.6-0.8之间

这道题我答得中规中矩,PagedAttention的原理说清楚了,但投机解码的工程细节(比如怎么处理draft model和target model的vocab不一致)说得不够细。

题目五:DSpark推理加速框架的核心机制推导

6月27日DeepSeek联合北大发布了DSpark推理加速框架,实测在V4-Flash上单用户生成速度提升60%-85%,高并发场景下有效吞吐量翻4倍。这道题如果考,大概率会让你推导DSpark的两项核心机制。

DSpark解决的核心问题是:传统推测解码中,自回归草稿模型(如Eagle3)逐token串行生成,草稿耗时随候选长度线性增长;并行草稿模型(如DFlash)虽然一次前向生成全部候选,但每个位置无法依赖块内先前token,导致长候选块接受率急剧衰减。

DSpark的第一个机制是半自回归架构

公式

公式

并行主干网络一次性产出全部候选位置的隐藏状态H,然后轻量级顺序模块逐token注入前缀依赖。顺序模块提供两种实现——仅依赖前一个token的马尔可夫头,以及通过循环状态累积完整前缀信息的RNN头。实验表明,两层Transformer深度的DSpark就能在所有测试领域上超过五层DFlash的接受长度。

第二个机制是置信度调度验证

公式

公式

模型在每个候选位置输出置信度分数

公式

,预测该token在给定此前所有token均被接受的条件下的存活概率。训练完成后在验证集上通过逐位置温度缩放校准,使置信度与经验接受率对齐。

然后硬件感知前缀调度器将验证长度选择建模为全局吞吐量最大化问题:

公式

给定一批并发请求及其各位置置信度,结合预先实测的引擎吞吐量曲线,调度器为每个请求动态决定验证多长的候选前缀,优先将目标模型的计算资源分配给全局存活概率最高的token。

推导过程中要注意:DSpark在全部目标模型、全部评测领域下稳定超越Eagle3与DFlash,Qwen3-4B上宏平均接受长度相对Eagle3提升30.9%、相对DFlash提升16.3%。结构化任务(数学推理、代码生成)的可接受长度天然更高(Qwen3-4B数学任务平均5.57,代码5.12),而开放式对话场景偏低(仅3.49)——这说明DSpark在结构化任务上加速效果更明显。

题目六:DeepSeek-V4的KV缓存压缩原理与内存占用计算

5月24日DeepSeek发布V4模型,参数规模达1.6万亿。最惊人的是KV缓存压缩——在百万级上下文长度下,V4仅需5.48GB高带宽内存即可运行;相比之下,参数量7000亿的GLM5需60GB,2350亿的Qwen3-235B-A22B需89GB。

这道题如果考,会让你推导KV压缩的原理并计算内存占用的理论下界。

标准Multi-Head Attention中,KV Cache的大小为:

公式

V4采用的KV压缩技术,核心是在保持注意力质量的前提下大幅降低每token的KV存储量。设压缩比为R(V4实际达到约28.5倍),则:

公式

对于百万级上下文

公式

公式

公式

,FP16存储(2 bytes/param):

公式

公式

但实际V4仅需5.48GB,说明压缩比远高于28.5倍,或者V4采用了更激进的分层压缩策略——可能对不同层使用不同压缩率,浅层保留更多信息、深层大幅压缩。

关键推导点在于:V4的压缩不是简单的量化或剪枝,而是硬件感知的KV缓存协同设计。DeepSeek将KV缓存压缩作为核心突破方向,大幅降低对高端HBM和高速闪存的依赖。这意味着压缩算法的设计必须与内存带宽、访问模式等硬件特性联合优化。

追问可能会让你分析:压缩引入的近似误差如何影响生成质量?以及在不同上下文长度下,压缩率应该动态调整还是固定?

题目七:多模态视觉原语推理框架的数学建模

4月30日DeepSeek开源了多模态大模型,首创“视觉原语”推理框架。核心洞察是:自然语言固有的模糊性与空间布局精确表达之间存在结构性落差——即“参照鸿沟”。

这道题如果考,会让你形式化定义视觉原语推理框架。

传统多模态Chain-of-Thought可以表示为:

P(answer∣I,Q)=t=1∏T​P(st​∣I,Q,s<t​)

其中

公式

是文本形式的推理步骤。问题在于:当推理步骤涉及空间位置时(如“左上角那个红色物体”),自然语言的模糊性导致模型无法精确定位。

视觉原语框架的核心创新是:将点、边界框等具有明确空间语义的几何元素直接纳入模型的推理基本单元。设视觉原语空间为P,包含:

公式

推理过程变为:

公式

其中

公式

,T是文本token空间。模型可在推理过程中动态生成可定位、可指代的空间锚点,将抽象的认知过程稳定映射至图像中的具体物理坐标。

关键推导点在于:视觉原语和文本token如何统一建模?概率如何在两种模态间分配?以及空间锚点的坐标误差如何传播?

实测表明该模型在多项计数与空间关系推理基准上达到领先水平,与当前主流前沿模型持平。

题目八:DeepSeek MoE的无辅助损失负载均衡机制推导

传统MoE通过辅助损失来约束负载均衡:

公式

其中

公式

是专家i被选中的频率,

公式

是路由概率。

但DeepSeek MoE引入了一种无需辅助损失的负载均衡方案。这道题如果考,会让你推导其数学原理。

核心思想是:通过专家级偏置项(expert-level bias)动态调整路由logits,而非在损失函数中加惩罚项。

设第i个专家的路由logit为

公式

,加入偏置项

公式

公式

路由概率变为:

公式

偏置项

公式

根据专家负载动态更新:

公式

其中

公式

是专家i当前接收的token数,

公式

是平均负载,η是更新步长。

这意味着:负载高的专家获得负偏置(路由概率被压低),负载低的专家获得正偏置(路由概率被抬高),从而在不修改损失函数的前提下实现负载均衡。

与辅助损失方案相比,无辅助损失方案的优势在于:不干扰主任务的优化目标,避免了辅助损失系数α的调参难题,且在训练早期就能有效工作。

追问可能会涉及:偏置项的更新频率如何设定?极端负载不均衡情况下偏置项是否会失控?以及这种方案在MoE层数增多时的扩展性如何?

最后说两句

这些题的特点是:全都来自DeepSeek最近两个月的真实技术产出。DSpark是6月27日刚发的,V4是5月24日发的,多模态视觉原语是4月30日开源的,无辅助损失负载均衡是DeepSeek MoE的长期技术积累。

如果你在准备DeepSeek的算法岗,光刷LeetCode肯定不够——得追着他们的论文和开源项目跑。这些题考的不是背答案的能力,而是你能不能跟上这家公司技术迭代的速度

毕竟,一家能让创始人在融资后还亲自写论文的公司,对技术的认真程度摆在那里。笔试只是第一关,后面还有几轮面试在等着呢。

更多推荐