2025.7.31 面试记录

记录面试

聊实习环节

Q:拷打实习,主要包括内容、产出、分布式框架使用了什么网络通信方式之类
A:略(大约15分钟)

基础知识环节

Q:你们的工作里有没有使用过RLHF?你了解RLHF么?
A:之前有,但是效果不好所以没用。我不太了解具体过程。

分析:RLHF:
RLHF(Reinforcement Learning from Human Feedback)是一种结合人类反馈与强化学习的训练方法。其核心思想是通过人类的偏好或评价来指导智能体的学习过程,以便更好地适应复杂任务或环境。
RLHF的基本流程如下:

  1. 人类反馈收集:首先,收集人类对智能体行为的反馈。这可以是直接的偏好比较(例如,给出两个动作,哪个更好?)或评分(例如,给出一个动作的质量评分)。
  2. 奖励模型训练:使用收集到的人类反馈数据来训练一个奖励模型。这个模型的目标是预测在给定状态下,某个动作的好坏程度。通常使用监督学习方法来训练这个模型。
  3. 强化学习优化:将训练好的奖励模型作为环境的奖励函数,智能体在这个环境中进行强化学习。智能体通过与环境交互,不断优化其策略,以最大化预期的奖励。
  4. 迭代改进:在智能体学习过程中,可以持续收集人类反馈,并更新奖励模型,以便智能体能够适应新的任务或环境变化。
    RLHF的优势在于它能够利用人类的直觉和经验来指导智能体学习,尤其在复杂任务中,人类的反馈可以提供更丰富的信息,帮助智能体更快地收敛到有效策略。此外,RLHF还可以用于解决传统强化学习中难以定义奖励函数的问题,因为人类可以直接提供关于行为的偏好,而不需要明确的奖励信号。
    然而,RLHF也存在一些挑战,例如如何有效地收集高质量的人类反馈、如何处理人类反馈的噪声以及如何平衡人类反馈与智能体自主探索之间的关系等。

Q:PPO是On-policy的还是Off-policy的?
A:PPO是On-policy的,这意味着与环境交互的Actor和用于训练的模型是同一个模型。
Q:你知道DeepSeek对PPO的改进吗?
A:GRPO
Q:GRPO为什么能改进PPO?
A:(这个问题没有回答)

分析:
GRPO(Group Relative Policy Optimization)是在PPO(Proximal Policy Optimization)基础上针对语言模型强化学习场景的革新,其核心在于用群体比较机制替代价值模型,并通过相对奖励归一化实现更稳定的策略优化。这一改进大幅降低了计算负担,同时提升了数学推理等稀疏奖励任务的表现,具体有效性原理如下:

1. 消除价值模型,降低计算成本

传统PPO需同时训练策略模型(Actor)和价值模型(Critic),后者需额外存储参数并参与前向/反向传播,导致内存与算力开销翻倍。GRPO直接舍弃价值模型,改为对同一问题采样多个答案(如G=64组),用组内平均奖励作为基线(Baseline)估算优势函数:

Ai=rimean(r)std(r)A_i = \frac{r_i - \text{mean}(r)}{\text{std}(r)}

其中 rir_i 是第 ii 个答案的奖励,mean(r)\text{mean}(r)std(r)\text{std}(r) 分别表示组内奖励的均值和标准差。这一设计使7B模型的训练内存从PPO的20GB降至13GB,效率提升30%以上。

2. 群体相对评估提升奖励鲁棒性

PPO的价值模型需为每个token估算价值,但在数学推理等任务中,奖励通常仅在答案生成后稀疏给出(如答案正确性),导致中间token的价值估计误差累积。GRPO的群体比较机制则天然适配稀疏奖励:

  • 相对排名替代绝对分数:归一化后的优势函数 AiA_i 本质是组内答案的“竞争力评分”。模型通过比较同类答案的优劣优化策略,避免了对绝对奖励数值的过度敏感。
  • 隐式状态价值估计:组平均奖励 mean(r)\text{mean}(r) 可近似视为状态价值 V(s)V(s)(即当前问题下所有可能动作的平均回报),使 AiQ(s,ai)V(s)A_i \approx Q(s,a_i) - V(s) 的估计更稳定。

3. 适配数学推理的任务特性

数学问题通常存在明确答案(如最终数值),但解法路径多样。GRPO的群体采样机制能覆盖多种解题策略:

  • 高质量答案获得正优势,推动模型学习更优解法;
  • 低质量答案的负优势抑制错误模式。
    实验表明,这种对比优化使DeepSeekMath在GSM8K准确率从82.9%提升至88.2%,MATH数据集从46.8%提升至51.7%,超越同期同等规模开源模型。

4. KL散度惩罚的显式控制

PPO依赖裁剪(Clipping)隐式约束策略偏移,但GRPO在目标函数中显式添加KL散度惩罚项

JGRPO(θ)=E[]βDKL(πθπref)J_{\text{GRPO}}(\theta) = \mathbb{E}[\cdots] - \beta D_{\text{KL}}(\pi_\theta \parallel \pi_{\text{ref}})

其中 β\beta 调节策略更新幅度,πref\pi_{\text{ref}} 为参考模型(SFT阶段模型)。此举防止优化后的模型偏离初始语言能力,确保生成文本的流畅性和安全性。


为什么有效? GRPO的有效性源于其对PPO痛点的精准改进:

  • 计算效率:去除价值模型减少资源消耗,使中小团队也能高效训练;
  • 奖励稳定性:群体归一化抵消奖励模型的系统性偏差,避免单一答案的奖励噪声误导优化;
  • 任务适配性:稀疏奖励下的组间对比,更贴合数学推理等“结果导向”任务的需求。
    这些改进共同推动GRPO成为大模型数学能力优化的实用算法,被DeepSeek等团队验证为闭源模型竞争力的关键突破。

Q:说说你论文里的ASN
A:(我已经忘记我的论文里还有ASN了)我的论文里的ASN是我自己取名的一个创新性模块,可能和您说的不是同一个东西。
Q:好吧那不问了

Q:手写一个MAPPO的伪代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class MAPPO:
def __init__(self, n_agents):
self.n_agents = n_agents
self.actors = [Actor() for _ in range(n_agents)] # 每个智能体的策略网络
self.critic = GlobalCritic() # 中心化critic
self.memory = ReplayBuffer()

def train(self, max_episodes):
for episode in range(max_episodes):
# 收集轨迹
observations = env.reset()
episode_data = []

for t in range(max_steps):
# 每个智能体选择动作
actions, log_probs = [], []
for i, actor in enumerate(self.actors):
action, log_prob = actor.sample(observations[i])
actions.append(action)
log_probs.append(log_prob)

# 环境交互
next_obs, rewards, dones = env.step(actions)

# 存储transition
self.memory.store(
observations, actions, rewards,
next_obs, log_probs, dones
)

observations = next_obs
if all(dones):
break

# 训练阶段
for _ in range(update_epochs):
batch = self.memory.sample()

# 计算优势函数和回报
values = self.critic(batch.global_states)
advantages = compute_gae(
rewards=batch.rewards,
values=values,
gamma=0.99,
lambda_=0.95
)
returns = advantages + values

# 更新每个智能体的策略
for i in range(self.n_agents):
# 计算新策略的动作概率
new_log_probs = self.actors[i].evaluate(
batch.observations[i],
batch.actions[i]
)

# 计算策略比率
ratio = torch.exp(
new_log_probs - batch.log_probs[i]
)

# PPO裁剪目标函数
actor_loss = -torch.min(
ratio * advantages[i],
torch.clamp(ratio, 1-epsilon, 1+epsilon) * advantages[i]
).mean()

# 更新策略网络
self.actors[i].optimizer.zero_grad()
actor_loss.backward()
self.actors[i].optimizer.step()

# 更新critic网络
value_pred = self.critic(batch.global_states)
critic_loss = F.mse_loss(value_pred, returns)

self.critic.optimizer.zero_grad()
critic_loss.backward()
self.critic.optimizer.step()

# 清空回放缓冲区
self.memory.clear()

Q:不错,如何把PPO改成off-policy的?
A:我认为为了消除不同策略带来的分布差异,应该使用重要性采样来调整策略的更新。然后通过经验回放来存储和重用经验。

分析:
PPO本质上是一个On-policy算法,但可以通过一些技巧将其改造成Off-policy版本。以下是一些关键步骤:

  1. 使用重要性采样:在Off-policy设置中,策略的分布可能与当前策略不同。可以使用重要性采样来调整策略更新:
    • 计算旧策略和新策略的概率比率:rt=πθ(atst)πθold(atst)r_t = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}
    • 在PPO目标函数中引入这个比率来调整损失函数。
  2. 经验回放:使用经验回放缓冲区存储智能体的交互数据。每次更新时,从缓冲区中采样批次数据,而不是直接从环境中采样。这允许重用过去的经验,提高样本效率。

Q:了解Transformer吗?
A:了解的
Q:说说Transformer的Q、K、V的含义
A:Q,K,V分别对应查询、键和值。按我理解,实际上是先计算Q和K的相似度分数,然后用这个相似度乘上值来获得查询的结果,或者说查询向量的编码。
Q:Transformer的变种你知道多少
A:我大概知道稀疏注意力,其他的不太了解

分析:这里没答上来,有一些变体,记录一下:

  • 稀疏注意力:如Reformer、Longformer等,通过限制注意力计算的范围来降低计算复杂度,适用于长序列任务。
  • 多模态Transformer:如ViT(视觉Transformer)等,将视觉和文本信息结合,适用于图像和文本的联合任务。

Q:transformer位置编码的作用
A:在Transformer中,长序列被作为整体送入神经网络,无法区分位置带来的影响。位置编码就是为了标志每个向量的位置,帮助模型理解序列中各个元素的相对或绝对位置。

分析:由于Transformer的self-attention机制本身是位置无关的,也就是说它会"忽略"序列中token的位置信息,这对于需要考虑序列顺序的任务来说是不够的。位置编码的引入就是为了让模型能够感知token的相对或绝对位置。

最常用的是正弦位置编码,对于位置pos和维度i,编码表示为:

PE(pos,2i)=sin(pos100002i/d)PE(pos,2i) = \sin(\frac{pos}{10000^{2i/d}})

PE(pos,2i+1)=cos(pos100002i/d)PE(pos,2i+1) = \cos(\frac{pos}{10000^{2i/d}})

这种编码方式的优势在于可以让模型轻松计算出任意两个位置之间的相对位置关系。而且由于正弦函数的周期性,即使序列很长也能保持位置的可区分性。当然也有其他方案如可学习的位置编码,但基于三角函数的这种方式因其数学特性和实现简单性被广泛采用。

Q:python中多进程访问同一个文件怎样避免冲突
A:用锁的方式
Q:还有别的方式吗
A:不知道

分析:
在Python中,多进程访问同一个文件时,除了使用锁(如multiprocessing.Lock)来避免冲突外,还有以下几种方法可以考虑:

  1. 文件级别锁:使用fcntl模块提供的文件锁功能,可以在打开文件时加锁,确保同一时间只有一个进程可以写入文件。
  2. 使用队列:将需要写入的数据放入multiprocessing.Queue中,然后由一个单独的进程负责从队列中读取数据并写入文件。这种方式可以避免多个进程同时写入同一文件。
  3. 临时文件:每个进程写入自己的临时文件,最后由一个进程将所有临时文件合并到最终文件中。这种方式可以避免直接冲突,但需要额外的合并步骤。

Q:python中字典的数据结构是什么
A:Hashmap

Q:有哪些比较常用的哈希函数
A:(不知道,随便说了个MD5加密的方法,给面试官逗乐了。。

分析:不重要

Q:RNN和Transformer的区别
A:RNN由于把输入序列化,导致越往后越会忘记前文。而Transformer从全局计算注意力矩阵,很好的考虑到了上下文信息。为了解决位置的独特影响,Transformer加入了位置编码。

分析:

  1. 序列处理方式

    • RNN:采用循环结构,按时间步顺序处理输入,当前时刻状态依赖于前一时刻状态。基本公式:ht=f(Whht1+Wxxt+b)h_t = f(W_h h_{t-1} + W_x x_t + b)
    • Transformer:基于自注意力机制,同时处理整个序列,每个位置直接与所有位置交互。核心公式:Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V
  2. 长程依赖

    • RNN:存在长期依赖问题 - 信息在序列传递过程中会逐渐衰减(梯度消失)或爆炸。LSTM/GRU等变体通过门控机制缓解但未完全解决此问题。
    • Transformer:通过自注意力机制建立任意距离位置间的直接连接,序列长度不影响信息传递距离,理论上能捕获无限长的依赖关系。
  3. 计算并行性

    • RNN:本质上是顺序计算,第t步必须等待第t-1步完成,无法并行。
    • Transformer:所有位置的计算可完全并行化,极大提高训练和推理速度,特别适合现代GPU/TPU加速。
  4. 位置信息

    • RNN:位置信息隐式编码在序列处理顺序中。
    • Transformer:需要显式添加位置编码(如正弦/余弦位置编码或可学习位置嵌入),否则模型无法区分不同位置的token。

Q:在一个POMDP中,如果需要使用局部观测观测进行全局信息的估计,你会使用LSTM还是Transformer?
A:其实我更倾向于LSTM,因为在RL问题中,往往越近的观测更具有可信度,那么遗忘就不是一件坏事了。这也就是为什么我们需要折扣因子的原因。

分析:

  1. LSTM对序列数据有天然的处理能力,特别适合POMDP中需要从历史观测推断当前状态的场景
  2. 计算效率方面,LSTM处理增量观测很高效,不需要每次都重新计算整个历史的注意力,这在实时决策环境中很重要
  3. LSTM的门控机制能够学习什么信息该记住、什么该忘记,这对处理有噪声的观测很有用
  4. 在RL环境中,LSTM产生的状态表示通常比较稳定,这对策略学习有好处

Transformer的优势:

  1. 如果历史观测中有关键信息,但出现顺序不规则,Transformer的全局注意力机制可能表现更好
  2. 对于超长序列依赖的环境,理论上Transformer不会像LSTM那样受到梯度消失的限制
  3. 训练速度上,Transformer的并行计算有优势

Q:RNN的主要问题以及后续GRU和LSTM如何解决
A:RNN主要问题就是长程依赖性问题,GRU和LSTM通过加入门控机制来解决,但这一块原理我有些淡忘了。

分析:这里确实忘了,毕竟没有经常使用RNN,基本不记得了。
首先,RNN的核心问题有两个方面:

  1. 梯度消失问题:在反向传播时,梯度会沿着时间步骤连乘,由于权重通常小于1,连乘后梯度会接近于0,导致远距离的信息几乎无法影响当前输出。这就是为什么vanilla RNN很难学习长距离依赖关系。
  2. 梯度爆炸问题:相反,如果权重大于1,梯度会随着时间步呈指数增长,导致训练不稳定。虽然梯度裁剪可以部分缓解这个问题,但并不能从根本上解决。
    针对这些问题,LSTM和GRU提出了不同的门控机制:
    LSTM引入了三个门:
  • 遗忘门:决定丢弃多少上一状态的信息
  • 输入门:决定更新多少新信息到细胞状态
  • 输出门:决定输出多少细胞状态的信息

这种设计的核心创新是细胞状态(cell state)通道,它可以不受门控直接传递信息,避免了反向传播中的连乘效应,从而解决梯度消失问题。
GRU则是LSTM的简化版本,它只使用两个门:

  • 更新门:相当于LSTM的输入门和遗忘门的组合
  • 重置门:控制使用多少前一状态的信息

GRU计算更高效,参数更少,在很多任务上表现与LSTM相当,特别是在数据量较小时可能更不容易过拟合。
这些门控机制的本质是让网络学会选择性地记住有用信息、遗忘无关信息,从而能够捕获长距离依赖。以语言模型为例,如果序列开头出现"我来自中国",即使在很多词之后,当需要用代词时,网络依然能"记得"说话者是"我"而非"他"。

现场编程环节

  1. 地上有一个rows 行和 cols 列的方格。坐标从[0,0]到[rows-1, cols-1]。一个机器人从坐标[0,0]的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 threshold 的格子。例如,当 threshold为 18 时,机器人能够进入方格[35,37],因为3+5+3+7=18。但是,它不能进入方格[35,38],因为3+5+3+8=19.请问该机器人能站达到多少个格子?

输入示例:
1,2,3
输出示例:
3
分析:这道题目可以使用DFS或BFS来解决。我们可以从起点[0,0]开始,递归地探索四个方向的格子,并检查是否满足条件(行坐标和列坐标的数位之和小于等于threshold)。如果满足条件,就继续探索下一个格子,直到无法再移动为止。

1
2
3
4
5
6
7
8
9
10
11
12
def digit_sum(x):
return sum(int(digit) for digit in str(x))
def can_move(x, y, threshold):
return digit_sum(x) + digit_sum(y) <= threshold
def moving_count(rows, cols, threshold):
visited = set()
def dfs(x, y):
if (x, y) in visited or not (0 <= x < rows and 0 <= y < cols) or not can_move(x, y, threshold):
return 0
visited.add((x, y))
return 1 + dfs(x + 1, y) + dfs(x - 1, y) + dfs(x, y + 1) + dfs(x, y - 1)
return dfs(0, 0)

总结

面试整体感觉还不错,虽然有些问题没有答上来,但整体还是比较顺利的。至少手撕部分都写出来了。不过依然需要在RLHF、GRPO等方面加强学习,毕竟这些都是我之前没有接触过的内容。关于python底层,无所谓了吧。


2025.7.31 面试记录
http://dufolk.github.io/2025/08/01/netease1/
作者
Dufolk
发布于
2025年8月1日
许可协议