岗位特点
- 侧重实战能力和工程落地
- 重视LLM/RAG/Agent技术栈
- 喜欢问开放性问题
- 重视代码实现能力
高频面试题
1. LLM基础篇
字节 Q1:Transformer 自注意力机制如何工作?
🧠 图解记忆: Q 找相关 K,再按权重汇总 V;多头注意力让模型同时观察不同关系。
💡 答案要点
题目: 请详细解释Transformer模型中的self-attention机制是如何工作的?为什么它比RNN更适合处理长序列?
答案要点:
Self-Attention工作原理:
展开 Python 代码示例(37 行)
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好:
| 维度 | RNN | Self-Attention |
|---|---|---|
| 并行性 | ❌ 串行计算 | ✅ 完全并行 |
| 长依赖 | ❌ 梯度消失 | ✅ 直接连接 |
| 计算复杂度 | O(n·d²) | O(n²·d) |
| 长文本 | ❌ 信息丢失 | ✅ 全局视野 |
面试话术:
"Self-Attention的核心是让每个词都能直接看到序列中的所有其他词。计算分3步:Q和K点积得分数,Softmax归一化,加权求和V。相比RNN,它最大优势是并行计算和直接的长距离依赖,不会梯度消失。代价是O(n²)复杂度,所以超长文本需要优化如FlashAttention。"
字节 Q2:位置编码为什么必要?绝对位置、RoPE、ALiBi 有何区别?
🧠 图解记忆: 绝对位置做加法,RoPE 旋转 Q/K,ALiBi 在注意力分数里给远距离扣分。
💡 答案要点
题目: 什么是位置编码?为什么Transformer必需它?请列举至少两种实现方式并对比。
答案要点:
为什么需要位置编码:
- Self-Attention是排列不变的(permutation-invariant)
- 没有位置信息,"我爱你"和"你爱我"的表示完全相同
- 位置编码注入顺序信息
方式1: 正弦位置编码(原始Transformer)
展开 Python 代码示例(30 行)
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)
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) - 字节高频考点
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 各用各的,MQA 全部共享,GQA 分组共享,在质量和 KV Cache 成本间折中。
💡 答案要点
题目: 请解释Multi-Head Attention (MHA)、Multi-Query Attention (MQA)、Grouped-Query Attention (GQA)的区别。
答案要点:
MHA (Multi-Head Attention) - 标准方法
展开 Python 代码示例(37 行)
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 行)
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 行)
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 heads | K/V heads | KV Cache | 质量 | 速度 | 使用 |
|---|---|---|---|---|---|---|
| MHA | 8 | 8 | 2MB | ⭐⭐⭐⭐⭐ | ⭐⭐ | GPT-3 |
| MQA | 8 | 1 | 256KB | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | PaLM |
| GQA | 8 | 2 | 512KB | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 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完整流程(7步):
展开 Python 代码示例(106 行)
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'])}个文档")关键优化点:
Chunking策略
- 固定长度(500字符) + 重叠(50字符)
- 按语义分割(段落/句子优先)
- 代码块/表格特殊处理
混合检索
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])元数据过滤
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 不是边想边猜,而是用工具取证,再按代码与测试证据完成审查。
💡 答案要点
题目: 用ReAct框架实现一个代码审查Agent,能自动发现代码问题并给出修复建议。
答案要点:
展开 Python 代码示例(43 行)
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 请按以下格式思考和行动:
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)
输出示例:
""" 代码审查报告:
安全问题:
- 🚨 SQL注入风险: 直接拼接用户输入到SQL语句
- ⚠️ 硬编码API密钥
性能问题:
- ⚠️ 嵌套循环导致O(n²)复杂度
- 💡 可以用集合交集优化
修复建议:
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- 质量评分: 6/10 建议: 在上线前解决所有安全问题 """
**ReAct关键点:**
1. **Thought(思考):** Agent分析当前情况
2. **Action(行动):** 选择合适的工具执行
3. **Observation(观察):** 获取工具返回结果
4. **循环:** 重复直到得出最终答案
**面试话术:**
> **示例表达(仅在能用本人经历或可复现实验佐证时使用):** "我用ReAct实现了代码审查Agent。定义了4个工具:静态分析/安全扫描/性能分析/修复建议。Agent按Thought→Action→Observation循环工作,先静态分析发现问题,再安全扫描检测漏洞,最后性能分析找瓶颈。关键是工具设计要专注单一职责,Prompt要清晰引导推理过程。实测能发现90%常见代码问题。"



