作业帮 大模型算法 一面(暑期)

1. 自我介绍

2. 介绍一个你做得比较完整、个人成长最多的项目,背景、目标、方案和效果是什么?

3. 做这个项目时具体用了哪些方案或手段达成目标?

答案:最核心的是把问题拆开,没有直接把所有文档塞给大模型。文档侧先做结构化解析,保留标题层级、版本号、适用部门、发布时间和权限标签;检索侧用了 BM25 和向量召回融合,因为制度类问题经常包含专有名词、系统名和缩写,单纯向量召回容易漏;生成侧强制模型基于证据回答,不允许没有证据时编造。

训练侧做了两类任务,一类是意图分类和工单类型预测,用历史工单标题、正文、处理队列作为弱标签,再人工抽检;另一类是指令微调样本,用高质量问答、证据片段和标准处理建议构造。线上 badcase 会回流到评测集和训练集里,不会直接只改 prompt。这个项目里提升最大的不是换模型,而是把检索、证据约束和数据闭环做扎实。

4. 整个系统的技术链路是怎样的?

答案:系统链路可以分为离线链路和在线链路。离线链路负责知识入库,包括文档抓取、OCR、正文抽取、表格解析、标题树构建、chunk 切分、embedding 计算、倒排索引和向量索引构建。在线链路从用户 query 进来后,先做 query 清洗和意图识别,再根据意图走不同检索策略,比如制度问答走知识库召回,工单问题走历史工单召回,接口问题走接口文档召回。

召回之后会做权限过滤、去重、rerank、上下文压缩,再交给 LLM 生成答案。生成结果还要经过引用校验、敏感信息过滤和结构化字段提取,最后写入日志系统供后续分析。技术上主要用了 Python、FastAPI、Elasticsearch、Milvus、Redis、PyTorch、Transformers 和 vLLM。

用户 query
  -> query 清洗/改写
  -> 意图识别
  -> BM25 + 向量混合召回
  -> 权限过滤
  -> rerank
  -> 上下文压缩
  -> LLM 生成
  -> 引用校验/字段抽取
  -> 日志与反馈回流

5. 整个系统存储数据用的是什么?存在哪些地方?采用了什么记录方案?

答案:系统里不是所有数据都存在一个库里。原始文档和附件存在对象存储,方便保留版本和回溯;解析后的结构化文本、标题树、元数据存在 PostgreSQL;全文检索索引放在 Elasticsearch;向量索引放在 Milvus;会话状态、热点缓存、prefix cache 的业务侧 key 存在 Redis;线上请求日志和模型中间结果写入日志平台。

记录方案上,每个 chunk 都有唯一 chunk_id,同时关联 doc_idversion_idtitle_pathpermission_tageffective_timesource_url。这样生成答案时可以追踪到底引用了哪个文档的哪个版本。对于历史工单,会保留脱敏后的问题、处理步骤、最终结论和工单分类,但不会把个人敏感字段直接进入训练集。

chunk_record = {
    "chunk_id": "c_202506_00021",
    "doc_id": "doc_hr_policy_008",
    "version_id": "v3",
    "title_path": ["员工服务", "账号权限", "权限申请流程"],
    "content": "权限申请需由直属主管审批后提交至 IT 服务台。",
    "permission_tag": ["internal", "it"],
    "effective_time": "2025-06-01",
    "embedding_id": "emb_77291"
}

6. query 做了什么处理?

答案:query 处理不是简单去空格,主要做了清洗、改写、分类和路由。清洗会处理错别字、特殊符号、无意义口语和复制进来的日志片段;改写会把省略表达补全,比如“这个怎么申请”要结合上下文改写成“VPN 权限怎么申请”;分类会判断是制度问答、故障排查、接口查询、闲聊还是无权限问题;路由决定后面走哪套检索和生成策略。

比较重要的是 query decomposition。复杂问题经常同时问多个点,比如“账号锁了怎么解,顺便查一下审批要多久”,这时如果用一个 query 召回,结果会混乱,所以会拆成多个子问题分别召回。对于包含代码、报错日志的 query,会保留关键错误码、接口名、堆栈关键词,不会过度语义改写。

def normalize_query(query, history=None):
    q = query.strip()
    q = q.replace("\u3000", " ")
    q = " ".join(q.split())

    if history and any(x in q for x in ["这个", "刚才那个", "它"]):
        q = f"结合上下文:{history[-1]}。当前问题:{q}"

    return q

7. 你自己做了一些模型微调,训练数据是从哪里来的?

答案:训练数据主要来自三部分。第一部分是历史工单和知识库问答,经过脱敏、去重、规则版本对齐和人工抽检后,用来构造意图分类、工单类型预测和字段抽取样本。第二部分是线上 badcase 回流,比如召回错、分类错、拒答错、格式错,这部分样本价值最高,因为它反映真实错误分布。第三部分是人工构造的边界样本,主要覆盖权限不足、证据冲突、问题不完整、多意图和敏感信息场景。

不会直接把所有历史数据丢进去训练。比如历史工单里有很多处理结论已经过期,或者包含个人信息、临时 workaround,这些都要过滤。用于 SFT 的样本必须保证输入、证据、答案和规则版本一致;用于分类的样本要处理类别不均衡;用于偏好训练的样本会从同一个 prompt 的多个候选答案中筛出 chosen/rejected。

def build_cls_sample(ticket):
    return {
        "text": ticket["title"] + "\n" + ticket["description"],
        "label": ticket["final_category"],
        "meta": {
            "source": "ticket",
            "version": ticket["policy_version"],
            "checked": ticket["human_checked"]
        }
    }

8. 当时微调用的模型是什么,选的是什么尺寸?

答案:分类和字段抽取任务用的是 DeBERTa-v3-base 和 RoBERTa-wwm-ext 这类 encoder 模型,原因是这类任务更需要稳定的判别边界和低延迟,没有必要上大参数生成模型。生成类任务做过 Qwen 系列 7B 级别模型的 LoRA 微调,主要学习企业内部问答风格、证据引用格式和拒答策略。没有直接选择更大的模型,是因为部署成本和延迟压力比较大,7B 级别配合 RAG 已经能覆盖大多数场景。

尺寸选择主要看任务。意图识别、工单分类这类高频链路,base 级别 encoder 更划算;复杂答案生成才走 LLM;如果是强规则判断,会放到规则引擎而不是模型里。微调方式上,生成模型主要用 LoRA/QLoRA,分类模型全参 fine-tune 或者只微调上层都试过,最终按验证集 F1、P95 延迟和线上资源成本综合选择。

lora_config = {
    "r": 16,
    "lora_alpha": 32,
    "target_modules": ["q_proj", "k_proj", "v_proj", "o_proj"],
    "lora_dropout": 0.05,
    "task_type": "CAUSAL_LM"
}

9. 传统 NLP 做分类场景训练时,最后的损失函数一般是什么?

答案:单标签多分类一般用 Cross Entropy Loss,多标签分类一般用 BCEWithLogitsLoss。如果类别极不均衡,可以用加权交叉熵、Focal Loss 或者重采样。传统 NLP 分类里,模型最后通常输出每个类别的 logits,交叉熵会先做 softmax,再最大化真实类别的概率,本质上是在优化负对数似然。

import torch
import torch.nn as nn

logits = torch.tensor([[2.0, 0.3, -1.2], [0.1, 1.5, 0.4]])
labels = torch.tensor([0, 1])

loss_fn = nn.CrossEntropyLoss()
loss = loss_fn(logits, labels)
print(loss.item())

10. 除了交叉熵,还有没有其他可以衡量两个分布之间差异的方式?

答案:可以用 KL 散度、JS 散度、Wasserstein 距离、Total Variation Distance、Hellinger Distance,也可以在蒸馏场景中用 MSE 对齐 logits 或 hidden states。交叉熵更偏训练目标,KL 更常用于分布对齐和蒸馏,JS 是对称且有界的,Wasserstein 在两个分布支撑集不重叠时也能提供更平滑的距离信号。

在大模型里,KL 很常见,比如 RLHF/DPO/GRPO 里会用 KL 控制当前模型不要偏离 reference model 太远。蒸馏里也常用 teacher/student 的 soft label 做 KL,这比只学 hard label 能保留类别间相似性。

import torch
import torch.nn.functional as F

student_logits = torch.tensor([[2.0, 0.5, -1.0]])
teacher_logits = torch.tensor([[1.5, 1.0, -0.5]])
temperature = 2.0

student_logp = F.log_softmax(student_logits / temperature, dim=-1)
teacher_p = F.softmax(tea

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

AI-Agent面试实战专栏 文章被收录于专栏

本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.

全部评论
感觉写的很好呢
点赞 回复 分享
发布于 昨天 23:35 北京

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务