华为AI岗-27届秋招题目8.19
单选题(第1-5题)
1、在极高维度的特征空间(如维度 d ≥ 4096)中,如果随机采样两个均匀分布的向量,它们的余弦相似度分布会?(单选)
A.极度集中在 0 附近,绝大多数随机向量彼此几乎正交B.呈现均匀分布,在 [-1, 1] 之间等概率出现D.极度集中在 1 附近,维度灾难导致所有向量相互趋同答案:A
解析:高维空间中存在“随机向量近似正交”的几何现象。维度越高,两个随机均匀向量的夹角越接近 90°,因此余弦相似度会集中在 0 附近。
2、在 FlashAttention 优化中,Tiling(分块)策略的主要目的是什么?(单选)
C.减少对 HBM 的访问,将计算限制在 SRAM 中答案:C
解析:FlashAttention 将 Q、K、V 切分为小块,充分利用高速 SRAM,减少较慢的 HBM 读写。注意力计算的主要瓶颈之一正是 IO 开销。
3、设 T: Rⁿ → Rᵐ 为一个映射。若对任意向量 u、v 以及任意标量 α,满足 T(u+v)=T(u)+T(v) 且 T(αu)=αT(u),则称 T 为线性变换。任意线性变换都可以通过哪种结构表示?(单选)
答案:C
解析:有限维线性变换与矩阵一一对应,任意 Rⁿ → Rᵐ 的线性变换都可以写成 y=Ax。
4、在处理大规模数据集时,为了降低层次凝聚聚类(HAC)的内存占用,以下哪种做法是合理的?(单选)
答案:D
解析:HAC 原始算法需要 O(N²) 的距离矩阵,大数据下容易造成内存压力。采用稀疏近邻图,只保存近邻距离,可显著降低内存占用。
5、音视频联合生成模型(如 VideoLDM、Stable Video Diffusion)中,音频条件通常如何注入视频生成过程?(单选)
A.将音频转换为频谱图作为第一帧,用图像扩散模型生成后续帧C.通过音频编码器提取时序特征,经交叉注意力或 AdaIN 注入视频 UNet 的时序层答案:C
解析:音视频扩散生成通常先通过音频编码器得到时序音频特征,再借助交叉注意力或 AdaIN 注入视频 UNet 的时序模块,从而实现音频驱动的视频生成。
单选题(第6-10题)
6、在循环神经网络(RNN)中,处理长序列时容易遇到的主要问题是?(单选)
答案:A
解析:RNN 在长序列反向传播时需要连续进行链式求导和权重矩阵连乘,因此容易出现梯度消失或梯度爆炸。
7、关于 Transformer 的编码器—解码器架构(如原始 Transformer)与仅解码器架构(如 GPT 系列)的区别,下列说法正确的是?(单选)
C.仅解码器架构使用双向注意力,编码器—解码器架构使用单向注意力答案:D
解析:编码器—解码器架构的解码器包含掩码自注意力和读取编码器输出的交叉注意力;GPT 等仅解码器架构只有因果自注意力,不包含交叉注意力。
8、设 A、B、C 为事件,已知 A 与 B 互斥,P(A)=0.25,P(B)=0.15,P(C)=0.4,P(A∩C)=0.1,P(B∩C)=0.05,则 P(A∪B|C) 为?(单选)
答案:A
解析:由于 A、B 互斥,P(A∪B|C)=[P(A∩C)+P(B∩C)]/P(C)=(0.1+0.05)/0.4=0.375。
9、某大模型采用 PagedAttention 管理 KV Cache,将 KV Cache 划分为大小为 4KB 的页。现有 5KB、2KB、3KB 三个碎片,关于分页分配,下列说法正确的是?(单选)
A.PagedAttention 无法利用小于 4KB 的碎片,3 个碎片均无法分配,仍为碎片化B.5KB 碎片可分配 1 个 4KB 页;2KB 与 3KB 碎片可合并后再分配 1 个 4KB 页C.仅 5KB 碎片可分配 1 个 4KB 页,2KB、3KB 碎片无法利用D.3 个碎片均可直接分配 4KB 页,实现无碎片利用答案:A
解析:PagedAttention 的页大小固定为 4KB,分配必须以完整页为单位,小于页大小的内部碎片不能直接复用;碎片也不会自动合并。
10、若随机变量 X~N(0, σ²),则其四阶中心矩 E[X⁴] 等于?(单选)
答案:B
解析:正态分布 N(0, σ²) 的四阶中心矩为 E[X⁴]=3σ⁴;标准正态分布的四阶矩为 3。
单选题(第11-15题)
11、在二分类任务中,以下哪一项最准确地描述了精确率(Precision)?(单选)
答案:D
解析:Precision=TP/(TP+FP),表示预测为正的样本中,真实正样本所占的比例。实际为正的样本中被预测为正的比例是 Recall。
12、在混合专家模型(MoE)中,一个 Transformer Block 的专家并行核心思想是什么?(单选)
A.将 Experts 分布到不同 NPU/GPU答案:A
解析:MoE 专家并行将不同专家分散部署到不同设备,Token 根据路由结果发送到对应设备上的专家进行计算。
13、在反向传播中累积梯度时,哪种情况最可能导致数值下溢(underflow)?(单选)
答案:A
解析:下溢是极小浮点数超出表示范围后变为 0。使用 FP16、bfloat16 等低精度格式保存和累加非常小的梯度时,容易发生数值下溢。
14、关于 GPT 系列模型的训练范式,下列描述正确的是?(单选)
答案:D
解析:GPT 采用自回归语言建模,给定上文预测下一个 Token,并使用单向因果注意力。掩码语言建模是 BERT 的典型预训练目标。
15、动量法的主要作用是?(单选)
答案:C
解析:动量法累积历史梯度,能够平滑更新方向、减少狭窄山谷中的震荡并加快收敛。
(多选题部分整理在题库里,加学长微信可获取:jackwwang8)
第1题 流水线并行最小瓶颈
题目描述
在大模型训练中,常使用流水线并行(Pipeline Parallelism)将模型的不同层划分到多个计算节点上执行。为避免某个节点负载过高导致系统整体变慢,需要合理划分模型层。
假设一个模型共有 L 层,第 i 层的计算量为 wᵢ(0 ≤ i ≤ L-1)。现需将这些层划分到 k 个计算节点上,使整个系统的峰值负载最小。
划分规则
每个计算节点负责一段连续的层,且至少分配 1 层;模型层按 0 → 1 → … → L-1 的顺序执行,划分不得打乱层序。若某节点分到连续层 wₗ、wₗ₊₁、…、wᵣ,则该节点的段负载为这些层计算量之和。系统峰值负载是所有节点段负载的最大值。
请输出合法划分下的最小系统峰值负载。
输入描述
第二行:L 个整型 w₀、w₁、…、wₗ₋₁,表示各层计算量。数据范围:1 ≤ L ≤ 128,1 ≤ k ≤ L,1 ≤ wᵢ ≤ 1000。
输出描述
输出一个整型,表示最小的系统峰值负载。
样例 1
输入:
5
3 1 4 1 5
2
输出:
8
说明:最优划分为 [3,1,4] 与 [1,5],两段负载分别为 8 与 6,峰值为 8。
解题思路
本题要求把长度为 L 的序列划分为恰好 k 段连续非空子段,使各段元素和的最大值最小。可以使用“二分答案 + 贪心验证”。
峰值的下界是 max(w),因为任何节点至少会承担一层;上界是 sum(w),对应所有层放在一个节点。给定候选上限 limit,从左到右累加当前段:如果加入当前层会超过 limit,就新开一段。该贪心过程得到峰值不超过 limit 时需要的最少段数。
若段数不超过 k,则 limit 可行。因为每段非空且 k ≤ L,还可以继续拆分已有区段,直到恰好得到 k 段,并且不会增大峰值。随后二分搜索最小可行的 limit 即可。
复杂度分析
时间复杂度:O(L log S),其中 S=sum(w)。更加详细解题思路和 CPP、Java 代码加我微信获取:jackwwang8
def min_peak(w, k):
"""二分最小峰值 limit,每次用贪心检验是否可行。"""
def can(limit):
cnt = 1
s = 0
for x in w:
if s + x > limit:
cnt += 1
s = 0
s += x
return cnt <= k
lo = max(w)
hi = sum(w)
while lo < hi:
mid = (lo + hi) // 2
if can(mid):
hi = mid
else:
lo = mid + 1
return lo
L = int(input())
w = list(map(int, input().split()))
k = int(input())
print(min_peak(w, k))
第2题 FlashAttention 分块合并
题目描述
在 Transformer 的 Attention 计算中,为降低长序列的内存开销,常采用 FlashAttention 分块流式计算。每个序列块维护局部状态三元组 (mx, wd, sv):
wd:重缩放后的权重和 Σexp(q·kⱼ-mx);sv:重缩放后的加权和 Σexp(q·kⱼ-mx)·vⱼ。该块的注意力结果为 attn=sv/wd。
相邻两块状态 A=(mxA,wdA,svA) 与 B=(mxB,wdB,svB) 合并为 C:
mxC = max(mxA, mxB)
wdC = wdA·exp(mxA-mxC) + wdB·exp(mxB-mxC)
svC = svA·exp(mxA-mxC) + svB·exp(mxB-mxC)
合并运算满足结合律。给定按顺序排列的 B 个块,需要处理 Q 次单点更新与区间查询:
1 i mx wd sv:把第 i 块替换为新状态;2 l r:依次合并第 l 到第 r 块,输出合并后的 sv/wd。输入描述
接下来 B 行:每行三个实数 mxᵢ、wdᵢ、svᵢ。数据范围:1 ≤ B,Q ≤ 2×10⁵,-50 ≤ mx ≤ 50,0 < wd ≤ 100,-10⁴ ≤ sv ≤ 10⁴。
输出描述
对每个区间查询输出合并后的注意力结果,保留小数点后 6 位。
样例
输入:
4 6
0.0 2.0 4.0
2.0 1.5 9.0
1.0 3.0 6.0
4.0 2.0 8.0
2 1 4
2 2 2
1 3 0.0 1.0 20.0
2 1 3
2 3 4
2 4 4
输出:
4.014241
6.000000
6.426028
4.145195
4.000000
解题思路
合并运算满足结合律,因此可以用线段树维护区间状态。每个结点存储其区间合并后的 (mx,wd,sv),merge按题目公式合并左右子区间。
单点更新时修改对应叶子,并向上重新合并父结点。区间查询时按从左到右的顺序合并所选结点。由于合并虽然满足结合律,但实现查询时仍需正确维护左右区间的顺序。
复杂度分析
更加详细解题思路和 CPP、Java 代码加我微信获取:jackwwang8
import math
def merge(a, b):
"""合并左块 a、右块 b,返回 (mx, wd, sv)。"""
mx_a, wd_a, sv_a = a
mx_b, wd_b, sv_b = b
mx = max(mx_a, mx_b)
ea = math.exp(mx_a - mx)
eb = math.exp(mx_b - mx)
return mx, wd_a * ea + wd_b * eb, sv_a * ea + sv_b * eb
class SegTree:
def __init__(self, blocks):
self.n = len(blocks)
self.size = 1
while self.size < self.n:
self.size <<= 1
self.tree = [(0.0, 0.0, 0.0)] * (2 * self.size)
for i, state in enumerate(blocks):
self.tree[self.size + i] = state
for i in range(self.size - 1, 0, -1):
self.tree[i] = merge(self.tree[i * 2], self.tree[i * 2 + 1])
def update(self, pos, value):
i = self.size + pos
self.tree[i] = value
i //= 2
while i:
self.tree[i] = merge(self.tree[i * 2], self.tree[i * 2 + 1])
i //= 2
def query(self, left_bound, right_bound):
left_bound += self.size
right_bound += self.size
left = right = None
while left_bound <= right_bound:
if left_bound & 1:
left = (self.tree[left_bound] if left is None
else merge(left, self.tree[left_bound]))
left_bound += 1
if not (right_bound & 1):
right = (self.tree[right_bound] if right is None
else merge(self.tree[right_bound], right))
right_bound -= 1
left_bound //= 2
right_bound //= 2
if left is None:
return right
if right is None:
return left
return merge(left, right)
B, Q = map(int, input().split())
blocks = []
for _ in range(B):
mx, wd, sv = map(float, input().split())
blocks.append((mx, wd, sv))
seg = SegTree(blocks)
for _ in range(Q):
parts = input().split()
if parts[0] == "1":
i = int(parts[1]) - 1
state = float(parts[2]), float(parts[3]), float(parts[4])
seg.update(i, state)
else:
left_bound = int(parts[1]) - 1
right_bound = int(parts[2]) - 1
mx, wd, sv = seg.query(left_bound, right_bound)
print(f"{sv / wd:.6f}")