1. 项目拆解:为什么非要把1684x、qwen3-vl和weknora三个东西绑到一起
1.1 这个项目到底在解决什么问题
单看“算能1684x部署qwen3-vl和腾讯开源RAG知识库weknora,并把两者连接使用”这个标题,很多人以为是三个独立任务的拼盘:先在一个国产推理卡上跑通多模态大模型,再部署一个知识库服务,最后用API串一下。但真正做下来你会发现,这三个东西是一个完整产品的最小闭环:企业内部那些带截图、扫描件、表格、签章的文档,之前要么靠人肉翻,要么只能搜到标题搜不到正文,现在可以用多模态大模型把图片“读”出来,再用RAG知识库把散落的内容检索出来,最后由大模型组织成带出处的回答。
这个项目适合谁参考?两类人。第一类是正在做信创或国产化适配的算法工程师,手里有算能1684x的卡,想在上面跑Qwen系列模型,却卡在模型转换和推理服务封装这一层。第二类是做RAG落地的后端工程师,已经试过Dify和FastGPT,发现要么太重,要么检索策略不透明,想找一个能本地部署、能接任意模型、检索模块足够灵活的开源知识库,而weknora正好补这个位。如果你只是想快速体验RAG,完全可以用一台Mac跑Ollama加本地知识库,没必要上国产卡;但一旦要考虑数据不出域、推理设备成本可控、离线可用这几件事,1684x加自研/开源的组合就成了绕不开的路线。
1.2 方案选型背后的“三角博弈”
先解释一下为什么是这三样东西,而不是随便拼一套。
算能1684x:型号是BM1684X,算能科技那款面向推理场景的AI芯片,单卡INT8算力在32TOPS级别,显存常见有16GB和32GB两种版本,功耗和价格都比同算力的英伟达卡友好不少。它最大的特点是支持BF16和INT8量化,Qwen3-VL这种7B~8B级别的多模态模型有希望在单卡上跑起来。如果换成老的1684(第一代),没有BF16支持,量化精度损失会更明显;如果换成4090,成本和功耗又上去了。做私有化部署,客户预算摆在那里,1684x属于“性能够用、成本可控”的折中答案。
qwen3-vl:Qwen3-VL系列是通义千问那帮人出的多模态模型,从2B到235B都有。我们选的是8B版本,关键的三个理由:一是中文理解能力和视觉理解能力在国内开源模型里属于第一梯队,OCR、表格还原、图表分析这些场景实测能用;二是8B这个体量正好卡在1684x单卡能装下的上限,再大的32B就算量化完也塞不进16GB显存;三是它在文档问答场景的指令遵循能力不错,做RAG生成答案时不会自顾自地跑偏。
weknora:腾讯开源的知识库服务,定位和Dify里的知识库模块类似,但更聚焦“检索”这件事。它把文档解析、切分、向量化、混合检索、重排、引用溯源这几个环节都做成了可配置的流水线,并且对外暴露OpenAI兼容接口,方便上游应用直接调用。选择它还有一个现实原因:团队不打算从零写RAG,又不想被某个云厂商的托管知识库绑死,weknora正好是“开源、可本地部署、检索链路透明”的中间态。
连接方式:整套系统里,1684x只是推理设备,qwen3-vl只是生成引擎,weknora是知识库服务。weknora内部通过OpenAI兼容接口把检索到的上下文和用户问题一起发给部署在1684x上的qwen3-vl,拿到答案后再附上引用来源返回给调用方。这个架构最大的好处是每一层都可以单独替换——今天qwen3-vl效果不行了,换一个模型只要改配置;明天weknora不满意了,换FastGPT也无所谓。
2. 环境准备:算能1684x部署前的这几个坑必须先趟
2.1 硬件与系统环境确认
动手之前,先把硬件资料核对一遍。我们拿到的是一张SC5系列PCIe卡,芯片是BM1684X,显存16GB。这一步很容易被跳过,但真正做起来很关键,因为不同显存容量决定了后面模型量化策略的取舍。
系统层面建议直接用Ubuntu 22.04 LTS server版,内核保持默认即可。算能官方驱动对Ubuntu 20.04和22.04支持最稳,建议不要轻易尝试新内核。如果你是x86服务器,直接装官方deb包就行;如果是ARM(比如飞腾、鲲鹏的主机),驱动包要选对应的arm64版本,千万别混装。
安装完驱动后,用下面命令验证:
bm-smi正常应该能看到卡名BM1684X、显存总量、当前利用率等信息。如果bm-smi都找不到命令,大概率是libsophon没装或环境变量没配好。再看一眼设备节点:
ls /dev/bmdev*有bmdev节点说明驱动层正常,接下来才能谈模型转换。
2.2 工具链安装:libsophon、sophon-driver、tpu-mlir
算能的环境分为两层:底层运行库叫libsophon,负责驱动和runtime;上层模型转换工具链叫TPU-MLIR,负责把PyTorch/ONNX模型转成bmodel。两个都必须装,缺一不可。
推荐用官方提供的Docker镜像方式搭建开发环境,省去很多Python依赖冲突。大体流程是:
# 先安装驱动和runtime deb包 sudo dpkg -i sophon-driver_*.deb libsophon_*.deb libsophon-dev_*.deb # 更新固件(不同版本固件可能导致推理性能差异) sudo bm_update_installer -i sophon-firmware_*.deb # 拉取官方SDK Docker镜像 docker pull sophgo/tpuc_dev:latest # 启动容器,把模型目录挂进去 docker run -it --privileged \ --device=/dev/bmdev* \ -v /data/models:/models \ sophgo/tpuc_dev:latest /bin/bashTPU-MLIR在容器里一般已经装好,如果没有,手动pip安装即可。转换模型的流程后面细讲,这里先说一个经验:版本一定要对应。SDK版本和TPU-MLIR版本不匹配是新手最常见的坑,建议直接按官方Release页面里版本号一一对应下载,别混搭最新版TPU-MLIR配旧驱动。
2.3 离线环境下的依赖准备与网络隔离
很多做国产化的项目现场是没有外网的。我在实际部署中就遇过这情况:模型权重要提前从魔搭或Hugging Face下载好拷进去,pip依赖也得全部离线包化。这里有个比较省事的做法:
- 模型权重直接下到本地文件夹,整个拷到部署机;
- pip依赖在一台能联网的机器上执行
pip download -r requirements.txt -d ./packages/,再把packages目录拷进去,离线执行pip install --no-index --find-links=./packages -r requirements.txt; - huggingface的下载可以使用huggingface_hub的镜像站或者离线缓存方式,提前把模型文件放到
~/.cache/huggingface/hub里。
这个环节花费的时间往往比部署本身还长,别轻视。如果现场连拷文件都有要求,那就要先确认哪些目录允许写、哪些不允许,提前规划好数据目录。
3. qwen3-vl在1684x上部署:从PyTorch权重到bmodel
3.1 模型选型与量化策略
qwen3-vl系列有多个规格。在1684x 16GB版上,我们最终选择的是Qwen3-VL-8B,量化方式为INT8。为什么不是BF16?因为BF16光模型权重就约16GB,再加上KV cache和推理时的中间张量,16GB显存根本放不下,跑起来大概率OOM。INT8量化后权重约8GB,剩余8GB留给KV cache和计算缓冲,单路上下文长度控制在4K左右比较稳。
如果你手里是32GB显存的版本,可以放宽到BF16或混合量化,但单卡多路并发依然要警惕显存占用。下面给个对照表便于快速决策:
| 配置 | 权重体积(约) | 是否能跑16GB版 | 推荐场景 |
|---|---|---|---|
| BF16 | ~16GB | 勉强,极易OOM | 32GB显存版,追求精度 |
| INT8 | ~8GB | 可以,推荐 | 16GB显存版,兼顾精度与速度 |
| INT4 | ~4GB | 可以,较宽裕 | 对精度要求不高的场景 |
需要说明的是:这里的体积估算只算权重,实际部署还要加上激活值和KV cache,所以决定用哪种量化,最好是先在服务器上跑一遍压力测试,不要拍脑袋选。
3.2 转换流程:导出ONNX、TPU-MLIR编译、生成bmodel
1684x上不能用PyTorch直接跑,必须把模型转成bmodel。整个流程我拆成三步。
第一步:导出ONNX。TPU-MLIR的入口是ONNX模型,所以先从原始的Qwen3-VL PyTorch权重导出ONNX。这里特别提醒:Qwen3-VL是视觉语言模型,导出时要同时包含视觉编码器、投影层和LLM主体,不能只导LLM部分,否则图片输入会直接报错。
第二步:用model_transform.py转成MLIR。
model_transform.py \ --model_name qwen3vl-8b \ --model_path ./qwen3_vl_8b.onnx \ --input_shape "input_ids:1,128" \ --input_shape "attention_mask:1,128" \ --input_shape "pixel_values:1,3,864,864" \ --mlir qwen3vl-8b.mlir这里的input_shape要和模型导出的实际动态轴对齐,尤其是图片尺寸。我的经验是:图片分辨率先固定下来,不要试图在1684x上做任意分辨率推理。Qwen3-VL的视觉编码器对输入分辨率很敏感,TPU-MLIR对动态shape支持又不够灵活,最省事的做法是预处理阶段把所有图片统一resize到固定尺寸(我们用的是864x864),然后把pixel_values的shape写成固定值。虽然稍微损失一点细节,但换来的是稳定性和转换成功率。
第三步:compile后生成bmodel。
model_deploy.py \ --mlir qwen3vl-8b.mlir \ --chip bm1684x \ --quantize int8 \ --calibration_table qwen3vl_calib_table \ --model qwen3vl-8b-1684x.bmodel--quantize int8需要一张校准表,校准表是从一批真实样本中统计出来的,建议从知识库的真实文档截图里抽几百张图做校准,不要用网上随便下的图片集。
3.3 用sail推理库做本地验证
bmodel生成后,建议先用Python的sail库写个简单脚本验证,再考虑接进weknora。sail是算能提供的Python推理接口,加载bmodel非常直接:
import sophon.sail as sail engine = sail.Engine("./qwen3vl-8b-1684x.bmodel", device_id=0) # 用多模态对话接口做一次OCR测试这一步的重点不是跑满性能,而是确认“模型能输出流畅的中文答案”。我的做法是准备三张测试图:一个纯文字截图、一个表格截图、一个带复杂背景的产品照片。纯文字截图看OCR能力,表格截图看结构还原,复杂背景照片看抗干扰能力。这三张过了,再谈生产接入。
在1684x上,Qwen3-VL-8B INT8的生成速度大概在10到20个token每秒之间(具体取决于上下文长度和并发情况)。这个速度相比消费级GPU不算快,但做企业内部的问答机器人是够用的。
4. weknora本地部署:知识库服务的完整落地流程
4.1 为什么用weknora而不是又造一个RAG轮子
RAG看起来简单,无非是“向量化+相似度检索+拼prompt”,但真正做生产级,要考虑文档解析格式、切分粒度、混合检索权重、重排策略、引用溯源、权限控制,这一套自己从零写没有两三个月出不来。weknora的设计正好把这些都做成了配置项,比如关键词召回和向量召回怎么融合、重排时取多少条、答案生成时要不要强约束引用等,都能在后台或配置里调。
另外值得提的一点是它支持OIDC。做企业内部知识库,认证通常是强需求。weknora接OIDC后,能直接复用公司现有的统一登录,不用为知识库单独建一套账号体系。虽然这个需求在demo阶段不明显,但一上生产就是刚需。
4.2 Docker Compose部署与依赖组件
weknora本身只是一个服务,但完整跑起来还要依赖几个外部组件:一个PostgreSQL存元数据,一个向量数据库存 embeddings,一个对象存储或者本地目录存源文件。我们当时的简化方案是:PostgreSQL + Qdrant + 本地文件目录,全部用Docker Compose串起来。
下面是一个精简版compose示例,具体版本号以官方文档为准:
version: "3.8" services: postgres: image: postgres:16 environment: POSTGRES_DB: weknora POSTGRES_USER: weknora POSTGRES_PASSWORD: weknora123 volumes: - pg_data:/var/lib/postgresql/data qdrant: image: qdrant/qdrant volumes: - qdrant_data:/qdrant/storage weknora: image: weknora/weknora:latest depends_on: - postgres - qdrant environment: DB_HOST: postgres DB_PORT: 5432 DB_NAME: weknora DB_USER: weknora DB_PASSWORD: weknora123 VECTOR_DB_TYPE: qdrant VECTOR_DB_URL: http://qdrant:6333 MODEL_PROVIDER__BASE_URL: http://your-server:8000/v1 MODEL_PROVIDER__MODEL_NAME: qwen3vl OIDC_ENABLED: "false" ports: - "8080:8080" volumes: - ./storage:/data/storage volumes: pg_data: qdrant_data:启动后访问8080端口,用初始化脚本创建管理员账号。第一次登录后,先把“文档解析”和“切分策略”两个配置确认一遍,这两项直接影响后面的检索效果。
4.3 模型服务与认证配置
weknora需要一个生成模型来完成问答。因为我们已经在1684x上把qwen3-vl跑成了OpenAI兼容接口,所以weknora这边的配置非常简单:填一个base_url,填一个model_name。如果1684x上的推理服务没有开启鉴权,api_key随便填一个值占位即可。
如果你希望整套链路完全离线,还有一个隐藏配置要留意:embeddings模型和rerank模型。weknora默认可能请求外部接口,如果没有内网服务,就必须配一个本地嵌入模型,比如bge-m3,以及一个本地重排模型,比如bge-reranker-v2-m3。这两个模型不需要跑在1684x上,普通CPU服务器也能跑,但最好用Docker封装成独立服务,别和weknora塞在同一个进程里。
关于OIDC,如果暂时不想接入,把OIDC_ENABLED设为false即可;需要接入时,关键是配好issuer、client_id和client_secret,并确保OIDC服务端的回调地址正确。这块如果配错,经常出现“登录成功后回跳地址404”的问题。
5. 把两者连起来:一份多模态RAG流水线的联调记录
5.1 图文混排文档的处理策略
把weknora的模型指向qwen3-vl之后,只完成了一半。真正的难点是:知识库里那些图片、扫描件、表格截图,怎么变成可检索的文本。
我们的做法是在上层加一个文档预处理服务。它的职责有三个:
- 对纯文本文档,走常规解析流程:分页、分块、生成标题层级。
- 对PDF里的扫描页和图片,调用1684x上的qwen3-vl视觉接口,将图片转成Markdown描述,比如表格识别后直接转成Markdown表格。
- 对包含图片的富文本文档,单独把图片切片出来走视觉理解,再把得到的文本描述与周围正文合并成一个chunk。
这样处理之后,知识库里检索到的每个chunk,本质上都是纯文本,weknora的向量召回和关键词召回都能正常工作。而原始的图片文件我们存到weknora的附件字段里,在最终回答的引用卡片中展示给用户。这就是那句热词“RAG知识库能存储图片嘛”的落地答案:向量库里存的是图片的文本描述,源文件还要单独存储。
5.2 检索与生成的完整调用链
联调阶段,我会先用一个Python脚本把链路跑通,再接业务接口,避免来回改:
import requests # 1. 上传文档到知识库 resp = requests.post( "http://weknora-server:8080/api/documents", json={"file_url": "http://storage/file.pdf", "doc_type": "pdf"} ) doc_id = resp.json()["id"] # 2. 在weknora里发起检索 query = "这份合同里关于违约金的条款是什么?" resp = requests.post( "http://weknora-server:8080/api/chat", json={ "knowledge_base_id": kb_id, "question": query, "stream": False, } ) answer = resp.json()["answer"] references = resp.json()["references"]weknora内部会先做一次混合检索(向量召回+关键词召回),再用重排模型对候选chunk排序,最后把top-k的chunk和用户问题一起塞给qwen3-vl生成回答。这里有一个可以调的参数:检索返回条数。默认是5条,但实际使用中,如果是多轮推理或对比类问题,5条往往不够,我们调到8~10条才稳定。
5.3 检索质量与回答效果的评测方法
联调不是“能跑通就行”,要有一个可量化的标准。我们当时建了一个只有30条问题的评测集,覆盖三类:事实型问题(“合同到期时间是哪天”)、归纳型问题(“这份文档里提到哪三种付款方式”)、图片型问题(“截图里的表格中第二行数据是多少”)。
评测指标取两个:
| 指标 | 含义 | 目标值 |
|---|---|---|
| Hit Rate | 真实来源是否出现在top-k结果中 | 不低于85% |
| Answer Accuracy | 回答内容与标准答案一致性 | 90%以上 |
我们第一轮跑下来,图片型问答准确率只有70%左右,排查下来发现是图片预处理时分辨率压得太低,表格里的数字识别错了。于是把图片resize从864x864改成1152x1152,准确率立刻回到92%。所以说,多模态RAG的瓶颈往往不在模型,而在预处理。
5.4 应对RAG瓶颈的常见思路
RAG做深了,你一定会遇到“召回准了但答案不对”“检索结果多但互相矛盾”这类情况。这里有两招实测有效。
第一招:给知识库加层级结构。纯向量的RAG分不清“这篇文档是张三的述职报告”和“这篇文档是公司考勤制度”之间的业务关系。如果你能在文档上传时打上标签或目录,weknora的检索就可以先按标签过滤再按向量排序,效果翻倍。
第二招:关注KG与RAG的关系。知识库问答不等于只有向量检索一条路。如果业务问题经常涉及多跳关系,比如“A项目的负责人同时也是B项目的评审人”,纯向量RAG答不上来,而知识图谱(KG)是更好的建模工具。weknora这类RAG服务擅长的是非结构化文本的语义检索;KG适合的是结构化关系的推理。我们的经验是:两类知识库共存,先查KG再查RAG,最后统一由qwen3-vl组织语言。这是热词里“rag知识库和结构知识库区分以及应用场景”的实践答案。
6. 常见问题与避坑实录
6.1 TPU-MLIR转换失败与算子不支持
这是我们踩得最深的坑。Qwen3-VL的视觉编码器在某些PyTorch版本下导出ONNX时会出现算子不兼容,报错信息五花八门,最常见的是“Unsupported op: xxx”。
排查思路分两条线。第一条:检查PyTorch版本,有些算子在2.1.0版本导出的ONNX和2.2.0不一样,换一个版本可能就过了。第二条:升级TPU-MLIR版本,算能社区对于Qwen系列的支持更新很频繁,往往新版本就能覆盖旧版本不支持的算子。
如果升级之后还不行,终极方案是拆分部署:视觉编码器单独转一个bmodel,LLM主体单独转一个bmodel,推理时先用视觉编码器把图片编码成embedding,再喂给LLM。这个方案能绕开绝大多数兼容问题,但工程复杂度高一些。
6.2 显存溢出与并发控制
16GB版跑8B INT8,单路没问题,一并发就容易OOM。排查时先用bm-smi盯显存曲线,发现峰值接近16GB就要调整了。
我们最终的参数是:最大并发2路,单路最大输入token数1500,输出最大token数800,流式返回。这个配置在真实业务里够用,因为知识库问答的query通常不会太长,回答也不需要长篇大论。如果并发要求更高,建议上32GB版本,或者把量化降到INT4。
6.3 weknora检索效果差
如果发现“问什么问题都答非所问”,多半不是模型问题,而是检索链路问题。按下面的顺序排查:
- 嵌入模型是不是弱。bge-m3和bge-large-zh-v1.5差距在业务场景里非常明显,别贪图小尺寸模型省资源。
- 切分chunk是不是太大。一段1000字和一个段100字的召回效果完全不同,先试512个字符左右切分,再按实际文档结构调整。
- 重排有没有开。weknora支持为候选chunk做rerank,不开的话,top-k结果里经常混入不相关段落。
6.4 OIDC配置回跳问题
OIDC最常见的错误是回调地址不对。很多同学配了redirect_uri但忘了在weknora反向代理层同步配置,导致登录成功后跳转到一个不存在的路径。解决方法是:在Nginx层把callback路径和weknora的路径统一起来,别用自定义路径。另一个坑是OIDC服务端的时间偏差,如果企业内网NTP没同步,JWT的exp校验会报错,看起来像认证失败,实际是时间差。
6.5 图片知识库的存储边界
“RAG知识库能存储图片嘛”这个问题我们也纠结了很久。最终的结论是:weknora这类RAG服务的检索主体是文本,图片必须以“文本描述”的形态进入向量库。图片本身存MinIO或本地磁盘,在搜索结果里作为引用附件展示。对于图片里的文字信息,交给qwen3-vl做OCR;对于图片里的图表意图,交给qwen3-vl做描述生成。这样既保证了检索效果,又保留了原始证据,用户点开引用卡片看到的还是原始图片。
7. 个人经验与后续扩展
如果你是从零开始复现这个项目,我建议把整个流程拆成两条独立战线:一条在1684x上做模型转换和视觉能力验证,另一条在Mac或普通服务器上用Ollama把weknora先跑通。两条战线都确认没问题后,再到1684x上做替换,能省下大量排障时间。
我实际操作下来最大的体会是:这套方案的难点不在“能不能跑”,而在“最终效果能不能达到业务要求”。很多团队把bmodel转换成功、weknora能出答案就宣告完成,结果一上线就被业务吐槽“答非所问”。真正要花时间的,是打磨预处理管线、调检索参数、建评测集这三个看起来不起眼的工作。
后续如果你想继续扩展,有两个方向很值得试。一个是把知识库的结构化能力补上,引入KG做实体关系管理,和weknora的向量检索形成互补;另一个是把1684x上那一路qwen3-vl的能力外扩成独立的“多模态理解服务”,不只给知识库用,任何需要OCR、图表理解、图片描述的模块都可以统一调用它。把推理能力沉淀成一个服务,比每次绑死在某个项目里更划算。