ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

国产AI加速卡上构建多模态RAG:Qwen3-VL+WeKnora实战

国产AI加速卡上构建多模态RAG:Qwen3-VL+WeKnora实战 从公司内部一堆扫描件和产品图里找答案这件事过去一直很“无解”。纯文本知识库做RAG检索遇到PDF里的截图、盖章扫描件、业务流程时序图就歇菜。把图变成文字再把文字和用户问题一起塞给模型听起来容易真落地时牵扯到AI加速卡、多模态大模型、知识库三个环节每一个都能卡你两三天。最近我刚好拿到一张算能1684x加速卡把Qwen3-VL部署上去再接上腾讯开源的RAG知识库WeKnora两套系统打通后效果超出预期。整个过程不算轻松但思路捋顺后实际上就是一条“视觉理解 - 文本索引 - 向量检索 - 多模态回答”的流水线。这篇文章就把我实际操作中的方案、代码、踩过的坑一次性写清楚给想搞多模态知识库的团队做参考。1. 项目定位与整体思路1.1 三个主角分别是什么先花一分钟对齐名词不然下面流程会绕晕。算能1684x是国产AI推理加速卡定位类似英伟达的T4/A10但驱动、推理工具链完全是另一套生态。它支持PyTorch模型到底层指令集的转换编译出来的产物叫bmodel跑起来功耗和成本都低。对我来说最实际的意义是不用去抢昂贵的GPU也能在机房或本地工作站上跑7B级别的大模型。Qwen3-VL是阿里通义千问系列的开源视觉语言模型能看图问答、解析OCR、理解截图里的图表逻辑。我们用的Qwen3-VL-7B版本配合1684x的算力基本能满足日常文档理解需求。7B这个体量既不会把显存撑爆又不至于智力不足。WeKnora是腾讯开源的RAG知识库框架主打“开箱即用”的企业问答场景。它内置文档解析、向量化、检索和生成链路支持OIDC认证也能挂接Dify这类工作流引擎。我们要做的不是重新发明框架而是把Qwen3-VL这个“外部眼睛”嫁接到WeKnora的文档处理管线里弥补它只能看文本的短板。1.2 为什么不能直接只用RAG框架RAG知识库最大的瓶颈不是检索算法不够快而是输入侧对非结构化视觉信息“睁眼瞎”。传统流程是上传PDF - 解析文字 - 切分chunk - embedding入库。遇到一份全是流程图和截图的PPT文字提取出来少得可怜检索阶段根本找不到视觉内容问它“架构图里用户服务挂在哪个网关下”结果自然是答非所问。解决思路无非两种一种是在解析阶段把图片提前翻译成文字描述让RAG索引到视觉信息另一种是在问答阶段如果用户上传了图片直接把图片和检索到的文本一起交给多模态模型。单纯用一个思路不够完整搭项目时我把两条链路都做了离线解析用Qwen3-VL做“图片转文字”在线问答用Qwen3-VL做“多模态上下文理解”。整个系统物理上分三层承载层算能1684x加速卡负责跑Qwen3-VL推理。知识库层WeKnora负责文档管理、切分、向量化、检索。适配层一个FastAPI中间服务负责把上游请求转成两个系统各自看得懂的命令。这套构架拆开后每个部分都可以单独替换比如以后想把Qwen3-VL换成更大的模型或者把WeKnora换成其他RAG框架都不会动到整条流水线。2. 准备阶段硬件环境与模型转换2.1 算能1684x环境初始化先说说硬件这边怎么准备。1684x不是插上就能跑需要装完整的BMNNSDK开发环境。我用的操作系统是Ubuntu 22.04驱动安装包的名称一般是sophon-driver和sophon-libsophon这类具体版本号一定要和卡上的芯片固件对应。安装完成之后用命令行验证# 查看设备是否识别 bm-smi # 能看到类似这样的输出一个名为 BM1684X 的加速设备显存 16GBbm-smi就是1684x的“任务管理器”能看到显存占用、温度、运行中的任务。如果这里都看不到卡别急着往下走大概率是PCIe驱动没加载重启一下或者重装驱动多半能解决。SDK装完后还需要准备推理运行环境。算能官方提供了Docker镜像docker.io/sophgo/sophonsdk_24.04.01之类的拉下来后把卡设备映射进容器docker run -it --privileged \ --device /dev/bm1684x0 \ -v /opt/models:/models \ sophgo/sophonsdk_24.04.01:latest \ bash注意1684x设备节点名称要和你机器里的实际名称一致有的机器直接是/dev/bm-soh开头的多个节点最好先ls /dev/bm*确认一遍。到这里环境只算就绪模型还跑不起来。因为1684x不像GPU那样直接用PyTorch加载模型必须先把模型转成bmodel。2.2 Qwen3-VL模型转换与部署模型转换是整个项目里最磨人的一步。Qwen3-VL-7B转换到1684x官方已经有适配脚本但你必须先把HuggingFace上的模型权重下载下来然后通过算能提供的bmnetp工具链完成转换。一般来说流程是这样# 下载原始权重huggingface-cli / modelscope均可 git lfs install git clone https://huggingface.co/Qwen/Qwen3-VL-7B-Instruct # 使用算能提供的转换脚本把fp16模型转成bmodel python convert_to_bmodel.py \ --model_dir ./Qwen3-VL-7B-Instruct \ --output_dir ./bmodel_qwen3_vl \ --quantize int8这里有个细节要认真对待直接转fp32或者fp16bmodel文件会非常大1684x存储带宽扛不住推理速度会很感人。我实际用的是INT8量化体积大概从14GB压到4.5GB左右精度损失在可接受范围内尤其是文档理解这类“语义识别”任务几乎感知不到差异。转换过程中如果报算子不支持不用慌。先看blade源码里是否有该算子的CPU回退实现如果sophon-sdk版本过旧建议升级到最新版如果某个模块确实不支持可以把该模块留在CPU上跑其他模块放到NPU上通过分模块部署策略绕开。模型转换完成之后一般通过本地推理服务暴露HTTP接口。算能的Python SDK提供了BMNNSDK2可以直接加载bmodel做推理我封装了一个简单的Qwen3VLInfer类后面章节会给出Simplified版本。2.3 WeKnora知识库服务搭建再来看知识库。WeKnora的部署方式比较友好官方提供docker compose一键启动方案。git clone https://github.com/Tencent/weknora.git cd weknora cp .env.example .env # 修改默认密钥、数据库账号密码 docker compose up -d启动后WeKnora会暴露两个端口一个是Web控制台一个是API服务。首次登录需要在控制台创建知识库、生成API Key。建议把API Key和知识库ID两个信息立刻存到本地配置文件里后面写适配层的时候要用。WeKnora本身已经内置了embedding模型选项可以选bge系列或者通过OpenAI兼容接口指定。因为我们要让知识库处理中文文档我选了基于BGE的向量模型中文语义检索效果明显好过通用模型。如果你的机器是纯CPU环境跑embedding建议使用较小的模型否则每次上传文档都会等很久。到这里三个组成部分各自就位1684x能跑Qwen3-VL推理了WeKnora能上传检索文档了。下一步是把它们串起来。3. 连接两边的核心实践中间层设计3.1 先画一条“不画图也看得懂”的流水线两个系统不会天然认识彼此需要中间层做翻译。这条流水线分为两条路径入库路径原始文档上传 - WeKnora解析文本 - 遇到图片/Qwen3-VL转成文字 - 合并文本 - WeKnora向量化索引。问答路径用户提问 - WeKnora向量检索 - 如果有图片同时把图片交给Qwen3-VL理解 - 检索文本视觉理解拼成上下文 - Qwen3-VL生成最终答案。简言之入口和出口都打在Qwen3-VL上中间永远走WeKnora。之所以不直接把Qwen3-VL输出的结果单独存成“图片知识库”是因为WeKnora有着成熟的chunk切分、相似度召回和权限管理。先让VL模型做预处理RAG框架负责记忆这样的分工最合适。3.2 入库路径让Qwen3-VL把图片翻译成可检索的文字入库这一步重点是控制好“翻译”的质量。我在适配层里写了一个image_to_text函数核心逻辑是调用1684x上的Qwen3-VL服务输入一张图片的base64流输出结构化的文本描述。为了不让整张文档的内存占用失控上传前先对图片做缩放和格式统一最大边控制在1024像素以内。看一段核心代码思路class Qwen3VLAdapter: def __init__(self, infer_url): self.infer_url infer_url # 1684x上跑的推理服务地址 def image_to_text(self, image_bytes: bytes, prompt: str) - str: # 将图片字节转为base64 import base64 b64 base64.b64encode(image_bytes).decode() payload { image: b64, prompt: prompt } # 调用本地推理服务 resp requests.post(f{self.infer_url}/v1/chat, jsonpayload) resp.raise_for_status() return resp.json()[text]重点在prompt设计。千万别用“请描述这张图片”这样泛泛的话要让它输出能被知识库有效检索的内容。我经过几轮打磨后固定了一套提示词请仔细分析这张图片。如果包含文字请把文字完整转录并说明文字上下文如果包含图表请用文字描述图表的结构、节点、箭头关系如果包含产品图请描述外观特征和可见标识。最后请总结出一段适合知识库检索的中文描述。这套prompt的最大好处是输出是“检索友好型”的包含了关键词、结构、实体名称后续召回准确率明显提升。普通PDF里嵌入的插图通过PyMuPDF一帧一帧抽出来调用image_to_text处理再把结果用\n[图片内容]\n的标记插入到文本段落中。这样WeKnora索引的文档就不是一段“有图无真相”的残缺文本而是一条带视觉注释的完整记录。3.3 问答路径检索和视觉理解同时工作问答路径比入库路径略复杂因为要处理“用户问题里带图”和“用户问题只带文字”两种场景。纯文字问题时流程是把用户问题发给WeKnora检索接口拿到top-k段落。把段落拼进提示词调用Qwen3-VL生成回答。返回答案并附上召回段落ID方便用户溯源。如果用户上传了图片说明他希望模型看图回答问题。那就要走多模态融合路线WeKnora还是正常做文本检索因为文档库里的相关文本能提供上下文。用户上传的图片也要交给Qwen3-VL理解生成图片描述。把“图片描述检索结果”一起作为上下文让Qwen3-VL综合推理图片本身也可以直接以base64形式传给模型视觉token和文本token会同时参与解码。后一种情况需要推理服务支持多张图片输入。Qwen3-VL原生是支持的但1684x转换出来的bmodel未必能开太多视觉token因此我实际使用时会把用户图片先压缩到448x448以内降低视觉token数量避免超出上下文窗口。调用链大致长这样def query(fastapi_request, qwen_adapter, weknora_client): # 1. 文本检索 text_hits weknora_client.search(fastapi_request.query, top_k5) # 2. 处理图片 image_desc images [] if fastapi_request.image: image_desc qwen_adapter.image_to_text( fastapi_request.image, 请用简洁文字描述这张用户问题配图 ) images.append(fastapi_request.image) # 3. 组装上下文 context \n\n.join([h[text] for h in text_hits]) full_prompt ( f知识库片段\n{context}\n\n f用户问题配图内容{image_desc}\n\n f请结合以上内容回答问题{fastapi_request.query} ) # 4. 多模态生成 return qwen_adapter.chat(full_prompt, imagesimages)这段代码是适配层的核心也是“连接”两个字最直接的体现。3.4 一个能直接改来用的FastAPI适配层我在项目里把适配层做成了独立微服务这样WeKnora和Qwen3-VL都不需要互相感知只要都认识HTTP协议即可。关键代码结构如下from fastapi import FastAPI, File, UploadFile, Form import uvicorn app FastAPI() app.post(/knowledge/upload) async def upload_knowledge( file: UploadFile File(...), ): # 1. 保存临时文件 # 2. 抽取图片并调用qwen_adapter.image_to_text() # 3. 把原始文本和图片描述合并 # 4. 调用weknora_client.upload_document(content, metadata) return {status: ok, doc_id: xxx} app.post(/query) async def query( question: str Form(...), image: UploadFile File(None), ): # 按上个小节逻辑执行问答 return {answer: xxx, citations: [{chunk_id: ...}]} if __name__ __main__: uvicorn.run(app, host0.0.0.0, port8000)这里有几个工程细节提醒一下上传接口一定要做超时控制Qwen3-VL处理一张图在1684x上大约要2-4秒如果同时传几十张图接口必须支持异步任务队列否则用户请求会直接超时。WeKnora的API有上传大小限制合并视觉描述后的文本可能很大建议按原始文档段落分批上传不要一次塞一大段。接口层面统一用JSON图片用base64传输省去文件流解析的麻烦。调试时先在终端里直接curl包请求测通再进web界面验证。这样能把问题锁定在某一层不用在FastAPI和浏览器之间反复猜。4. 实测效果与性能调优4.1 用一份“带图手册”跑通全流程我拿一份simulated的《售后服务操作手册》做测试PDF里既有文字又有截图比如“故障码查询流程图”“更换滤芯的爆炸图”。传统RAG直接索引这段PDF问“更换滤芯需要先关闭哪个阀门”因为爆炸图上的零件编号和阀门名称都在图片里知识点完全丢失。接入Qwen3-VL后入库时每张截图都被转成了结构化文本比如“图3更换滤芯爆炸图从左到右依次为旧滤芯、密封圈、进水管、出水阀拆卸顺序为关闭出水阀、逆时针旋转滤芯壳”。问题在对答阶段WeKnora能检索到这段视觉描述Qwen3-VL基于检索原文生成答案先关闭出水阀再逆时针旋转滤芯壳。整个流程的准确率直接在人工标注的30个问题上达到了80%比纯文本RAG的20%提升非常明显。当然效果不是白给的。入库速度会慢很多一份10页PDF如果嵌20张截图视觉描述处理可能额外花1分钟。但在知识库质量优先的前提下这个成本值得。4.2 性能和精度调优跑了一段时间后我遇到了三个典型性能问题挨个解决掉才算真正能用。第一个是并发。一家公司内部知识库不可能只有一个人用。但1684x跑7B模型并发一高就会排队。我采取的办法是在Qwen3-VL推理服务外面加一层请求合并把多张待理解图片攒到一个小批次里同时送进NPU推理。这样避免了单个请求不断切换NPU上下文整体吞吐量提升2倍左右。第二个是缓存。同一份文档里的图片可能昨天和今天重复入库或者两个相似文档包含相同截图。我在中间层加了一个基于图片感知哈希的缓存如果图片hash命中直接把上次生成的视觉描述拿回来用不再调用模型。实测上重复文档入库时间缩短约七成。第三个是上下文长度。Qwen3-VL的上下文如果被多张图片和大量检索文本撑爆就会丢信息。调优手段是控制每轮问答检索片段数量。WeKnora默认可能给10段我降到5段并把每段长度限制在256个中文字符牺牲一点召回丰富的度换回生成稳定性。如果企业场景回答质量优先可以放宽到8段但上下文长度必须控制在模型支持范围内。4.3 常见问题速查我把整条链路上最容易踩的坑整理成了表格当作速查表用。问题现象可能原因解决方案bm-smi看不到加速卡PCIe驱动未加载重启服务或重装sophon-driver模型转换时算子不支持SDK版本旧升级BMNNSDK到最新版本Qwen3-VL推理返回乱码bmodel量化过狠改用混精度或int8局部保留fp16WeKnora上传文档后检索不到图片内容没有将视觉描述合入文本检查入库前文本是否插入了[图片内容]块多轮对话时上下文混乱WeKnora未启用会话隔离开启OIDC用户级隔离或按session传递对话ID图片处理速度慢每张图都打满全分辨率上传前统一resize到1024px内检索结果相关度不高embedding模型不适合中文换bge-large-zh或在WeKnora中调相似度阈值这些坑基本覆盖了从硬件到软件的常见点尤其第一个和第四个属于那种当场面红耳赤排半天才发现的问题。5. 最后一点个人建议这套“1684xQwen3-VLWeKnora”的组合真正有价值的地方不是“部署成功”这个结果而是把两个本来毫无关系的AI组件通过一个薄薄的适配层连起来之后治好了RAG的视觉盲区。我个人在实际项目里最大的体会是不要一开始就追求大模型端到端先把“图转文、文检、文生答”三段式跑通后面再逐步增加更强的模型或更复杂的路由策略这样每步验证成本都低。另外如果要上生产环境建议把FastAPI适配层的任务队列换成Celery把大PDF处理丢到后台异步执行否则Web前端会一直转圈。权限方面WeKnora本身支持OIDC如果你所在团队已经有统一登录直接对接避免每个知识库单独配账号省下很多运维心力。最后的最后还可以朝两个方向扩展一是把Qwen3-VL的视觉描述结果回写进原始文档生成一份“带批注的可检索副本”二是让WeKnora的召回结果触发不同prompt模板比如查故障码时走“操作步骤模板”查产品参数时走“规格对比模板”。这些都是后面的事先把主线跑通这个项目就成功了大半。
返回列表