Skip to content
🔗 分享本题
查看我的学习进度 →

岗位特点

  • 侧重实战能力和工程落地
  • 重视LLM/RAG/Agent技术栈
  • 喜欢问开放性问题
  • 重视代码实现能力

高频面试题

1. LLM基础篇

字节 Q1:Transformer 自注意力机制如何工作?

Transformer自注意力从QKV映射到加权汇总的完整机制图解

🧠 图解记忆: Q 找相关 K,再按权重汇总 V;多头注意力让模型同时观察不同关系。

💡 答案要点

题目: 请详细解释Transformer模型中的self-attention机制是如何工作的?为什么它比RNN更适合处理长序列?

答案要点:

Self-Attention工作原理:

展开 Python 代码示例(37 行)
python
def self_attention(Q, K, V):
    """
    Q: Query矩阵 (batch, seq_len, d_k)
    K: Key矩阵 (batch, seq_len, d_k)
    V: Value矩阵 (batch, seq_len, d_v)
    """
    # 1. 计算注意力分数
    # Attention(Q,K,V) = softmax(QK^T / sqrt(d_k)) * V

    d_k = Q.shape[-1]

    # 2. Q和K做点积
    scores = torch.matmul(Q, K.transpose(-2, -1))  # (batch, seq_len, seq_len)

    # 3. 缩放(避免点积过大导致 Softmax 饱和)
    scores = scores / math.sqrt(d_k)

    # 4. Softmax归一化
    attention_weights = F.softmax(scores, dim=-1)

    # 5. 加权求和V
    output = torch.matmul(attention_weights, V)

    return output, attention_weights

# 示例
seq_len = 5
d_model = 512
d_k = d_v = 64

Q = torch.randn(1, seq_len, d_k)
K = torch.randn(1, seq_len, d_k)
V = torch.randn(1, seq_len, d_v)

output, weights = self_attention(Q, K, V)
print(output.shape)  # (1, 5, 64)
print(weights.shape)  # (1, 5, 5) - 注意力矩阵

为什么比RNN好:

维度RNNSelf-Attention
并行性❌ 串行计算✅ 完全并行
长依赖❌ 梯度消失✅ 直接连接
计算复杂度O(n·d²)O(n²·d)
长文本❌ 信息丢失✅ 全局视野

面试话术:

"Self-Attention的核心是让每个词都能直接看到序列中的所有其他词。计算分3步:Q和K点积得分数,Softmax归一化,加权求和V。相比RNN,它最大优势是并行计算和直接的长距离依赖,不会梯度消失。代价是O(n²)复杂度,所以超长文本需要优化如FlashAttention。"

字节 Q2:位置编码为什么必要?绝对位置、RoPE、ALiBi 有何区别?

绝对位置编码RoPE和ALiBi机制与外推能力对比图解

🧠 图解记忆: 绝对位置做加法,RoPE 旋转 Q/K,ALiBi 在注意力分数里给远距离扣分。

💡 答案要点

题目: 什么是位置编码?为什么Transformer必需它?请列举至少两种实现方式并对比。

答案要点:

为什么需要位置编码:

  • Self-Attention是排列不变的(permutation-invariant)
  • 没有位置信息,"我爱你"和"你爱我"的表示完全相同
  • 位置编码注入顺序信息

方式1: 正弦位置编码(原始Transformer)

展开 Python 代码示例(30 行)
python
def sinusoidal_positional_encoding(seq_len, d_model):
    """
    PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
    PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
    """
    position = torch.arange(seq_len).unsqueeze(1)  # (seq_len, 1)
    div_term = torch.exp(
        torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)
    )

    pe = torch.zeros(seq_len, d_model)
    pe[:, 0::2] = torch.sin(position * div_term)  # 偶数位置
    pe[:, 1::2] = torch.cos(position * div_term)  # 奇数位置

    return pe

# 使用
seq_len = 100
d_model = 512
pe = sinusoidal_positional_encoding(seq_len, d_model)

# 可视化
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.imshow(pe[:50, :50], cmap='RdBu', aspect='auto')
plt.xlabel("Dimension")
plt.ylabel("Position")
plt.colorbar()
plt.title("Sinusoidal Positional Encoding")
plt.show()

方式2: 可学习位置编码(BERT)

python
class LearnedPositionalEncoding(nn.Module):
    def __init__(self, max_seq_len, d_model):
        super().__init__()
        # 直接学习一个位置Embedding矩阵
        self.pos_embedding = nn.Embedding(max_seq_len, d_model)

    def forward(self, x):
        seq_len = x.size(1)
        positions = torch.arange(seq_len, device=x.device)
        return x + self.pos_embedding(positions)

# 使用
model = LearnedPositionalEncoding(max_seq_len=512, d_model=768)
x = torch.randn(1, 100, 768)
output = model(x)

方式3: 旋转位置编码(RoPE) - 字节高频考点

python
def rotate_half(x):
    """旋转一半的维度"""
    x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]
    return torch.cat((-x2, x1), dim=-1)

def apply_rotary_pos_emb(q, k, cos, sin):
    """应用RoPE"""
    # q, k: (batch, seq_len, num_heads, head_dim)
    q_embed = (q * cos) + (rotate_half(q) * sin)
    k_embed = (k * cos) + (rotate_half(k) * sin)
    return q_embed, k_embed

# RoPE的优势:
# 1. 相对位置信息(query和key之间的距离)
# 2. 外推性好(训练512可推理1024+)
# 3. 不增加参数

对比表:

方法优点缺点使用
正弦编码无参数,外推性好固定模式GPT-3
可学习编码灵活,可适应数据超出已训练位置通常需要扩展并继续训练BERT
RoPE相对位置,外推性强实现复杂LLaMA

面试话术:

"不含位置编码的 Self-Attention 无法单独表达 token 顺序。Sin/Cos 编码无训练参数且可以计算任意位置;可学习位置向量在训练范围内灵活;RoPE 让注意力分数携带相对位置信息。任何方案在训练长度之外都需要专项评测,不能直接宣称外推最好。"

字节 Q3:MHA、MQA、GQA 有什么区别?

MHA独立KVMQA全共享GQA分组共享及KVCache取舍图解

🧠 图解记忆: MHA 各用各的,MQA 全部共享,GQA 分组共享,在质量和 KV Cache 成本间折中。

💡 答案要点

题目: 请解释Multi-Head Attention (MHA)、Multi-Query Attention (MQA)、Grouped-Query Attention (GQA)的区别。

答案要点:

MHA (Multi-Head Attention) - 标准方法

展开 Python 代码示例(37 行)
python
class MultiHeadAttention(nn.Module):
    def __init__(self, d_model=512, num_heads=8):
        super().__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads

        # 每个head都有独立的Q、K、V
        self.W_q = nn.Linear(d_model, d_model)  # 8个head,每个64维
        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, x):
        batch_size, seq_len, d_model = x.shape

        # 1. 线性变换
        Q = self.W_q(x)  # (batch, seq_len, d_model)
        K = self.W_k(x)
        V = self.W_v(x)

        # 2. 分割成多个head
        Q = Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        K = K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        V = V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        # 形状: (batch, num_heads, seq_len, d_k)

        # 3. Attention
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        attn = F.softmax(scores, dim=-1)
        output = torch.matmul(attn, V)

        # 4. 拼接
        output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
        return self.W_o(output)

# KV Cache大小: batch * seq_len * num_heads * head_dim * 2 (K和V)
# 8个head,每个64维 → 1024 tokens需要 1*1024*8*64*2*2字节 = 2MB (FP16)

MQA (Multi-Query Attention) - Google提出

展开 Python 代码示例(30 行)
python
class MultiQueryAttention(nn.Module):
    def __init__(self, d_model=512, num_heads=8):
        super().__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads

        # Q有多个head,但K和V只有1个(共享)
        self.W_q = nn.Linear(d_model, d_model)  # 8个head
        self.W_k = nn.Linear(d_model, self.d_k)  # 只1个head!
        self.W_v = nn.Linear(d_model, self.d_k)  # 只1个head!
        self.W_o = nn.Linear(d_model, d_model)

    def forward(self, x):
        batch_size, seq_len, d_model = x.shape

        Q = self.W_q(x).view(batch, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        K = self.W_k(x).unsqueeze(1)  # (batch, 1, seq_len, d_k) - 广播给所有head
        V = self.W_v(x).unsqueeze(1)  # (batch, 1, seq_len, d_k)

        # 所有Q head共享同一个K和V
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        attn = F.softmax(scores, dim=-1)
        output = torch.matmul(attn, V)

        output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
        return self.W_o(output)

# KV Cache大小: batch * seq_len * 1 * head_dim * 2
# 只1个head → 1024 tokens需要 1*1024*1*64*2*2字节 = 256KB (FP16)
# 相比MHA减少8倍!

GQA (Grouped-Query Attention) - LLaMA 2

展开 Python 代码示例(37 行)
python
class GroupedQueryAttention(nn.Module):
    def __init__(self, d_model=512, num_heads=8, num_kv_heads=2):
        super().__init__()
        self.num_heads = num_heads
        self.num_kv_heads = num_kv_heads  # K/V的head数(比Q少)
        self.num_queries_per_kv = num_heads // num_kv_heads  # 每个KV对应几个Q
        self.d_k = d_model // num_heads

        self.W_q = nn.Linear(d_model, d_model)  # 8个head
        self.W_k = nn.Linear(d_model, num_kv_heads * self.d_k)  # 2个head
        self.W_v = nn.Linear(d_model, num_kv_heads * self.d_k)  # 2个head
        self.W_o = nn.Linear(d_model, d_model)

    def forward(self, x):
        batch_size, seq_len, d_model = x.shape

        Q = self.W_q(x).view(batch, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        # (batch, 8, seq_len, 64)

        K = self.W_k(x).view(batch, seq_len, self.num_kv_heads, self.d_k).transpose(1, 2)
        V = self.W_v(x).view(batch, seq_len, self.num_kv_heads, self.d_k).transpose(1, 2)
        # (batch, 2, seq_len, 64)

        # 重复K和V,让每个KV对应4个Q
        K = K.repeat_interleave(self.num_queries_per_kv, dim=1)  # (batch, 8, seq_len, 64)
        V = V.repeat_interleave(self.num_queries_per_kv, dim=1)

        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        attn = F.softmax(scores, dim=-1)
        output = torch.matmul(attn, V)

        output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
        return self.W_o(output)

# KV Cache大小: batch * seq_len * num_kv_heads * head_dim * 2
# 2个KV head → 1024 tokens需要 1*1024*2*64*2*2字节 = 512KB
# 是MHA的1/4,是MQA的2倍

对比总结:

方法Q headsK/V headsKV Cache质量速度使用
MHA882MB⭐⭐⭐⭐⭐⭐⭐GPT-3
MQA81256KB⭐⭐⭐⭐⭐⭐⭐⭐PaLM
GQA82512KB⭐⭐⭐⭐⭐⭐⭐⭐LLaMA 2

面试话术:

"MHA是标准的多头注意力,每个head都有独立的Q、K、V,质量最好但KV Cache大。MQA让所有head共享1个K和V,Cache减少8倍推理快,但质量略降。GQA是折中方案,8个Q head对应2个KV head,Cache减少4倍,质量接近MHA。LLaMA 2用GQA达到性能和效率平衡,字节面试常问这个演进逻辑。"

2. RAG系统篇

字节 Q4:如何设计完整的生产级 RAG 流程?

生产级RAG离线知识加工在线检索重排生成评估完整流程图解

🧠 图解记忆: 离线建好可检索知识,在线先找准、再排好,最后基于证据生成并持续评估。

💡 答案要点

题目: 设计一个完整的RAG系统,从数据准备到最终生成,详细描述每个步骤。

答案要点:

RAG完整流程(7步):

展开 Python 代码示例(106 行)
python
class RAGSystem:
    def __init__(self):
        self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
        self.vectordb = Qdrant(...)
        self.llm = ChatOpenAI(model="qwen3.5-flash")
        self.reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')

    def build_knowledge_base(self, documents):
        """Step 1-3: 构建知识库"""

        # Step 1: 文档加载
        from langchain.document_loaders import PyPDFLoader, TextLoader

        docs = []
        for doc_path in documents:
            if doc_path.endswith('.pdf'):
                loader = PyPDFLoader(doc_path)
            else:
                loader = TextLoader(doc_path)
            docs.extend(loader.load())

        # Step 2: 文档切块(Chunking)
        from langchain.text_splitter import RecursiveCharacterTextSplitter

        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=500,      # 每块500字符
            chunk_overlap=50,    # 重叠50字符(保持上下文)
            separators=["\n\n", "\n", "。", "!", "?", " ", ""]
        )

        chunks = text_splitter.split_documents(docs)
        print(f"切分成{len(chunks)}个chunk")

        # Step 3: Embedding + 存入向量库
        texts = [chunk.page_content for chunk in chunks]
        embeddings = self.embedding_model.encode(texts)

        self.vectordb.add(
            embeddings=embeddings,
            documents=texts,
            metadatas=[chunk.metadata for chunk in chunks]
        )

    def retrieve_and_generate(self, query):
        """Step 4-7: 检索+生成"""

        # Step 4: Query Embedding
        query_embedding = self.embedding_model.encode([query])[0]

        # Step 5: 向量检索(召回Top-K)
        results = self.vectordb.search(
            query_embedding,
            limit=20  # 先召回20个候选
        )

        # Step 6: Rerank(精排)
        candidate_docs = [r['document'] for r in results]

        # 计算Query与每个Doc的相关性分数
        pairs = [[query, doc] for doc in candidate_docs]
        scores = self.reranker.predict(pairs)

        # 按分数排序,取Top-5
        ranked_results = sorted(
            zip(candidate_docs, scores),
            key=lambda x: x[1],
            reverse=True
        )[:5]

        top_docs = [doc for doc, score in ranked_results]

        # Step 7: LLM生成
        context = "\n\n".join([f"文档{i+1}: {doc}" for i, doc in enumerate(top_docs)])

        prompt = f"""
        请基于以下文档回答问题。如果文档中没有答案,请明确说"文档中未找到相关信息"。

        文档:
        {context}

        问题: {query}

        回答(要求引用文档编号):
        """

        answer = self.llm.invoke(prompt).content

        return {
            "answer": answer,
            "sources": top_docs,
            "num_retrieved": len(results)
        }

# 使用
rag = RAGSystem()

# 构建知识库
rag.build_knowledge_base([
    "company_docs/产品手册.pdf",
    "company_docs/FAQ.txt"
])

# 查询
result = rag.retrieve_and_generate("如何退货?")
print(result["answer"])
print(f"引用了{len(result['sources'])}个文档")

关键优化点:

  1. Chunking策略

    • 固定长度(500字符) + 重叠(50字符)
    • 按语义分割(段落/句子优先)
    • 代码块/表格特殊处理
  2. 混合检索

    python
    # 向量检索 + BM25关键词检索
    from rank_bm25 import BM25Okapi
    
    # BM25检索
    bm25_results = bm25_search(query, top_k=20)
    
    # 向量检索
    vector_results = vector_search(query, top_k=20)
    
    # RRF融合
    final_results = reciprocal_rank_fusion([bm25_results, vector_results])
  3. 元数据过滤

    python
    # 按时间/部门/权限过滤
    results = vectordb.search(
        query_embedding,
        filter={
            "department": "销售部",
            "created_at": {"$gte": "2024-01-01"}
        }
    )

面试话术:

示例表达(仅在能用本人经历或可复现实验佐证时使用): "RAG分7步:1)文档加载2)切块(500字符+50重叠)3)Embedding存向量库4)Query编码5)向量检索Top-20 6)Rerank精排Top-5 7)LLM生成。关键优化3点:切块策略要保留语义完整性,混合检索(向量+BM25)提升召回,Rerank用CrossEncoder提升精度。实测Rerank让答案准确率从70%→85%提升15%。"

3. Agent实战篇

字节 Q5:如何用 ReAct 实现代码审查 Agent?

ReAct代码审查Agent使用工具取证并输出结构化问题的循环图解

🧠 图解记忆: ReAct 不是边想边猜,而是用工具取证,再按代码与测试证据完成审查。

💡 答案要点

题目: 用ReAct框架实现一个代码审查Agent,能自动发现代码问题并给出修复建议。

答案要点:

展开 Python 代码示例(43 行)
python
from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent
from langchain.prompts import StringPromptTemplate
from langchain.llms import OpenAI
from langchain.chains import LLMChain
import ast
import subprocess

class CodeReviewAgent:
    def __init__(self):
        self.llm = OpenAI(temperature=0)

        # 定义工具
        self.tools = [
            Tool(
                name="静态代码分析",
                func=self.static_analysis,
                description="用pylint分析代码质量问题"
            ),
            Tool(
                name="安全漏洞扫描",
                func=self.security_scan,
                description="检测SQL注入、XSS等安全问题"
            ),
            Tool(
                name="性能分析",
                func=self.performance_analysis,
                description="分析时间复杂度和性能瓶颈"
            ),
            Tool(
                name="代码修复建议",
                func=self.fix_suggestion,
                description="给出具体的修复代码"
            )
        ]

        # ReAct Prompt模板
        self.prompt_template = """
        你是一个专业的代码审查专家。请按照ReAct模式分析代码。

        可用工具:
        {tools}

        代码:
        ```python
{code} ```
    请按以下格式思考和行动:

    Thought: 我需要分析这段代码的问题
    Action: 静态代码分析
    Observation: [工具返回结果]

    Thought: 发现了XX问题,需要进一步检查安全性
    Action: 安全漏洞扫描
    Observation: [工具返回结果]

    ...继续思考和行动,直到完成审查...

    Final Answer: [完整的审查报告]

    开始:
    Thought: {agent_scratchpad}
    """

    self.agent = self._create_agent()

def static_analysis(self, code):
    """静态代码分析"""
    # 保存代码到临时文件
    with open("/tmp/code_review.py", "w") as f:
        f.write(code)

    # 运行pylint
    result = subprocess.run(
        ["pylint", "/tmp/code_review.py"],
        capture_output=True,
        text=True
    )

    return result.stdout

def security_scan(self, code):
    """安全漏洞扫描"""
    issues = []

    # 检测SQL注入
    if "execute(" in code and "%" in code:
        issues.append("⚠️  可能存在SQL注入风险: 使用字符串拼接构建SQL")

    # 检测硬编码密钥
    if "password" in code.lower() or "api_key" in code.lower():
        issues.append("⚠️  发现硬编码的敏感信息")

    # 检测eval/exec
    if "eval(" in code or "exec(" in code:
        issues.append("🚨 危险: 使用了eval/exec,可能导致代码注入")

    return "\n".join(issues) if issues else "✅ 未发现明显安全问题"

def performance_analysis(self, code):
    """性能分析"""
    try:
        tree = ast.parse(code)

        issues = []

        # 检测嵌套循环
        for node in ast.walk(tree):
            if isinstance(node, ast.For):
                for child in ast.walk(node):
                    if isinstance(child, ast.For) and child != node:
                        issues.append("⚠️  发现嵌套循环,时间复杂度可能是O(n²)")

        # 检测list comprehension vs 循环
        has_list_comp = any(isinstance(node, ast.ListComp) for node in ast.walk(tree))
        if not has_list_comp:
            issues.append("💡 建议: 可以使用列表推导式提升性能")

        return "\n".join(issues) if issues else "✅ 未发现明显性能问题"

    except:
        return "⚠️  代码语法错误,无法分析"

def fix_suggestion(self, issue):
    """生成修复建议"""
    prompt = f"""
    代码问题: {issue}

    请给出具体的修复代码示例(Python):
    """

    return self.llm(prompt)

def review(self, code):
    """执行代码审查"""
    result = self.agent_executor.run(code=code)
    return result

使用示例

agent = CodeReviewAgent()

code_to_review = """ def get_user(user_id): # SQL注入风险 query = f"SELECT * FROM users WHERE id = '{user_id}'" result = db.execute(query)

# 硬编码密钥
api_key = "sk-1234567890abcdef"

# 嵌套循环
for item1 in list1:
    for item2 in list2:
        if item1 == item2:
            result.append(item1)

return result

"""

report = agent.review(code_to_review) print(report)

输出示例:

""" 代码审查报告:

  1. 安全问题:

    • 🚨 SQL注入风险: 直接拼接用户输入到SQL语句
    • ⚠️ 硬编码API密钥
  2. 性能问题:

    • ⚠️ 嵌套循环导致O(n²)复杂度
    • 💡 可以用集合交集优化
  3. 修复建议:

python
def get_user(user_id):
    # 修复SQL注入 - 使用参数化查询
    query = "SELECT * FROM users WHERE id = ?"
    result = db.execute(query, (user_id,))

    # 修复硬编码 - 使用环境变量
    api_key = os.getenv("API_KEY")

    # 修复性能 - 使用集合交集
    result = list(set(list1) & set(list2))

    return result
  1. 质量评分: 6/10 建议: 在上线前解决所有安全问题 """

**ReAct关键点:**

1. **Thought(思考):** Agent分析当前情况
2. **Action(行动):** 选择合适的工具执行
3. **Observation(观察):** 获取工具返回结果
4. **循环:** 重复直到得出最终答案

**面试话术:**
> **示例表达(仅在能用本人经历或可复现实验佐证时使用):** "我用ReAct实现了代码审查Agent。定义了4个工具:静态分析/安全扫描/性能分析/修复建议。Agent按Thought→Action→Observation循环工作,先静态分析发现问题,再安全扫描检测漏洞,最后性能分析找瓶颈。关键是工具设计要专注单一职责,Prompt要清晰引导推理过程。实测能发现90%常见代码问题。"