手写 Multi-Head Attention。

考查重点:多头张量形状与掩码计算。Q/K/V投影后拆头,用QKᵀ/√dₖ加需要的mask并做softmax,再乘V、拼头和输出投影;核对形状与全掩码处理

本题目录

参考答案

核心回答

MHA先将输入分别投影为Q、K、V,拆成多个头;每个头计算softmax(QKᵀ/√d_k)V,再拼接各头并通过输出投影。下面用PyTorch实现batch-first自注意力,包含布尔注意力掩码和key padding掩码;为突出机制,省略dropout、KV缓存及残差/归一化。

PyTorch实现

输入x形状为[B,T,D],D必须能被头数H整除。这里两类掩码都约定True表示禁止关注:attn_mask为[T,T],key_padding_mask为[B,T]。每个query至少保留一个可见key,全遮蔽行明确报错。

import math
import torch
from torch import nn

class MultiHeadSelfAttention(nn.Module):
    def __init__(self, d_model: int, num_heads: int):
        super().__init__()
        if d_model <= 0 or num_heads <= 0 or d_model % num_heads:
            raise ValueError("d_model必须为正且能被正num_heads整除")
        self.d_model = d_model
        self.num_heads = num_heads
        self.head_dim = d_model // num_heads
        self.qkv = nn.Linear(d_model, 3 * d_model)
        self.out = nn.Linear(d_model, d_model)

    def forward(self, x, attn_mask=None, key_padding_mask=None):
        if x.ndim != 3 or x.shape[-1] != self.d_model or x.shape[1] == 0:
            raise ValueError("x应为非空序列[B,T,D]")
        batch, length, _ = x.shape
        q, k, v = self.qkv(x).chunk(3, dim=-1)
        def split(t):
            return t.reshape(batch, length, self.num_heads,
                             self.head_dim).transpose(1, 2)
        q, k, v = split(q), split(k), split(v)
        scores = q @ k.transpose(-2, -1) / math.sqrt(self.head_dim)
        blocked = torch.zeros((batch, 1, length, length),
                              dtype=torch.bool, device=x.device)
        if attn_mask is not None:
            if attn_mask.dtype != torch.bool or attn_mask.shape != (length, length):
                raise ValueError("attn_mask须为布尔[T,T]")
            blocked = blocked | attn_mask.to(x.device)[None, None]
        if key_padding_mask is not None:
            if (key_padding_mask.dtype != torch.bool or
                    key_padding_mask.shape != (batch, length)):
                raise ValueError("key_padding_mask须为布尔[B,T]")
            blocked = blocked | key_padding_mask.to(x.device)[:, None, None, :]
        if blocked.all(dim=-1).any():
            raise ValueError("每个query至少需要一个可见key")
        weights = scores.masked_fill(blocked, float("-inf")).softmax(dim=-1)
        heads = weights @ v
        merged = heads.transpose(1, 2).contiguous().reshape(batch, length, self.d_model)
        return self.out(merged), weights

维度与容易写错的位置

投影后Q/K/V各为[B,T,D],分头变成[B,H,T,D/H],分数和权重为[B,H,T,T]。softmax沿最后的key维做;加权V后回到[B,H,T,D/H],再还原[B,T,D],最后仍需输出投影W_O。

例如B=2、T=5、D=12、H=3,每头维度4,缩放除以2,分数形状是[2,3,5,5]。拼头时先交换回T和H的次序,再整理内存,不能直接把原[B,H,T,d]按[B,T,D]解释。

因果掩码把主对角线上方设为True,禁止读取未来。key padding掩码屏蔽的是被关注的key列,不会自动把padding query的输出清零;训练时还需相应损失掩码。不同PyTorch API对布尔掩码的约定可能不同,不能机械复用。

复杂度与扩展

对长度T、隐藏维度D,线性投影约O(BTD²),注意力约O(BT²D);显式权重占O(BHT²)空间。交叉注意力改为从一组输入产生Q、另一组输入产生K/V,并允许query与key长度不同。生产中通常使用已验证的融合实现;这段代码用于展示机制,不代表最快内核。

对应教程

参考资料

相关面经

6 篇

字节跳动

大模型实习算法岗
实习
查看原面经 →
手撕MHA

百度

多模态大模型
一面
查看原面经 →
手撕:MHA

小鹏、卓驭、百度、Momenta、华为、美团、零跑汽车、经纬恒润、长安汽车、智驾新程、三一重工

面经合集校招
查看原面经 →
其次是Attention和Transformer。Attention is All you need。伟大无需多言,经典的要能手撕,尤其是MHA,如果你用了不同的att变种,关键改进点要会手撕,大部分考察都是变量的计算过程和维度变化,少部分考察要用torch来写,个人感觉做到大部分差不多。

腾讯

大模型算法岗
一面、二面
查看原面经 →
手撕:手写多头注意力(Multi-Head Attention)

百度

端到端决策规划控制算法工程师
一面
查看原面经 →
a MHA
追问
追问:用torch写下
展开剩余 1 篇面经

阿里巴巴

Agent 开发工程师
一面
查看原面经 →
手撕 MHA

收录题集

5 个来源,共12 份题集

AI 工程编码

AMIT SHEKHAR · ai-engineering-interview-questions · 1 条原题

查看题集 →

大模型手写代码题

Chaofa Yuan · AI-Interview-Code · 1 条原题

查看题集 →

训练、数据与模型优化:从数据清洗到 LoRA

Onefly · zero2Agent · 1 条原题

查看题集 →

编程实战面试题

阿东玩 AI · AgentGuide · 3 条原题

查看题集 →

编码题专题汇编

阿东玩 AI · AgentGuide · 5 条原题

查看题集 →
展开其余 2 条原题
展开剩余 7 份题集

大模型手写代码练习路线

阿东玩 AI · AgentGuide · 2 条原题

查看题集 →

公司案例问答汇编

阿东玩 AI · AgentGuide · 1 条原题

查看题集 →

多模态与计算机视觉

阿东玩 AI · AgentGuide · 1 条原题

查看题集 →

深度学习基础汇编

阿东玩 AI · AgentGuide · 9 条原题

查看题集 →
展开其余 6 条原题

百度高频面试题

Rocky Ding · AIGC-Interview-Book · 1 条原题

查看题集 →

DeepSeek高频面试题

Rocky Ding · AIGC-Interview-Book · 1 条原题

查看题集 →

爱奇艺高频面试题

Rocky Ding · AIGC-Interview-Book · 1 条原题

查看题集 →

相关题目

4 题