背景痛点:传统客服系统“三座大山”
过去两年,我先后接手过三套客服系统,无一例外都在以下三处踩坑:
意图识别准确率低于80%
- 老系统用关键词+正则,用户换种说法就“抓瞎”。
- 多轮对话里,上文信息无法沉淀,导致“请问您贵姓”反复出现。
多轮状态维护靠Redis裸奔
- 会话key只有user_id,没有version,导致A/B测试切换模型时串线。
- 高并发下TTL漂移,出现“僵尸会话”,内存陡增。
高并发场景TPS≈30就报警
- 同步Flask服务,GIL锁死,一条推理200ms就把CPU吃满。
- GPU显存没有池化,batch=1,利用率15%,P99延迟飙到2s。
技术选型:Fine-tuning vs Prompt Engineering
| 维度 | Fine-tuning(BERT+BiLSTM) | Prompt Engineering(大模型直接答) |
|---|---|---|
| 数据量 | 5k+标注即可收敛 | 需要10w+提示样例才稳定 |
| 延迟 | 单句15ms(T4) | 首token 200ms+ |
| 成本 | 训练1h,推理GPU 2G | 100QPS≈A100×4 |
| 合规 | 参数私有化,可审计 | 第三方API,日志外泄风险 |
结论:在企业场景下,延迟、成本、合规三杀,Fine-tuning胜出。
于是采用“BERT-mini做Embedding + BiLSTM捕捉时序”的混合结构,参数量仅22M,单机可压500TPS。
核心实现:FastAPI异步推理+状态缓存
1. 异步推理服务(FastAPI)
# main.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel import asyncio import torch, time from typing import List app = FastAPI() model = torch.jit.load("intent.pt") # 已转TorchScript model.eval().cuda() class Query(BaseModel): uid: str text: str @app.post("/infer") async def infer(q: Query) -> dict: try: loop = asyncio.get_event_loop() # 线程池里跑GPU推理,防止event阻塞 logits = await loop.run_in_executor(None, model.forward, q.text) label = int(logits.argmax(-1)) return {"uid": q.uid, "label": label, "ts": time.time()} except Exception as e: raise HTTPException(status_code=500, detail=str(e))时间复杂度:BERT部分O(n²)与序列长度相关,但max_len=64,常数可忽略;BiLSTM O(n)线性。整体<50ms。
2. 带缓存的对话状态管理
# state_manager.py import aioredis from typing import Optional, Dict, Any import json class DialogState: def __init__(self): self.redis = aioredis.from_url("redis://localhost:6379", decode_responses=True) async def get(self, uid: str) -> Optional[Dict[str, Any]]: data = await self.redis.get(f"dlg:{uid}") return json.loads(data) if data else None async def set(self, uid: str, state: Dict[str, Any], ttl=600): await self.redis.setex(f"dlg:{uid}", ttl, json.dumps(state))关键点:
- key带版本号
dlg:{uid}:v2,模型热更新时双写,灰度切换零丢失。 - 缓存命中情况下,P99延迟<5ms,极大缓解下游GPU压力。
3. K8s负载均衡+自动扩缩容
apiVersion: apps/v1 kind: Deployment metadata: name: llm-svc spec: replicas: 3 template: spec: containers: - name: infer image: myrepo/llm-infer:1.4 resources: requests: nvidia.com/gpu: "1" limits: nvidia.com/gpu: "1" --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: llm-svc-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: llm-svc minReplicas: 3 maxReplicas: 30 metrics: - type: Pods pods: metric: name: gpu_utilization target: type: AverageValue averageValue: "70"经验:
- 采用KEDA+Prometheus Adapter,自定义GPU利用率指标,比CPU指标更贴合推理场景。
- 扩容冷启动40s,需配合就绪探针
initialDelaySeconds=30,防止流量打入未初始化容器。
性能优化:AB测试与显存监控
| 框架 | 平均延迟(P95) | GPU显存 | 备注 |
|---|---|---|---|
| TF Serving 2.9 | 42ms | 1.8G | 开Op融合 |
| ONNX Runtime 1.15 | 28ms | 1.1G | TensorRT fp16 |
结论:ORT+TRT组合延迟降低33%,显存省40%,直接上生产。
显存监控方案:
- DCGM Exporter + Prometheus,每10s采样,告警规则
gpu_memory_used > 90%。 - 结合
nvidia-ml-py写兜底脚本,OOM前5s自动清空闲置session,宁可杀会话不杀服务。
避坑指南:日志脱敏+模型漂移+恶意输入
日志脱敏
- 写前正则匹配手机、身份证,替换为
$$1。 - 开源库
presidio+自定义字典,CPU消耗<1%。
- 写前正则匹配手机、身份证,替换为
模型漂移检测
- 每夜跑批测试集,计算F1;连续3天下降>2%即回滚。
- 用KS统计监控线上特征分布,阈值0.09,自动触发标注员回流。
恶意输入防御
- 输入长度>512直接截断并记警告。
- 引入
token_entropy检测乱码,熵值>6拒绝服务。 - 同IP 30min内请求>1000,Nginx+Lua返回418。
代码规范与复杂度
- 所有函数均写Type Hint,关键路径加
@torch.no_grad()。 - 异常捕获后统一抛指标,避免日志爆炸。
- 核心检索算法用倒排,复杂度O(log n) + 位运算,单查询<1ms。
延伸思考:temperature与敏感行业
temperature实验
固定top_p=0.9,让temperature从0.2到1.0每0.1步进,观察回复多样性(Distinct-2)。
结果:- 0.2→重复率42%,适合FAQ;
- 0.8→重复率18%,适合闲聊;
0.9开始“跑火车”,出现违规词概率+3%。
建议:企业客服默认0.35,营销外呼可提到0.6,并加后处理黑名单。
金融/医疗特殊处理
- 强制加“免责声明”模板,任何回答不得出现绝对化措辞。
- 引入知识图谱做事实核查,置信度<0.97走人工兜底。
- 合规审计接口,返回trace_id方便监管回放。
把这套流程撸完后,我们实测峰值520TPS,平均响应22ms,P99 65ms,GPU利用率稳在75%左右。
最爽的是上线三个月,意图准确率从78%提到93%,客服小姐姐终于不用每天对着“机器人答非所问”抓狂。
如果你也在给自家客服换“大脑”,不妨从BERT+BiLSTM小模型开始,先把延迟压下来,再慢慢玩大模型创意。祝你迭代顺利,少踩坑。