从模型到用户:我在新东家踩过的机器学习部署坑
入职这家中型 SaaS 公司刚两个月,我这个“AI原住民”就被塞进了一个听起来高大上、实则焦头烂额的项目——把团队内部的智能问答模块上线给客户用。说是“智能问答”,其实就是个基于 LangChain 的 RAG(Retrieval-Augmented Generation)系统,底层跑着一个经过 Fine-tuning 的开源 LLM。听起来很酷?现实是:开发环境跑得飞起,一到部署就翻车。
作为一个重度 Cursor 用户,平时写 CRUD 都靠它生成,连注释都懒得打。但这次不一样——这玩意儿要直接面对前端、面对用户、面对产品经理那双“怎么还没好”的死亡凝视。上周五晚上九点,测试群里弹出消息:“线上问答返回空白,客户急了”。那一刻,我真想拔掉网线假装断电。
于是,痛定思痛,写下这篇血泪总结。不为炫技,只为下次别再被运维拉去背锅。
为什么部署比训练更让人头秃?
很多人以为,Fine-tuning 完模型、LangChain 编排好流程,就万事大吉了。天真!模型在 Jupyter Notebook 里输出完美答案,和它在生产环境扛住并发请求、快速响应、不崩不卡,完全是两码事。
我们最初犯的错,就是把“能跑”当成“能用”。本地用一台 32G 内存的笔记本跑 LangChain + 向量数据库 + LLM,延迟 800ms,觉得“还行”。结果一上 K8s,前端调用超时设的是 2 秒,用户刷三次没反应就骂娘。更惨的是,某次高峰时段 QPS 突然飙升,Pod 直接 OOMKilled,整个服务雪崩。
这时候我才意识到:机器学习部署不是技术终点,而是工程起点。
从 Fine-tuning 到轻量化:别让模型成为累赘
我们的业务场景是帮客户自动解析他们上传的 PDF 合同,提取关键条款并回答问题。原始数据是几千份法律文档,噪声大、格式杂。一开始我们直接拿 Llama-3-8B 做全参数微调,效果确实不错,准确率 85%+。但部署时傻眼了——单个推理请求吃掉 6GB 显存,还得配 A10G,成本直接爆炸。
后来在 Cursor 里敲了个 prompt:“如何在保持性能的前提下减小 LLM 部署体积?” 它建议试试 QLoRA 微调 + GGUF 量化。我半信半疑试了下,用 Unsloth 库做 QLoRA,只更新 0.1% 的参数,然后导出为 GGUF 格式,用 llama.cpp 推理。
结果惊人:模型从 14GB 压缩到 4.7GB(4-bit),推理速度提升 3 倍,显存占用降到 2GB 以下。虽然准确率掉了 2 个百分点(83% → 81%),但在业务容忍范围内。关键是——终于能在普通云服务器上跑了,不用求着运维申请 GPU 资源。
# 示例:用 llama.cpp 加载 GGUF 模型
./main -m ./models/contract-qa.Q4_K_M.gguf \
-p "根据合同第5条,违约金是多少?" \
-n 256 --temp 0.7
教训:Fine-tuning 不是为了追求 SOTA,而是为了在约束条件下找到性价比最优解。有时候,少即是多。
LangChain:强大但危险的双刃剑
LangChain 让我们快速搭起了 RAG 流程:用户问 → 检索相关段落 → 注入 prompt → 调 LLM → 返回。但默认配置简直是“内存泄漏制造机”。
问题出在两个地方:
- 向量检索未缓存:每次请求都重新查 FAISS,高频词反复计算。
- LLM 调用阻塞主线程:Python 的同步 I/O 在高并发下直接卡死。
我们改了三处:
- 用 Redis 缓存 top-k 检索结果,TTL 设 5 分钟(合同内容不会秒变)
- 把 LangChain 的
RunnableSequence改成异步模式(感谢 FastAPI) - 限制最大上下文长度,防止用户问“把整本合同总结一下”这种魔鬼问题
# 异步 LangChain 调用示例
from langchain_core.runnables import RunnableLambda
import asyncio
async def async_retrieve(query: str):
return await retriever.ainvoke(query)
async def async_llm_call(context: str, question: str):
prompt = f"Context: {context}\n\nQ: {question}\nA:"
return await llm.ainvoke(prompt)
# 在 FastAPI 路由中
@app.post("/ask")
async def ask(request: AskRequest):
docs = await async_retrieve(request.question)
context = "\n".join([d.page_content for d in docs])
answer = await async_llm_call(context, request.question)
return {"answer": answer}
另外,LangChain 的日志默认关得太死。上线前务必打开 LANGCHAIN_TRACING_V2=true,配合 LangSmith,不然出问题根本不知道是检索错了还是 LLM 幻觉了。
前端不是终点,而是用户体验的第一道防线
别以为部署完 API 就完事了。前端同学才是直面用户的勇士。我们最初给前端的接口是“同步阻塞式”:发请求 → 等 2 秒 → 返回答案。结果用户疯狂点“发送”,后端瞬间被打爆。
后来和前端老哥对齐,改成流式响应 + 加载态反馈:
- 用 SSE(Server-Sent Events)逐字返回 LLM 输出
- 前端显示“正在思考…” + 动画
- 设置 5 秒超时,超时后提示“稍后再试”
前端代码也做了兜底:
// 前端防抖 + 超时控制
let abortController;
const askQuestion = async (question) => {
if (abortController) abortController.abort();
abortController = new AbortController();
try {
const response = await fetch('/stream-ask', {
method: 'POST',
body: JSON.stringify({ question }),
signal: abortController.signal,
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// 实时追加到 UI
appendToAnswer(chunk);
}
} catch (e) {
if (e.name !== 'AbortError') {
showError("服务繁忙,请稍后重试");
}
}
};
产品经理看了 demo 后居然说“有 ChatGPT 那味儿了”,难得夸一次,感动哭。
部署架构:别把所有鸡蛋放一个 Pod 里
早期我们图省事,把 LangChain、向量库、LLM 全塞进一个 FastAPI 服务。结果就是:一崩全崩。
现在拆成三层:
| 组件 | 技术栈 | 部署方式 | 扩缩容策略 |
|---|---|---|---|
| 前端 API 网关 | FastAPI + Uvicorn | K8s Deployment | HPA based on CPU |
| 检索服务 | FAISS + Redis | 独立 Pod | 固定副本数 |
| LLM 推理服务 | llama.cpp + GGUF | K8s Deployment + GPU node selector | 手动扩缩(成本敏感) |
关键改进:
- LLM 服务独立:即使 LLM 挂了,前端还能返回“服务暂时不可用”,而不是 500
- 向量库预加载:启动时加载 FAISS 索引到内存,避免冷启动延迟
- 健康检查:每个服务暴露
/health,K8s 自动剔除异常实例
顺便吐槽一句:运维大哥看到我们从“一个 Pod”变成“五个服务”,表情一度非常复杂。但自从没再半夜被 PagerDuty 叫醒,他现在见我都笑。
监控与回滚:别等用户投诉才行动
上线第一周,我们靠人工刷页面测功能。第二周,客户群里开始有人问“为什么昨天能答的问题今天不行了?”
赶紧补监控:
- Prometheus + Grafana:跟踪 QPS、P99 延迟、错误率
- LangSmith:记录每条用户 query 的完整 trace
- 日志告警:LLM 输出包含“我不知道”或“根据我的知识”超过阈值,自动告警(可能检索失效)
最救命的是一键回滚机制。我们把模型版本、LangChain prompt 模板、检索索引都打 tag 存 Git。一旦线上出问题,5 分钟内切回上一版。上周就有一次,因为新合同模板没入库,导致检索召回率暴跌,靠回滚保住了 SLA。
写在最后:AI 工程化,本质是“克制的艺术”
刚入职时,我满脑子都是“上更大模型”、“搞更复杂 pipeline”。现在明白了:在真实业务场景里,稳定 > 新颖,可维护 > 黑科技,成本可控 > 性能极致。
Fine-tuning 不是为了秀技术,而是解决特定问题;LangChain 是脚手架,不是银弹;前端不是调用方,而是体验共建者。
现在,我依然每天开着 Cursor 写代码,但它更多帮我生成 boilerplate、写单元测试、解释报错。真正的架构决策、部署取舍、性能权衡,还得靠人脑——毕竟 AI 还不能替我挨产品经理的骂。
如果你也在搞 ML 部署,记住:别追求一步到位。先让服务跑起来,再让它跑得稳,最后才考虑跑得快。毕竟,在互联网公司,活下来比赢漂亮更重要。
(完)
P.S. 上周五那个线上事故,最后发现是 FAISS 索引文件在 CI/CD 中没正确复制……运维请我喝了杯冰美式,算扯平了。

评论 0