大厂真题 / 华为

华为 2026-10-23 笔试真题 - AI岗

本场考试概述

考试时间:2026年10月23日

考试岗位:AI岗

难度评级:中等偏易

收录范围:材料标注选择题共 20 道,但完整披露的题干、选项与答案只有前 6 道单选题;本页仅收录这 6 道及 2 道编程题,不根据概述补造其余选择题。材料概述另提到因果掩码、GQA/MQA、推理加速、K-Means、KNN、F1、数据布局、矩阵分解和修正牛顿法,未披露的具体题目不展开。

考点分析:

  • 已披露选择题:自注意力计算复杂度、池化、PCA、模型评测、线性相关与泊松分布。
  • 第一题:KV Cache 内存池管理,核心是向上取整与排序贪心。
  • 第二题:OCR 智能纠错,核心是平滑概率、整数分数比较与同步自训练模拟。

建议策略:先把业务背景转成明确的计数与约束。第一题求最终最多保留多少请求,不必模拟缓存释放;第二题严格区分“用旧模型统一预测”和“重建新模型”,并落实平局时取字典序最小的规则。

选择题:已披露的 6 道

1. 自注意力分数矩阵的复杂度

假设输入序列长度为 $n$,每个 token 的维度为 $d$,在标准自注意力中,计算注意力分数矩阵 $QK^T$ 的复杂度是多少?

  • A. $O(nd^2)$
  • B. $O(nd)$
  • C. $O(n^2d)$
  • D. $O(n^2d^2)$

答案:C。 $Q,K$ 均为 $n\times d$ 矩阵,结果包含 $n^2$ 个元素,每个元素需要一次长度为 $d$ 的内积,故为 $O(n^2d)$。不要与生成 Q、K 的线性投影复杂度混淆。

2. 池化层的主要作用

池化层(POOLING)主要作用是:

  • A. 减少计算量
  • B. 增加非线性
  • C. 提取特征
  • D. 增加感受野

答案:A。 按本题单选口径,池化的主要作用是下采样,减小特征图的空间尺寸,从而降低后续计算量。最大池化也具有非线性,池化也可能扩大有效感受野,但不是此题要求的主要作用。注意:特征图变小不意味着后续卷积核的参数量必然减少;卷积参数量还取决于核大小与通道数。

3. PCA 的性质

关于 PCA,下列说法正确的是:

  • A. PCA 是有监督降维,会利用标签 $y$
  • B. PCA 寻找使类间距离最大的投影方向
  • C. PCA 的主成分是原特征的线性组合,按方差从大到小排序
  • D. PCA 对各特征的量纲不敏感,因此无需标准化

答案:C。 PCA 是无监督线性降维方法,选取投影方差较大的正交方向。它不使用分类标签,也不是按类间距离确定方向。特征量纲会影响方差及主成分,是否标准化应结合特征含义决定,不能认定总是不需要。

4. 固定标准答案的评测指标

在评测大模型的数学推理能力时,通常会使用含有 8500 道小学数学应用题的数据集 GSM8K。对于这类有固定标准答案的题目,最常用的评测指标是:

  • A. 准确率(Accuracy)
  • B. 语义相似度(Cosine Similarity)
  • C. 困惑度(Perplexity)
  • D. 训练时长(Training Time)

答案:A。 按规定抽取、规范化最终答案后,与标准答案比较,统计答对比例。语义相似不能代替数值答案判定,困惑度与训练时长也不直接衡量这类题目的正确率。

5. 线性相关的定义

线性相关的向量组满足?

  • A. 向量个数小于维度
  • B. 任意向量不能由其他向量线性表示
  • C. 存在不全为零的系数使线性组合为零
  • D. 向量长度相等

答案:C。 存在不全为零的系数 $k_1,\ldots,k_s$,使

\[k_1\alpha_1+\cdots+k_s\alpha_s=0,\]

就是线性相关的定义。向量个数小于维度并不能单独判断相关性;向量长度相等也不是判定条件。

6. 泊松分布

在概率论中,若随机变量 $X$ 服从泊松分布,且参数 $\lambda=2$,则 $P(X=0)$ 的值为?

  • A. $e^{-2}$
  • B. $1-e^{-2}$
  • C. $e^{-1}$
  • D. $2e^{-2}$

答案:A。 泊松分布的概率质量函数为

\[P(X=k)=\frac{\lambda^k e^{-\lambda}}{k!}.\]

代入 $k=0,\lambda=2$,得到 $P(X=0)=e^{-2}$。

第 1 题:大模型推理 KV Cache 内存池管理

题目描述

在大语言模型推理过程中,KV Cache 存储历史 token 的 Key 和 Value,避免重复计算,加速自回归生成。随着序列增长,缓存占用增加,系统使用预先分配的等大小内存块(Block)管理 GPU 内存。

内存池共有 $m$ 个 Block,每个 Block 可以容纳 $k$ 个 token 的 KV 数据。现有 $n$ 个推理请求,第 $i$ 个请求处理 $len[i]$ 个 token(输入长度加生成长度),需要占用

\[b_i=\left\lceil\frac{len[i]}{k}\right\rceil\]

个 Block。

分配规则如下:

  1. 请求按给定顺序依次到达。
  2. 对每个请求可以选择接受或拒绝;接受后的最终总占用不能超过 $m$,拒绝不占资源。
  3. 允许在后续决策中撤销之前已经接受的请求,释放其 Block,以获得更好的整体结果。
  4. 请求一旦被拒绝,不可再次考虑。
  5. 请求之间不能重排,必须按输入顺序进行决策。

求总占用不超过 $m$ 时,最终可以接受的最多请求数量。

输入描述与约束

第一行三个整数 $m,k,n$,分别表示 Block 总数、每个 Block 可容纳的 token 数和请求数。

第二行 $n$ 个整数 $len[1],\ldots,len[n]$,表示各请求的 token 数。

  • $1\le m\le 100000$。
  • $1\le k\le 1000$。
  • $1\le n\le 100000$。
  • $1\le len[i]\le 1000000$。

若某请求需要的 Block 数大于 $m$,它一定无法被接受。题目建议算法时间复杂度优于 $O(n^2)$。

输出描述

输出一个整数,表示最多可接受的请求数量。

样例 1

输入

3 10 3
25 5 15

输出

2

解释:Block 需求依次为 $3,1,2$。只保留请求 1 会占满内存,只能保留一个请求;跳过请求 1,接受请求 2、3,总占用为 $1+2=3$,可保留两个请求。

样例 2

输入

5 10 4
8 25 15 12

输出

3

解释:Block 需求依次为 $1,3,2,2$。选择请求 1、3、4,总占用 $1+2+2=5$,共接受三个请求。

解题思路:排序贪心

先区分计算顺序与请求到达顺序。 输入已经给出了全部请求,算法可以离线计算应该保留的集合;对需求排序只是求最优数量,并不是让系统重新排列到达顺序。

任取一个总需求不超过 $m$ 的集合 $S$,按照原输入顺序处理:属于 $S$ 的请求接受,其余拒绝。由于需求均为正数,任意前缀的占用都不超过整个集合的占用,因此过程始终可行。这甚至不需要执行撤销操作,也不会重新考虑被拒绝的请求。

问题因此等价于:从所有 Block 需求中选尽量多个数,使其和不超过 $m$。每个请求带来的收益都是“数量加一”,自然优先选择需求小的请求。

  1. 用整数式 (length + k - 1) // k 计算向上取整,不使用浮点数。
  2. 将全部需求升序排序。
  3. 从小到大累加;若当前需求放不下,则停止,因为后面的需求不会更小。

正确性证明

设排序后的需求为 $b_1\le b_2\le\cdots\le b_n$。

引理一: 任意满足容量限制的子集,都能按原到达顺序合法实现。证明如上:逐个接受集合内的请求,前缀需求和不会超过集合总需求和。

引理二: 任意包含 $t$ 个请求的集合,其需求和都不小于 $\sum_{i=1}^{t}b_i$。因为最小的 $t$ 个数在所有大小为 $t$ 的选择中取得最小总和。

若贪心接受了 $r$ 个请求后停止,前 $r$ 个需求和不超过 $m$,由引理一可实现。当 $r<n$ 时,前 $r+1$ 个需求和超过 $m$,由引理二,任何 $r+1$ 个请求都不可能同时保留。因此不存在更大的可行解。若 $r=n$,已经接受全部请求,同样最优。

Python ACM 代码

import sys


def max_accept(m, k, lengths):
    needs = sorted((length + k - 1) // k for length in lengths)
    used = 0
    answer = 0
    for need in needs:
        if used + need > m:
            break
        used += need
        answer += 1
    return answer


def main():
    data = list(map(int, sys.stdin.buffer.read().split()))
    if not data:
        return
    m, k, n = data[:3]
    print(max_accept(m, k, data[3:3 + n]))


if __name__ == "__main__":
    main()

复杂度分析

时间复杂度:$O(n\log n)$,排序占主导,计算需求和扫描均为 $O(n)$。

空间复杂度:$O(n)$,包括输入、需求数组以及 Python 排序使用的辅助空间。

本题小结

关键是证明到达顺序不影响最终可行集合,再使用“固定数量时取最小需求”的贪心。不必套容量背包,也不必模拟复杂的缓存管理。所有请求都放不下时输出 0;恰好装满时仍应计入最后一个请求。

第 2 题:OCR 智能纠错系统迭代训练和推理

题目描述

某智能终端搭载字符级 OCR 纠错系统,把“识别字符到真实字符”的纠错建模为朴素贝叶斯过程。

字符集为小写英文字母 ${a,b,\ldots,z}$,记 $K=26$。模型由两组统计量描述:

  • $emit[c][o]$:训练语料中,真实字符 $c$ 被 OCR 识别为 $o$ 的次数。
  • $prior[c]$:训练语料中,真实字符 $c$ 出现的次数。

对发射概率和先验概率使用加 $\alpha$ 拉普拉斯平滑,$\alpha$ 是正整数:

\[P(o\mid c)=\frac{emit[c][o]+\alpha} {\sum_{o'}emit[c][o']+\alpha K}, \qquad P(c)=\frac{prior[c]+\alpha} {\sum_{c'}prior[c']+\alpha K}.\]

对于待纠错字符串中的每一个 OCR 字符 $o$,独立选择使 $P(o\mid c)P(c)$ 最大的真实字符 $c$。如果多个候选取得相同最大值,选择字典序最小的字符。

除 $N$ 条带标注训练对外,还有 $M$ 条只有 OCR 输出、没有真实文本的未标注样本。初始模型仅由原始带标注样本统计得到,共进行 $T$ 轮自训练。每轮执行:

  1. 使用当前模型对全部 $M$ 条未标注样本解码,得到伪真实文本。
  2. 使用 $N$ 条原始标注样本,加上本轮的 $M$ 条“(OCR,伪真实)”训练对,重新统计模型。

每轮伪标注替换上一轮伪标注,不累积叠加;原始标注始终保留。完成 $T$ 轮后,用最终模型纠正测试 OCR 字符串并输出。

数值稳定性要求:比较候选时必须用整数交叉相乘,避免浮点误差。令

\[N_c=(emit[c][o]+\alpha)(prior[c]+\alpha), \qquad D_c=\sum_{o'}emit[c][o']+\alpha K.\]

先验概率的分母对所有候选相同,可以省略。两个候选的比较为

\[\frac{N_1}{D_1}>\frac{N_2}{D_2} \iff N_1D_2>N_2D_1.\]

输入描述与约束

第一行四个整数 $N,M,T,\alpha$,依次表示带标注样本数、未标注样本数、迭代轮数和平滑系数。

接下来 $N$ 行,每行两个等长字符串 $ocr_i,truth_i$,前者是 OCR 输出,后者是真实文本。

接下来 $M$ 行,每行一个未标注 OCR 字符串。最后一行是测试 OCR 字符串。

  • $1\le N\le 100$。
  • $0\le M\le 100$。
  • $0\le T\le 20$。
  • $1\le\alpha\le 10$。
  • 所有字符串仅含小写英文字母,长度满足 $1\le L\le 100$。
  • 每个标注对内的两个字符串长度相等,不要求不同样本之间等长。

输出描述

输出一行,表示测试 OCR 字符串经过最终模型纠正后的结果。

样例 1

输入

1 5 2 1
u i
v
v
v
v
v
v

输出

i

解释:标注对是 OCR u、真实字符 i。其后前五行 v 是未标注样本,最后一行 v 是测试串。初始模型对 v 没有发射观测;省略公共分母后,候选 i 的得分为 $2/27$,其余候选为 $1/26$,所以选 i。

第一轮将五个未标注 v 都标为 i,重建后 $emit[i][v]=5$、$prior[i]=6$。第二轮替换伪标注而非再次叠加,最终输出仍为 i。这里的判断同时考虑先验和发射分母,并非只比较先验计数。

样例 2

输入

2 0 0 1
axc abc
axd abd
ax

输出

ab

解释:没有未标注样本,也不进行迭代。训练集中 OCR 字符 x 两次对应真实字符 b,测试串的 x 被纠正为 b,a 保持为 a。

样例 3

输入

2 1 2 1
ax ab
ay ab
az
az

输出

aa

解释:初始模型中真实字符 a、b 都出现两次;对于没有观测过的 z,两者得分均为 $3/28$,高于其他候选,平局选择 a。第一轮把未标注样本 az 纠正为 aa 后重新计数,产生真实 a 被识别为 z 的观测。后续迭代保持这一预测,测试输出 aa。

解题思路:朴素贝叶斯计数与同步迭代

1. 统计方向不能颠倒

对一对 (ocr, truth),逐位置取真实字符 c 和识别字符 o,增加 emit[c][o] 与 prior[c]。再用 total[c] 记录该真实字符的发射总数。

在本题每个真实字符恰好对应一个 OCR 字符,所以 total[c] == prior[c]。代码保留两个数组,使发射分母和先验分子分别对应题面定义,便于核对。

2. 用整数比较候选得分

删去所有候选共有的先验分母后,得分是

\[score(c,o)=\frac{(emit[c][o]+\alpha)(prior[c]+\alpha)}{total[c]+\alpha K}.\]

由于 $\alpha>0$,分母始终为正。交叉相乘可以精确判断大于和相等,不需要对数、浮点数或误差阈值。

按 a 到 z 的顺序枚举候选,初始化最优候选为 a,仅在新分数严格更大时替换。相等时保留先遇到的候选,正好实现字典序规则。Python 整数可自动扩展;其他语言实现应使用足够宽的整数,不能直接用 32 位乘积。

3. 每个模型只建立一次字符映射

同一轮内,一个 OCR 字符的预测只由字符本身和当前模型决定,与其位置、所在字符串无关。因此可以先计算

\[table[o]=\text{当前模型对字符 }o\text{ 的最优预测}.\]

枚举 $K$ 个识别字符及 $K$ 个真实字符,建表需要 $O(K^2)$ 时间。之后解码任何字符串只需逐字符查表。

4. 所有伪标注必须来自同一个旧模型

先由原始标注建立初始统计量。每轮先固定当前映射表,用它解码全部未标注样本;全部解码完成后,将原始标注和本轮伪标注一起传入计数函数,从零建立新统计量。

不能解码一条就更新模型,让后续样本看到已经改变的统计量;也不能在上一轮统计量上直接累加,否则会重复计算旧伪标注。

完成循环后必须再用最终模型建表,不能直接拿最后一轮用于产生伪标注的旧表输出测试结果。$T=0$ 时直接使用初始模型;$M=0$ 时每轮仍只统计原始标注,模型不变。

正确性证明

引理一:计数函数正确。 每对训练字符串中的每个位置,恰好对题面定义的 $emit[c][o]$、$prior[c]$ 及发射总数贡献一次。遍历所有位置后得到的统计量与定义相同。

引理二:映射表符合解码规则。 省略的先验分母对所有候选相同且为正,不改变得分顺序;交叉相乘保持大小关系。枚举覆盖所有真实字符,只在严格更优时替换,因此最终得到最大分数及其中字典序最小的候选。逐字符查表与逐字符独立解码完全等价。

归纳证明迭代正确。 初始计数由原始标注建立,由引理一正确。假设某轮开始时模型与规定一致,由引理二,本轮统一生成的所有伪标注正确;随后从原始标注和本轮伪标注重新统计,由引理一得到规定的新模型,且没有上一轮伪标注残留。归纳可得完成 $T$ 轮后的模型正确,再由引理二可得测试输出正确。

Python ACM 代码

import sys

K = 26


def build_counts(pairs):
    emit = [[0] * K for _ in range(K)]
    total = [0] * K
    prior = [0] * K
    for ocr, truth in pairs:
        for observed, actual in zip(ocr, truth):
            o = ord(observed) - ord('a')
            c = ord(actual) - ord('a')
            emit[c][o] += 1
            total[c] += 1
            prior[c] += 1
    return emit, total, prior


def build_table(emit, total, prior, alpha):
    table = [0] * K
    for o in range(K):
        best = 0
        best_num = (emit[0][o] + alpha) * (prior[0] + alpha)
        best_den = total[0] + alpha * K
        for c in range(1, K):
            num = (emit[c][o] + alpha) * (prior[c] + alpha)
            den = total[c] + alpha * K
            if num * best_den > best_num * den:
                best = c
                best_num = num
                best_den = den
        table[o] = best
    return table


def decode(table, text):
    return ''.join(
        chr(ord('a') + table[ord(ch) - ord('a')])
        for ch in text
    )


def self_train(labeled, unlabeled, rounds, alpha):
    emit, total, prior = build_counts(labeled)
    for _ in range(rounds):
        table = build_table(emit, total, prior, alpha)
        pseudo = [(ocr, decode(table, ocr)) for ocr in unlabeled]
        # 从零统计:仅保留原始标注与本轮伪标注。
        emit, total, prior = build_counts(labeled + pseudo)
    return build_table(emit, total, prior, alpha)


def main():
    tokens = iter(sys.stdin.read().split())
    first = next(tokens, None)
    if first is None:
        return
    n = int(first)
    m = int(next(tokens))
    rounds = int(next(tokens))
    alpha = int(next(tokens))
    labeled = [(next(tokens), next(tokens)) for _ in range(n)]
    unlabeled = [next(tokens) for _ in range(m)]
    test = next(tokens)
    table = self_train(labeled, unlabeled, rounds, alpha)
    print(decode(table, test))


if __name__ == "__main__":
    main()

复杂度分析

记 $A$ 为原始标注中 OCR 字符串的总长度,$U$ 为未标注字符串的总长度,$Q$ 为测试串长度,字符集大小 $K=26$。

  • 初始计数:$O(A+K^2)$。
  • 每轮:建表 $O(K^2)$,生成伪标注 $O(U)$,重新计数 $O(A+U+K^2)$。
  • 最终建表与输出:$O(K^2+Q)$。

总时间复杂度为

\[O\bigl(A+K^2+T(A+U+K^2)+Q\bigr).\]

以最大字符串长度 $L$ 表示,上界也可写为

\[O\bigl(NL+(T+1)K^2+T(N+M)L+L\bigr).\]

这包含 $T=0$ 时仍需执行的初始计数和最终建表,不能把所有预处理都放进乘以 $T$ 的项中。

空间复杂度为 $O(A+U+Q+K^2)$,包括输入字符串、当前轮伪标注和计数表;每轮旧伪标注被替换,不随轮数线性增长。

本题小结

实现重点有四个:emit[真实][识别] 的方向、整数交叉相乘、相等时不替换、每轮重新计数。映射表把同字符的重复预测合并,既降低开销,也便于保证本轮所有伪标注使用同一个模型。

总结

这场编程题不要求调用机器学习库。第一题把 KV Cache 管理抽象为等收益的容量选择,用排序贪心解决;第二题把概率公式落实为计数、精确比较和同步迭代。审题时先检查可行性与状态更新的含义,再处理边界条件,比围绕业务背景搭建复杂系统更直接。