做大模型项目,很多团队一开始都会有一个非常自然的想法:
先微调,再上线。
看起来这条路顺理成章:
业务数据整理好,模型调一轮,效果更贴近场景,然后再部署推理服务,最后接入业务系统。
但真正落到工程现场,情况往往没这么理想。
最近我们围绕一个“行业知识服务平台”做了一轮完整实践:目标是基于手册、规章、说明文档、流程资料等内容,构建一个具备问答、检索、引用追溯能力的知识服务系统。模型选型上,前期重点验证了 Qwen3.5,硬件环境则放在 海光 K100AI 上。
这篇文章不讲概念,不讲愿景,只讲一个问题:
当“大模型 + 行业知识库”真正落到国产算力环境时,哪些路径能更快形成可用结果?又有哪些看起来正确的路线,实际上会在工程上卡住?
我们的阶段性结论是:
现阶段,推理 + RAG 比“硬上微调”更容易形成可交付能力。
⸻
一、项目目标:不是聊天,而是知识服务
这个项目从一开始就不是做通用聊天机器人,而是要做一个行业知识服务平台。
它要解决的其实是几类非常典型的问题:
* 文档太多,人工检索效率低
* 问题表述不标准,传统关键词搜索不稳定
* 需要基于资料回答,而不是模型“凭感觉生成”
* 回答后最好还能指出参考来源,方便核验
* 长文档、多片段、多规则场景下,需要更强的上下文整合能力
所以,从需求本质上看,这不是“让模型会聊天”,而是:
让模型在知识约束下更好地理解、检索、组织和回答。
⸻
二、第一步:先把推理链路跑通
在任何大模型项目里,最容易被低估的一件事,就是基础环境适配。
这次在海光 K100AI 上,我们优先做的不是业务问答,而是:
* 容器内识别 DCU
* PyTorch 识别设备
* vLLM 服务启动
* OpenAI 兼容接口打通
* /v1/models、/v1/chat/completions 返回正常
我们实际使用的容器启动方式
为了把项目目录、模型目录和驱动环境都挂进去,我们使用了类似下面的容器启动命令:
docker run -it --name qwen-rag-dev \
--network host \
--ipc=host \
--shm-size=64g \
--group-add video \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--privileged \
--device=/dev/kfd \
--device=/dev/dri \
--device=/dev/mkfd \
-v /sys/kernel/debug:/sys/kernel/debug:ro \
-v /opt/hyhal:/opt/hyhal:ro \
-v /opt/dtk-26.04:/opt/dtk-26.04:ro \
-v /volumes/oss0:/volumes/oss0 \
-v $(pwd):/workspace \
-w /workspace \
harbor.sourcefind.cn:5443/dcu/admin/base/custom:vllm018-ubuntu22.04-dtk26.04-gemma4-0413 \
/bin/bash
一个非常重要的经验是:
-v /sys/kernel/debug:/sys/kernel/debug:ro
这一项不要省。
它不一定直接提升性能,但能显著改善 KFD / HW queue 等底层调试能力,否则很多关键信息根本看不到。
⸻
三、微调不是不能做,但未必适合作为第一优先级
最初我们也做了 LoRA 微调验证,而且第一轮其实是成功的:
* 训练可以启动
* checkpoint 能保存
* LoRA merge 能完成
* 合并后模型也能被加载推理
从表面看,这条路好像已经通了。
但很快问题就暴露出来了:
* HIPBLAS_STATUS_INTERNAL_ERROR
* rocBLAS error
* Invalid address access
* VMFault
这些错误一旦出现,说明问题已经不再是“脚本参数调一调”能解决的,而是进入了:
模型结构 + 框架实现 + 驱动栈 + 底层 kernel 适配 的复杂交叉区。
因此,虽然“第一轮能跑通一次”,但并不代表“持续训练可以稳定复现”。
从工程视角看,这种情况就不适合继续把“微调优先”当成当前阶段主路线。
⸻
四、RAG 为什么成了现阶段更实际的方案
当微调链路出现不稳定以后,一个很现实的问题就摆在面前:
项目是继续等训练稳定,还是先想办法把系统做出来?
我们的答案是后者。
因为对于知识服务场景来说,当前最重要的,不是“模型是否被训练过”,而是:
* 能不能基于资料回答
* 能不能检索到相关依据
* 能不能处理长文档
* 能不能给出可追溯结果
* 能不能真正上线给人用
而这些能力,RAG 恰恰非常适合。
⸻
五、最小可用 RAG:不是先服务化,而是先跑通链路
很多团队在做 RAG 时,一上来就想:
* 上向量数据库
* 上服务化 embedding
* 上独立 reranker
* 上复杂编排框架
* 上完整前后端
结果常常是系统越搭越复杂,但最基本的“问一个问题能不能回答出来”反而迟迟没有闭环。
这次我们走的是另一条路:
先做最小闭环
1. 文档抽取
2. 文本切片
3. 本地 embedding
4. 本地 FAISS 建索引
5. 问题检索
6. 检索结果拼接
7. 交给大模型回答
实际执行顺序非常简单
python3 scripts/rag/extract_docs.py
python3 scripts/rag/chunk_docs.py
python3 scripts/rag/build_faiss.py
python3 scripts/rag/search_faiss.py
python3 scripts/rag/rag_query.py
这条链路非常朴素,但价值很高,因为它能最快回答一个问题:
这套方案到底能不能工作?
答案是:能。
⸻
六、知识库处理链路:从文档到索引
为了快速跑通链路,我们先把原始文档做抽取、切片和本地索引。
目录结构大致是这样:
qwen35_full_bundle/
├── docs/
├── knowledge/
│ ├── raw/
│ ├── chunks/
│ ├── index/
│ └── cache/
├── scripts/
│ └── rag/
1)抽取文档文本
支持 .pdf、.txt、.md 等原始文件抽取。
python3 scripts/rag/extract_docs.py
2)切片
把长文档切成适合检索的小片段。
python3 scripts/rag/chunk_docs.py
3)构建本地向量索引
embedding 模型我们采用本地加载,避免运行时联网:
EMBED_MODEL_NAME = "/volumes/oss0/bge-small-zh-v1.5"
构建索引命令:
python3 scripts/rag/build_faiss.py
4)检索测试
先验证召回是否正常:
python3 scripts/rag/search_faiss.py
这一步成功的标志不是“回答多漂亮”,而是:
* embedding 模型能加载
* FAISS 索引能建立
* 问题能召回到相对相关的片段
⸻
七、Qwen3.5 服务启动:几个关键参数比模型本身更重要
在部署 Qwen3.5 时,我们踩过不少坑,最后比较稳定的一组经验是:
1. 不要只靠客户端关思维链
如果希望模型只输出最终答案,不展示 thinking,不能只靠客户端 prompt,而是要在服务端启动时就关掉:
--default-chat-template-kwargs '{"enable_thinking": false}'
否则经常会出现:
* content 和 reasoning_content 混乱
* 明明要求不要思维链,结果还是输出
* 前端展示异常
2. 如果要支持工具调用,需要显式开启
例如:
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
不加的话,客户端传 tool_choice: "auto" 会直接报 400。
3. 一套实际启动命令示例
我们后来实际使用的启动方式大致如下(已脱敏):
export HIP_VISIBLE_DEVICES=0,1
export VLLM_LOGGING_LEVEL=INFO
export VLLM_SPEC_DECODE_EAGER=1
export VLLM_MLA_DISABLE=0
export VLLM_USE_FLASH_MLA=1
export VLLM_RPC_TIMEOUT=1800000
export VLLM_NUMA_BIND=1
export VLLM_RANK0_NUMA=0
export VLLM_RANK1_NUMA=1
vllm serve
\
--served-model-name Qwen3.5-27B \
--tensor-parallel-size 2 \
--max-model-len 4096 \
--enable-prefix-caching \
--gpu-memory-utilization 0.75 \
--port 8000 \
--trust-remote-code \
--default-chat-template-kwargs '{"enable_thinking": false}' \
--enable-auto-tool-choice \
--tool-call-parser qwen3_coder \
--api-key
⸻
八、RAG 问答调用:先检索,再生成
索引建好以后,rag_query.py 负责做两件事:
1. 本地检索最相关片段
2. 把这些片段拼成“参考资料”,再调用 Qwen 生成答案
最小调用方式
python3 scripts/rag/rag_query.py
如果要直接用 curl 调试模型服务,也可以这样:
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-d '{
"model": "Qwen3.5-27B",
"messages": [
{
"role": "system",
"content": "你是一个知识助手,只输出最终答案,不要输出思考过程。"
},
{
"role": "user",
"content": "请解释某个问题"
}
],
"temperature": 0.2,
"max_tokens": 256
}'
⸻
九、一个非常现实的问题:显卡不是只给一个模型准备的
很多时候,业务方看到大模型项目会问:
为什么需要这么多显卡?为什么还要扩内存?
如果只看“一个问答模型”,这个问题似乎不好回答。
但如果看完整链路,其实很容易理解。
显卡不是只给主模型用的
在一个完整知识服务系统里,可能同时存在:
* 主大模型推理
* embedding 向量化模型
* reranker 重排模型
* 工具调用或辅助模型
* 并发请求调度
如果所有东西都抢同一张卡,结果一定是:
* 延迟高
* 资源争抢
* 抖动大
* 难排障
内存也不是“可有可无”
RAG 本身很吃内存,因为它涉及:
* 文档切片
* 索引加载
* 检索缓存
* 上下文拼接
* 中间数据组织
所以在这种场景里:
显卡解决的是算力问题,内存解决的是知识链路问题。
两者缺一不可。
⸻
十、现阶段的最终判断
如果把这次实践浓缩成一句话,那就是:
在当前环境下,先把推理 + RAG 做成,比坚持“微调优先”更有工程价值。
这不是说微调没意义,而是说:
* 当前阶段,推理已经能跑通
* RAG 已经能形成知识服务闭环
* 微调暂时不够稳定,不适合作为首要交付路径
所以更合理的路线应该是:
当前阶段
* 推理服务优先
* RAG 落地优先
* 知识源整理优先
* 输出质量和引用能力优先
后续阶段
* 继续预研更适合训练的模型基座
* 再评估 LoRA / 微调是否回归主线
* 把 RAG 和微调能力结合起来
⸻
十一、最后一点经验:先做成,再做优
很多时候,项目最大的风险不是“模型不够强”,而是路线过于理想化。
如果一开始就把目标定成:
* 模型必须先微调成功
* 上线前必须全部能力齐备
* 性能、效果、架构一次到位
那很可能会在工程现实里反复打转。
反过来,如果先问自己一句:
什么路径最有可能在当前条件下先形成可用结果?
很多决策反而会清晰很多。
而对这次项目来说,当前答案已经很明确:
先把 Qwen 推理 + RAG 跑稳,再逐步迭代。
这条路,不一定最理想,但很可能是现阶段最正确的。