从CV小白到上线:我在字节搞了个视觉问答系统,顺便玩了把RAG+AI编程
上周五晚上十一点半,我瘫在工位上盯着屏幕右下角的 build success,心里终于松了口气。这破项目总算能交差了——不是因为需求多复杂,而是产品经理在双11前两周突然拍脑袋说:“能不能让用户上传一张商品图,然后问‘这是什么品牌?’、‘有没有类似款?’,系统自动回答?”
我当时差点把咖啡泼他脸上。但转念一想,这不就是个典型的多模态检索增强生成(RAG)场景吗?而且最近正好在啃 Rust,不如借机练练手。于是,这个“临时起意”的计算机视觉实战项目就这么上了。
为啥是我来搞这个?
先简单自报家门:字节跳动基础架构组后端开发,干了快两年,日常和 Kubernetes、分布式存储、中间件打交道。VSCode 插件装了快 50 个,光是 Rust 相关的就有 rust-analyzer、crates、even better TOML……别笑,真香警告。
虽然主业不是算法,但字节有个“技术轮岗”文化——只要你敢接,就让你试。加上最近 AI 编程工具(比如 Cursor、GitHub Copilot)越来越猛,我也想看看它们能不能帮我这种“非科班 CV 工程师”快速搭出可用系统。
于是,一个融合 计算机视觉 + RAG + AI 编程辅助 的小项目就这么诞生了。
需求拆解:用户传图,我得答对
核心目标很清晰:
用户上传一张图片 → 系统理解图像内容 → 支持自然语言提问 → 返回准确答案
听起来像 CLIP + LLM 的组合?差不多,但细节魔鬼。我们内部商品库有上亿 SKU,不能靠纯生成模型瞎编,必须基于真实数据检索后再生成——这正是 RAG 的用武之地。
所以整体 pipeline 分三块:
- 图像编码:把图片转成向量
- 多模态检索:用问题文本 + 图像向量去商品库找 top-K 相似项
- 生成回答:把检索结果喂给 LLM,让它组织语言回答
关键在于:怎么让这三个环节高效、准确、低成本跑起来?
技术选型大战:CV 模型哪家强?
我拉了个表格,对比了几种主流方案(别嫌土,字节内部也这么干):
| 方案 | 模型 | 是否开源 | 向量维度 | 中文支持 | 推理速度 (CPU) | 是否适合电商 |
|---|---|---|---|---|---|---|
| CLIP (ViT-B/32) | OpenAI | ✅ | 512 | 弱(英文为主) | 中等 | ⚠️ 需微调 |
| Chinese-CLIP | OFA | ✅ | 512 | ✅ 强 | 快 | ✅ |
| BLIP-2 | Salesforce | ✅ | 768 | 中等 | 慢(需 GPU) | ✅✅ |
| Qwen-VL | 阿里 | ✅ | 4096 | ✅✅ | 慢 | ✅✅(但太重) |
| 自研小模型 | 内部 | ❌ | 256 | ✅ | 极快 | ✅✅✅ |
产品经理第一句话就是:“预算有限,别上 GPU。”
运维兄弟补刀:“QPS 至少 50,P99 延迟 < 800ms。”
直接把 BLIP-2 和 Qwen-VL 判死刑。CLIP 虽然轻,但中文商品名识别烂得一批——测试时把“李宁云 cushion 跑鞋”识别成 “Nike Air Max”,我当场裂开。
最后咬牙上了 Chinese-CLIP,理由如下:
- 开源免费,社区活跃
- 在中文图文对齐任务上表现优秀(论文指标吊打原版 CLIP)
- 可以用 ONNX 导出,部署到 CPU 服务毫无压力
不过,光靠预训练模型肯定不够。我们拿内部 50 万张带标签的商品图做了两轮微调:
- 第一轮:对比学习,拉近“同款不同图”距离
- 第二轮:加入用户真实 query,做图文匹配蒸馏
微调完,mAP@100 提升了 22%,线上 bad case 减少一大半。
RAG 架构设计:别让 LLM 胡说八道
很多人以为 RAG 就是“检索 + 生成”,其实坑多得很。最典型的问题:检索不准,LLM 一本正经胡说八道。
比如用户问:“这款手机支持无线充电吗?”
如果检索返回的是“iPhone 13”,但实际图片是“Redmi Note 12”,LLM 很可能答:“支持,最高 15W。” —— 完全错误!
所以我们加了三层保险:
1. 双路检索:图文联合 embedding
- 文本 query 先用 BGE-M3 编码
- 图片用 Chinese-CLIP 编码
- 最终向量 = α * text_emb + (1-α) * image_emb (α=0.6 实测最佳)
2. 粗排 + 精排
- 粗排:FAISS HNSW,召回 1000 条
- 精排:用轻量 Cross-Encoder 重打分(只跑 top-100)
3. 检索结果可信度过滤
如果 top-1 和 top-2 的相似度差 < 0.1,直接返回“无法确定,请换张图或描述更详细”
这部分代码用 Rust 重写了核心逻辑(主要是 FAISS 查询 + 向量加权),比 Python 快 3.2 倍,内存占用降了 60%。虽然学 Rust 时被 borrow checker 折磨到想删库跑路,但现在真香。
// 示例:图文混合 embedding 计算(简化版)
pub fn fuse_embeddings(
text_emb: &[f32],
image_emb: &[f32],
alpha: f32,
) -> Vec<f32> {
text_emb.iter()
.zip(image_emb.iter())
.map(|(t, i)| alpha * t + (1.0 - alpha) * i)
.collect()
}
AI 编程:我的“赛博结对编程”伙伴
说实话,要不是用了 AI 编程工具,这项目根本赶不上双11。
我主要用 Cursor(比 Copilot 更懂上下文),几个骚操作:
自动生成 ONNX 导出脚本
输入:“用 PyTorch 导出 Chinese-CLIP 为 ONNX,支持动态 batch”
→ 直接生成可运行代码,省了我查文档两小时Rust FAISS 封装
我写了个注释:“封装 faiss-rs,提供 add/search/delete 接口,线程安全”
→ Cursor 自动生成 struct + impl + RwLock 保护Prompt 调优助手
让它帮我改 LLM 的 system prompt:“你是一个电商客服,只能基于提供的商品信息回答。如果信息不足,就说‘不清楚’。”
它建议加入:“禁止推测、禁止编造参数、禁止比较未提及品牌”——救命,这比我想得周全。
当然,AI 也不是万能的。有一次它给我生成了个 unsafe { transmute },差点引发内存泄漏。后来我学会了:生成代码必 review,尤其涉及 unsafe 或并发。
部署上线:K8s + Envoy + 血泪教训
作为基础架构组老油条,部署这块我熟。但这次还是踩了个大坑。
初始架构:
用户请求 → API Gateway → CV Service (Rust) → FAISS Index → LLM Service
压测时发现:FAISS 单线程加载 index 时,整个服务卡死 8 秒!
原因是 FAISS 默认不是线程安全的,而我们的服务是多 worker 模型。
解决方案:
- 启动时预加载 index 到共享内存(用 mmap)
- 所有查询走只读视图
- 加个 health check,index 未就绪时返回 503
配置片段(K8s Deployment):
env:
- name: INDEX_PATH
value: "/mnt/shared/faiss_index.bin"
volumeMounts:
- name: index-volume
mountPath: /mnt/shared
volumes:
- name: index-volume
persistentVolumeClaim:
claimName: faiss-index-pvc
上线那天,运维盯着 Grafana 屏幕说:“P99 稳在 620ms,牛啊。”
我回了句:“下次别让产品周五提需求就行。”
效果与反思:值不值得搞?
上线两周,数据还不错:
- 准确率(人工抽样):86.3%
- 平均响应时间:580ms
- 用户满意度(NPS):+42
最关键的是,bad case 大幅减少。以前纯 LLM 回答经常“幻觉”,现在有了 RAG 锚定,靠谱多了。
但也有遗憾:
- 没上多模态 LLM(如 LLaVA),因为延迟扛不住
- 图片 OCR 还没集成,遇到带文字的商品图会漏信息
- 中文 query 的语义泛化还不够,比如“耐克那种气垫鞋”识别率低
不过嘛,技术永远没有完美方案,只有“够用就好”。毕竟在字节,能跑赢 deadline 的代码才是好代码。
给同行的建议
如果你也在搞类似项目,几点血泪经验:
- 别迷信 SOTA 模型:轻量 + 可控 > 重型 + 黑盒
- RAG 的核心是检索质量,不是 LLM 多 fancy
- AI 编程是加速器,不是替代品——你得知道它哪里会翻车
- 尽早压测,尤其是向量检索这种 I/O 密集型操作
- 中文场景一定要用中文预训练模型,别头铁上英文 CLIP
最后,Rust 虽然学习曲线陡,但在高性能服务里真的稳。我现在看 Go 都觉得 GC 是种“不确定性”。
项目代码暂时不能开源(字节规矩你懂的),但核心思路完全可以复用。如果你也在折腾 CV + RAG,欢迎评论区交流——或者下次团建我请你喝瑞幸,聊聊怎么让产品经理闭嘴 😏

评论 0