近期,DeepSeek-R1模型凭借其在数学推理、代码生成和自然语言推理Reasoning等领域的卓越性能,引起广泛关注,从AI界火出圈了。
很少有一种技术既能充当幕后扛旗的无名英雄,又能兼具网红明星的气质。
而DeepSeek做到了这一点。
作为提升生产力的利器,DeepSeek正吸引着众多个人开发者与企业用户的兴趣,他们纷纷寻求在本地环境中部署DeepSeek-R1模型,以充分利用其强大的AI能力。
随着大语言模型和RAG技术的快速发展,AI知识库系统正在全面渗透各行各业。
目前,我们已经在多个领域见证了其成功应用,包括跨境电商平台的智能客服、教育机构的个性化学习助手、医疗机构的诊断支持系统,以及餐饮行业的智能点餐服务等实际落地案例。
下面博主将详细介绍如何利用一张RTX 4090显卡在本地部署基于DeepSeek-R1(深度思考模型)和RAG技术的知识库系统(Knowledge Base System)。
该系统可广泛应用于智能客服、企业内部知识管理、学术研究及教育等多个领域,为企业智能化转型提供新动能,助力企业实现提质增效。
首先体验一下部署效果(图片来自MaxKB),一睹为快。
汽车跨境电商智能AI客服
生物医药AI客服
微信客服
钉钉机器人
配置飞书机器人
飞书机器人
深圳信用中心 AI 助手
华莱士智能AI客服助手
高校教学管理AI小助手
高校教学管理AI小助手-微信公众号
低代码可视化业务流程编排
创建函数脚本
后端应用监控
在数字化转型的浪潮下,个人和企业内部的信息管理面临着很大的挑战。传统的信息管理系统往往存在数据分散、检索效率低下、缺乏智-能化支持等问题。尤其是在面对海量非结构化数据时,企业难以快速提取有价值的信息,导致决策效率低下。
在专有领域,AI大模型LLM无法学习到所有的专业知识细节,因此在面向专业领域知识的提问时,无法给出可靠准确的回答,甚至会“胡言乱语”,这种现象称之为LLM的“幻觉”。
为此,实现AI大模型商用级知识库主要有两种方法:
第一种:通过专业知识的再训练或模型微调来增强模型能力,但这种方法需要大量标注数据和计算资源,成本高昂,对个人用户来说不太可行;
第二种:在向大模型提问时提供相关背景知识,使模型能够基于这些上下文信息生成更准确的回答。这种知识库构建方法的核心就是RAG(Retrieval-Augmented Generation,检索增强生成)技术。
RAG将信息检索与生成模型相结合,其核心流程是:
在生成回答前,先从外部知识库中检索相关信息,让模型能够引用训练数据之外的专业知识,使其在生成响应之前能够引用训练数据来源之外的权威知识库,再将检索结果与用户输入结合,指导生成模型输出更可靠的回答。
这种方法允许大型语言模型在不重新训练的情况下访问特定领域或组织的内部知识库,从而保持其输出的相关性、准确性和实用性。
检索增强生成(RAG)把信息检索技术和大模型结合起来,将检索出来的文档和提示词一起提供给大模型服务,从而生成更可靠的答案,有效地缓解大模型推理的“幻觉”问题。
作为商用级的知识库,不仅仅需要通过RAG和其它基础组件满足用户问题分类、敏感词检索等各类复杂场景需求,还能够内置强大的工作流引擎和函数库,支持业务流程编排,甚至是通过低代码实现可视化自定义工作流,从而指导大模型的工作过程,满足复杂业务场景下的需求,而这些则交由Agent智能体解决。
如果把AI大模型LLM比作学生的大脑,把RAG比作教材教辅,那么,就可以把Agent比作眼、耳、鼻、舌、身,协助LLM完成“应试教育”之外的“素质教育”。为了过五关斩六将,应对各种考试,学霸则需要LangChain这样的工程化框架,统筹以上各项能力的发挥。
实际上,LangChain提供了Models、Prompts、Indexes、Memory、Chains、Agents六大核心抽象,在降低系统实现复杂度的同时,提升系统整体的扩展性。它的能力边界只取决于LLM的智力水平和LangChain能提供的工具集的丰富程度。
一、整体框架
-
- 硬件:一张RTX 4090显卡(24GB显存)
- 大语言模型:DeepSeek-R1-Distill-Qwen-32B(Qwen 320亿参数Q4量化版DeepSeek-R1蒸馏模型)
- 模型推理框架:vLLM
- 向量模型:text2vec-base-chinese
- Agent智能体框架:LangChain
- 向量数据库:PostgreSQL / PG Vector
- 前端:Vue.js、LogicFlow
- 后端:Python、Django
二、本地部署DeepSeek
如何准确计算大模型所需的显存大小,是许多开发者经常遇到的问题。掌握GPU内存的估算方法,并据此合理配置硬件资源以支持模型运行,是确保大模型成功部署和扩展的关键。这一能力也是衡量开发者对大模型生产环境部署和可扩展性理解程度的重要指标。
要估算服务大型语言模型所需的 GPU 内存,可以使用以下公式:
-
- M是所需的 GPU 显卡内存(单位:GB千兆字节)。
- P是模型中的参数数量,表示模型的大小。例如,这里使用的 Llama 90B模型有 900 亿个参数,则该值将为 90。
- 4B表示每个参数使用 4 个字节。每个参数通常需要 4 个字节的内存。这是因为浮点精度通常占用 4 个字节(32 位)。但是,如果使用半精度(16 位),则计算将相应调整。
- Q是加载模型的位数(例如,16 位或 32 位)。根据以 16 位还是 32 位精度加载模型,此值将会发生变化。16 位精度在许多大模型部署中很常见,因为它可以减少内存使用量,同时保持足够的准确性。
- 1.2 乘数增加了 20% 的开销,以解决推理期间使用的额外内存问题。这不仅仅是一个安全缓冲区;它对于覆盖模型执行期间激活和其他中间结果所需的内存至关重要。
这个计算告诉我们,需要大约1610.4 GB 的 GPU 显存来为 16 位模式下具有 6710 亿个参数的满血版 DeepSeek-R1 大模型提供推理服务。
因此,单个具有 80 GB 显存的 NVIDIA A100 GPU 或者 H00 GPU 不足以满足此模型的需求,需要至少20张具有 80 GB 内存的 A100 GPU 才能有效处理内存负载。
此外,仅加载 CUDA 内核就会消耗 1-2GB 的内存。实际上,无法仅使用参数填满整个 GPU 显存作为估算依据。
如果是训练大模型,则需要更多的 GPU 显存,因为优化器状态、梯度和前向激活每个参数都需要额外的内存。
-
-
- DeepSeek-R1 (671B)
- DeepSeek-R1-Zero (671B)
- DeepSeek-R1-Distill-Llama-70B
- DeepSeek-R1-Distill-Qwen-32B
- DeepSeek-R1-Distill-Qwen-14B
- DeepSeek-R1-Distill-Llama-8B
- DeepSeek-R1-Distill-Qwen-7B
- DeepSeek-R1-Distill-Qwen-1.5B
-
-
-
- DeepSeek-R1-Zero:AI界的"极限探索者"
- 超强算力:6710亿参数(采用MoE架构,每个token可调动370亿参数)
- 创新训练:采用纯强化学习的端到端训练方式
- 突破性能:实现自我验证、长链推理等前沿能力
- 实战表现:在AIME 2024基准测试中取得71%的亮眼成绩
- DeepSeek-R1:AI界的"全能冠军"
- 强大算力:同样拥有6710亿参数的超强实力
- 独特训练:创新性采用多阶段混合训练方法
- 双重加持:结合监督微调冷启动与强化学习优化
- 卓越成就:在AIME 2024测试中达到79.8%的惊人准确率
- DeepSeek-R1-Zero:AI界的"极限探索者"
-
值得一提的是,DeepSeek团队通过知识蒸馏技术,成功将这些顶级模型的能力传承给更轻量级的版本。
这种创新方式不仅大幅降低了模型应用门槛,还提升了小型模型的推理能力,这正是DeepSeek在AI领域备受瞩目的重要原因之一。
DeepSeek-R1 蒸馏模型的几款小尺寸模型,是使用 DeepSeek-R1 生成的包含<think>...</think>
标记的思考链数据进行微调后的蒸馏版本,继承了 R1的推理能力。
毕竟博主囊中羞涩,为了完成这篇文章,选择 bartowski/DeepSeek-R1-Distill-Qwen-32B-GGUF的DeepSeek-R1-Distill-Qwen-32B大模型的4bit量化模型,根据上面的估算公式,仅使用1张具有 24 GB 内存的 4090 GPU 就可以运行完成本文所需的推理任务。
Qwen2.5-32B是一个通用的预训练语言模型,而DeepSeek-R1-Distill-Qwen-32B是基于Qwen2.5-32B使用DeepSeek-R1生成的包含<think>...</think>
标记的思考链数据进行微调后的蒸馏版本,因此继承了R1的推理能力。
这些微调数据包含问题拆解、中间推导等推理过程,通过强化学习让DeepSeek-R1-Distill-Qwen-32B对齐了R1生成推理步骤的行为模式。通过这种蒸馏机制,小型模型既能保持计算效率,又获得了接近大模型的复杂推理能力,这在资源受限场景下具有重要应用价值。
第一种方法是将个人电脑或租用的服务器配置成一台LLM推理服务器,本文采取的便是这种搭建方式。
第二种方法是利用OpenAI、Anthropic等大型公司的LLM服务,只需通过API密钥直接调用,而无需访问其官网。
第三种方法是在云平台上构建LLM服务器,并调用该服务器的API,常见的选择包括阿里云、腾讯云、百度云、AWS、Azure、谷歌云,甚至Modal和SkyPilot等服务。
在具体选择时,一定要综合考虑成本,谨慎挑选最适合的模型。
为编写本文,博主租了一台带有一张RTX 4090 GPU 显卡的服务器(花费大概10元左右就能完成本文案例的部署,当然还需要一些降低费用的小技巧,比如提前租用配置的服务器把模型文件下载到服务器,这样就可以节省很多费用💰,具体情况可以关注本公众号咨询博主)。
模型推理服务器的配置如下:
-
-
- GPU:RTX 4090,24GB显存
- CPU:16 核,Xeon(R) Platinum 8352V
- 内存:90 GB
- 系统盘:30 GB
- 数据盘:50 GB(用于存放模型文件、分词器文件)
-
🔥 四大天王对决:
1️⃣ SGLang - 大规模集群部署专家
2️⃣ Ollama - 轻量级玩家最爱
3️⃣ vLLM - GPU推理性能王者
4️⃣ LLaMA.cpp - CPU部署救星
💡 选择秘籍:
✅ 要极致性能 → 选vLLM
✅ 要简单易用 → 选Ollama
✅ 要集群部署 → 选SGLang
✅ 要CPU运行 → 选LLaMA.cpp
📊 性能对比:
-
-
- 推理速度:vLLM > SGLang > Ollama > LLaMA.cpp
- 易用程度:Ollama > LLaMA.cpp > vLLM > SGLang
- 硬件要求:vLLM(需GPU) > SGLang > Ollama > LLaMA.cpp
-
-
-
- 单卡或双卡4090用户 → 闭眼入vLLM
- 个人开发者 → Ollama快速上手
- 企业级部署 → SGLang更专业
-
目前市面上关于如何用Ollama拉取Q4或Q8量化模型进行本地推理的教程已经层出不穷,Ollama 确实以简单易用俘获了一大批开发者,但如果你和我一样,追求的是生产环境的稳定高效,那么Ollama可能就不是你的菜了!
为什么我最终选择了 vLLM?
-
-
- 🚀 性能才是硬道理:Ollama 在高并发和推理速度上,相比 vLLM 真的弱了不少,尤其是在吃 GPU 算力的场景下。
- 🏭 生产环境 Real Talk:如果你是认认真真要搞生产部署 DeepSeek-R1,vLLM 这种专为生产设计的框架才是更稳的选择。
- 💻 RTX 4090 最佳拍档:单卡 4090 想发挥最大威力?vLLM 的优化更到位!SGLang 那种大规模集群方案,对我们来说就太重了。
-
-
-
- Linux版本:Ubuntu 22.04.3 LTS
- CUDA版本:12.1
- cuDNN版本:8.9.0
- Python版本:3.10.8
- PyTorch版本:2.5.1+cu124
-
读者可以使用VSCode、Cursor或者其它SSH客户端连接到云服务器,然后使用以下命令安装CUDA和cuDNN。
安装前需要确保服务器上已经安装了NVIDIA驱动,可以使用以下命令查看是否安装了NVIDIA驱动:
$ nvidia-smi
# 添加 CUDA 存储库
$ sudo apt update
$ sudo apt install -y software-properties-common
$ sudo add-apt-repository ppa:graphics-drivers/ppa
# 下载并安装 CUDA 12.1
$ wget https://developer.download.nvidia.com/compute/cuda/12.1/12.1.0/local_installers/cuda_12.1.0_520.61.05_linux.run
$ sudo sh cuda_12.1.0_520.61.05_linux.run
在安装过程中,选择是否安装 NVIDIA 驱动(如果你已经有安装过,可以跳过)。
设置环境变量:
# 在 .bashrc 中添加 CUDA 路径
$ echo "export PATH=/usr/local/cuda-12.1/bin:$PATH" >> ~/.bashrc
$ echo "export LD_LIBRARY_PATH=/usr/local/cuda-12.1/lib64:$LD_LIBRARY_PATH" >> ~/.bashrc
$ source ~/.bashrc
要安装 cuDNN,首先需要从 NVIDIA 官方下载 cuDNN 8.9.0。
下载后,解压并安装 cuDNN。
# 假设 cuDNN 安装包下载到当前目录
$ tar -xzvf cudnn-12.1-linux-x64-v8.9.0.131.tgz
# 将 cuDNN 文件复制到 CUDA 路径
$ sudo cp cuda/include/cudnn*.h /usr/local/cuda-12.1/include
$ sudo cp cuda/lib64/libcudnn* /usr/local/cuda-12.1/lib64
$ sudo chmod a+r /usr/local/cuda-12.1/include/cudnn*.h /usr/local/cuda-12.1/lib64/libcudnn*
# 更新库路径
$ echo "export LD_LIBRARY_PATH=/usr/local/cuda-12.1/lib64:$LD_LIBRARY_PATH" >> ~/.bashrc
$ source ~/.bashrc
# 安装 Python 3.10
$ sudo apt update
$ sudo apt install -y python3.10 python3.10-dev python3.10-distutils
# 设置 Python 3.10 为默认版本
$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
# 检查 Python 版本
$ python3 --version
# 安装 pip
$ sudo apt install -y python3-pip
# 安装 PyTorch 2.5.1+cu124
$ pip install torch==2.5.1+cu124 torchvision torchaudio
# 验证安装
$ python -c "import torch; print(torch.__version__)"
# 检查 CUDA 版本
$ nvcc --version
# 检查 cuDNN 版本
$ cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2
$ python -c "import torch; print(torch.cuda.is_available())"
现在已经成功地安装了 CUDA 12.1、cuDNN 8.9.0、Python 3.10.8 和 PyTorch 2.5.1+cu124 环境。
可以使用以下命令查看GPU内存信息
$ nvidia-smi
如果安装正确,则可以看到类似如下的GPU内存信息
由于模型权重文件较大,不建议直连下载,根据博主的经验,按照下面的方式下载速度较快。
$ source /etc/network_turbo
$ pip install -U huggingface_hub
$ export HF_ENDPOINT=https://hf-mirror.com
$ huggingface-cli login
$ huggingface-cli download bartowski/DeepSeek-R1-Distill-Qwen-32B-GGUF --include "DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf" --local-dir ./
#【这里的./可替换为模型在本地的其它存放路径】
total 19386083
drwxr-xr-x 3 root root 4096 Feb 14 12:34 ./
drwxr-xr-x 4 root root 4096 Feb 14 21:00 ../
drwxr-xr-x 3 root root 4096 Feb 14 10:29 .cache/
-rw-r--r-- 1 root root 19851335840 Feb 14 12:34 DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf
正常情况下,使用vLLM加载上面DeepSeek模型GGUF权重文件后,不需要单独加载分词器文件,即可实现推理。
然而,博主在本地部署DeepSeek后,发现DeepSeek的官方模型库的一个bug,需要通过修改DeepSeek的分词器配置文件予以解决。
由于这个月DeepSeek的官方模型库更新了分词器配置文件tokenizer_config.json里的chat-template,导致本地部署DeepSeek-R1-Distill-Qwen-32B后,
向模型提问时,模型只输出</think>
,没有开头的<think>
,从而导致前端应用无法正确识别DeepSeek的思考过程(Reasoning)。
还有一种解决办法是:
通过源码编译安装vLLM ,修改deepseek_r1_reasoning_parser.py文件中的相关代码,把解析think标签的部分更新一下;说不定还能顺便给 vLLM 提个 PR 呢
此外,网上有开发者建议建议使用DeepSeek基础模型的 tokenizer 而不是 GGUF 模型的 tokenizer。因为从 GGUF 转换 tokenizer 既耗时又不稳定,
尤其是对于一些词汇量较大的模型。
基于以上两点原因,博主单独下载DeepSeek分词器文件,并使用vLLM进行加载。
在浏览器进入DeepSeek的Hugging Face官方模型库页面,
打开Files and versions标签,找下面2个文件。
-
-
- tokenizer.json
- tokenizer_config.json
-
<think>\\n
删除掉即可。<think></think>
标签,才能启动DeepSeek模型的推理Reasoning能力。后文的提示词中有所体现,请读者留意。{
"add_bos_token":true,
"add_eos_token":false,
"bos_token":{
"__type":"AddedToken",
"content":"<|begin▁of▁sentence|>",
"lstrip":false,
"normalized":true,
"rstrip":false,
"single_word":false
},
"clean_up_tokenization_spaces":false,
"eos_token":{
"__type":"AddedToken",
"content":"<|end▁of▁sentence|>",
"lstrip":false,
"normalized":true,
"rstrip":false,
"single_word":false
},
"legacy":true,
"model_max_length":16384,
"pad_token":{
"__type":"AddedToken",
"content":"<|end▁of▁sentence|>",
"lstrip":false,
"normalized":true,
"rstrip":false,
"single_word":false
},
"sp_model_kwargs":{},
"unk_token":null,
"tokenizer_class":"LlamaTokenizerFast",
"chat_template":"#此处内容省略"
}
$ scp -rP [端口号] [本地文路径] root@[远程服务器地址]:[远程服务器目录]
$ huggingface-cli download deepseek-ai/DeepSeek-R1-Distill-Qwen-32B --include "tokenizer.json" "tokenizer_config.json" --local-dir ./tokenizer
高效运行DeepSeek R1需要一个高性能的推理引擎,上文博主已分析过了 vLLM 是最佳选择之一,因为它具有优化的内存管理、快速的执行速度以及与 Hugging Face 模型的无缝集成。
本文使用 vLLM v1 在本地安装和运行 DeepSeek R1,以实现消费级 GPU 上的高速推理。
$ pip install vllm --upgrade
$ export VLLM_USE_V1=1
$ python -m vllm.entrypoints.openai.api_server \
--served-model-name bartowski/DeepSeek-R1-Distill-Qwen-32B-GGUF \
--model [DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf文件的存储路径] \
--trust-remote-code \
--host 0.0.0.0 \
--port 6006 \
--max-model-len 2048 \
--dtype float16 \
--enable-prefix-caching \
--enforce-eager \
--max_num_seqs 1 \
--api-key [API访问密钥] \
--tokenizer [分词器tokenizer的存储路径]
-
-
- served-model-name,指定模型的名称。
- model,DeepSeek模型权重文件的路径。
- max-model-len,模型上下文长度。如果未指定,将继承模型自身配置。将max_model_len设置为2048、4096或8196,以找到在没有错误的情况下工作的最大值。如果取值过大,你可能会遇到OOM错误。
- max_num_seqs,用于配置同时处理多少个请求;由于这将使内存使用量增加一倍,因此最好将其设置为1。
- trust-remote-code,加载HuggingFace自定义代码时必须启用。
- host和port,可以根据你的服务器机器的具体情况进行配置。
- dtype,权重和激活参数的数据类型,用于控制计算精度,常用float16/bfloat16。
- enforce-eager,用于启用 eager 模式,加快推理速度。
- enable-prefix-caching,重复调用接口时缓存提示词内容,以加快推理速度。例如,如果你输入一个长文档并询问有关它的各种问题,启用该参数将提高性能。
- api-key,用于设置API访问秘钥,保证自己的DeepSeek不会被随便访问,自行设置一个字符串即可;当后文部署的知识库应用访问DeepSeek推理服务器时,会检查其请求头中的 API 密钥。
- tokenizer,如前文所述,单独下载的DeepSeek分词器存储目录。
- tensor-parallel-size,指定GPU数量,本文只用单卡,因此不用设置此参数。
-
vllm serve
命令,加载DeepSeek-R1-Distill-Qwen-32B-GGUF模型。$ vllm serve
[DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf文件的存储路径] \
--served-model-name bartowski/DeepSeek-R1-Distill-Qwen-32B-GGUF \
--trust-remote-code \
--host 0.0.0.0 \
--port 6006 \
--max-model-len 2048 \
--dtype float16 \
--enable-prefix-caching \
--enforce-eager \
--max_num_seqs 1 \
--api-key [API访问密钥] \
--tokenizer [分词器tokenizer的存储路径]
在执行如上所述的命令后,希望没有错误发生。
加载完毕后出现如下信息说明服务成功启动。
INFO 02-19 19:49:36 gpu_model_runner.py:872] Loading model weights took 18.5326 GB
INFO 02-19 19:49:45 kv_cache_utils.py:407] # GPU blocks: 376
INFO 02-19 19:49:45 kv_cache_utils.py:410] Maximum concurrency for 2048 tokens per request: 2.94x
INFO 02-19 19:49:45 core.py:91] init engine (profile, create kv cache, warmup model) took 8.87 seconds
INFO 02-19 19:49:45 api_server.py:756] Using supplied chat template:
INFO 02-19 19:49:45 api_server.py:756] None
INFO 02-19 19:49:45 launcher.py:21] Available routes are:
INFO 02-19 19:49:45 launcher.py:29] Route: /openapi.json, Methods: GET, HEAD
INFO 02-19 19:49:45 launcher.py:29] Route: /docs, Methods: GET, HEAD
INFO 02-19 19:49:45 launcher.py:29] Route: /docs/oauth2-redirect, Methods: GET, HEAD
INFO 02-19 19:49:45 launcher.py:29] Route: /redoc, Methods: GET, HEAD
INFO 02-19 19:49:45 launcher.py:29] Route: /health, Methods: GET
INFO 02-19 19:49:45 launcher.py:29] Route: /ping, Methods: POST, GET
INFO 02-19 19:49:45 launcher.py:29] Route: /tokenize, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /detokenize, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /v1/models, Methods: GET
INFO 02-19 19:49:45 launcher.py:29] Route: /version, Methods: GET
INFO 02-19 19:49:45 launcher.py:29] Route: /v1/chat/completions, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /v1/completions, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /v1/embeddings, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /pooling, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /score, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /v1/score, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /rerank, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /v1/rerank, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /v2/rerank, Methods: POST
INFO 02-19 19:49:45 launcher.py:29] Route: /invocations, Methods: POST
INFO: Started server process [3433]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:6006 (Press CTRL+C to quit)
nvidia-smi
查看显存占用情况。1. 模型权重内存 (主要占用)
-
-
-
- 模型权重文件加载后占用 18.5326 GB
- 这是量化后的 Q4_K_M 格式模型,原始 32B 模型如果是 FP16 格式约需 64GB,量化后显存占用大幅降低
- 包含 embedding 层、transformer 层参数等
-
-
-
-
-
- 分配了 376 个 GPU blocks 用于存储 KV cache
- 每个 block 存储的 token 数由
block_size
参数决定(默认 16) - 计算公式:
KV Cache 内存 = 2 * num_layers * num_heads * head_dim * seq_length * batch_size * dtype_size
- 支持 2048 token 请求时的最大并发达到 2.94 倍;这部分内存在生成过程中按需分配,可大幅提高推理效率,但也会增加总体显存占用。
- 当前配置下约占用 3-4 GB
-
-
-
-
-
- 中间激活值:约 1-2 GB
- 系统保留内存:约 0.5-1 GB
-
-
因此,整个 GPU 内存消耗可以看作是静态的模型参数和动态计算缓存(KV cache)两大块的合并。
确保 GPU 显存充足不仅要满足模型权重加载的 18.5GB,还需要预留足够空间应付 KV cache 及其他运行时需求。
使用vLLM成功加载模型后,现在就可以使用DeepSeek R1模型了。
-
通过浏览器查看当前服务接口文档
通过浏览器访问 http://localhost:6006/docs 查看 vLLM 支持的 DeepSeek API 接口
$ curl http://localhost:6006/v1/models -H "Authorization: Bearer ds-key-001"
{
"object":"list",
"data":[
{
"id":"/root/autodl-fs/model/deepseek/bartowski/DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf",
"object":"model",
"created":1739969336,
"owned_by":"vllm",
"root":"/root/autodl-fs/model/deepseek/bartowski/DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf",
"parent":null,
"max_model_len":2048,
"permission":[
{
"id":"modelperm-f96541eb4c5849baa7d25b138009a094",
"object":"model_permission",
"created":1739969336,
"allow_create_engine":false,
"allow_sampling":true,
"allow_logprobs":true,
"allow_search_indices":false,
"allow_view":true,
"allow_fine_tuning":false,
"organization":"*",
"group":null,
"is_blocking":false
}
]
}
]
}
$ curl http://localhost:6006/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ds-key-001" \
-d '{
"model": "bartowski/DeepSeek-R1-Distill-Qwen-32B-GGUF",
"messages": [
{"role": "user", "content": "问题:玄武门之变结束的当天,李世民在深夜写下一段独白,他会写什么?要求:先思考,然后按照以下格式回答用户的问题:<think>推理内容</think>内容"}
],
"temperature": 0.6
}'
注:如前文所述,为了启用DeepSeek的推理能力,需要在提示词中的问题后面增加以下内容:
先思考,然后按照以下格式回答用户的问题:
<think>推理内容</think>
内容
使用建议:
为了获得预期的性能,建议在使用 DeepSeek-R1 系列模型(包括基准测试)时,遵循以下配置:
-
-
-
- 将温度设置在 0.5-0.7 范围内(推荐 0.6),以防止无休止的重复或语无伦次的输出。
- 避免添加系统提示;所有指令都应包含在用户提示中。
- 对于数学问题,建议在提示中包含如下指令:“请逐步推理,并将最终答案放在 \boxed{} 中。”
- 在评估模型性能时,建议进行多次测试并对结果取平均值。
-
-
INFO 02-19 20:55:19 loggers.py:72] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Waiting: 0 reqs GPU KV cache usage: 0.0%.
INFO 02-19 20:55:24 loggers.py:72] Avg prompt throughput: 9.2 tokens/s, Avg generation throughput: 36.8 tokens/s, Running: 1 reqs, Waiting: 0 reqs GPU KV cache usage: 4.5%.
INFO 02-19 20:55:29 loggers.py:72] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 38.7 tokens/s, Running: 1 reqs, Waiting: 0 reqs GPU KV cache usage: 7.2%.
INFO: 127.0.0.1:54702 - "POST /v1/chat/completions HTTP/1.1" 200 OK
-
-
-
- Avg prompt throughput 反映了 prompt 处理的速度,数值可能因 prompt 大小及初始处理开销而波动;
- Avg generation throughput 则表示生成输出过程中的 token 速率,数值较高表明生成效率较好;
- Running reqs 和 Waiting reqs 分别展示了当前正在处理与等待处理的请求数量,当前系统负载较低(运行1个请求、无等待请求);
- GPU KV cache usage 表示生成过程中用于缓存的 GPU 内存占用率,随生成 token 数的累计逐步增加,反映模型在动态生成时对内存的使用情况。
-
-
-
-
-
- Avg prompt throughput(平均提示吞吐率)
- 含义:该指标表示系统处理输入 prompt 时,单位时间内处理的 token 数量,通常以 tokens/s 为单位。
- 日志信息分析:
- 日志中显示有一次为
0.0 tokens/s
,另一处为9.2 tokens/s
。 - 这说明在某个时刻(例如刚接收到请求时)prompt 的 token 数处理速度可能较低,而在实际载入或处理 prompt 数据后,平均每秒可以处理大约 9.2 个 token。
- 日志中显示有一次为
- Avg generation throughput(平均生成吞吐率)
- 含义:该指标描述模型在生成响应(即生成 tokens 时)的 token 生成速度,同样以 tokens/s 为单位。
- 日志信息分析:
- 日志中分别显示
36.8 tokens/s
和38.7 tokens/s
。 - 这表示模型在生成阶段的算力较强,平均每秒能产生大约 37 个 token,反映了模型生成阶段的高效性。
- 日志中分别显示
- Running reqs(正在运行的请求数量)
- 含义:这是当前系统中正处于活跃状态、正在被处理(例如生成响应)的请求数。
- 日志信息分析:
- 日志中状态显示为
Running: 1 reqs
,这表明当前有一个请求在被系统积极处理。
- 日志中状态显示为
- Waiting reqs(等待中的请求数量)
- 含义:表示目前在队列中等待处理、尚未启动生成的请求数量。
- 日志信息分析:
- 日志中显示为
Waiting: 0 reqs
,说明没有请求处于排队等待状态,系统的调度和资源分配都能及时处理进入的请求。
- 日志中显示为
- GPU KV cache usage(GPU KV缓存使用率)
- 含义:KV cache(Key-Value Cache)用于 Transformer 模型中保存此前计算得到的 key 和 value,以便在生成过程中复用这些信息,从而避免重复计算。该指标显示当前这部分缓存占用了 GPU 显存的百分比。
- 日志信息分析:
- 日志中先后显示
4.5%
和7.2%
的使用率。 - 这说明随着生成 token 数量的增加,KV cache 会逐渐占用更多的 GPU 显存,反映了生成过程动态累积缓存的结果。
- KV cache 的设计目的是为避免重复计算,同时支持更高的并发和长序列生成,因此其内存占用会随着生成任务的 token 数增加而逐步上升。
- 日志中先后显示
- Avg prompt throughput(平均提示吞吐率)
-
-
from openai import OpenAI
openai_api_key = "ds-key-001"#填写前文启动DeepSeek推理服务时设置的API秘钥
openai_api_base = "http://localhost:6006/v1"
model_name='bartowski/DeepSeek-R1-Distill-Qwen-32B-GGUF'
client = OpenAI(
api_key=openai_api_key,
base_url=openai_api_base,
)
prompt="""
问题:
玄武门之变结束的当天,李世民在深夜写下一段独白,你觉得他会写什么?
要求:
先思考,然后按照以下格式回答用户的问题:
<think>推理内容</think>
内容
"""
chat_response = client.chat.completions.create(
model=model_name,
messages=[
{"role": "user", "content": prompt},
],
temperature=0.6
)
print("Chat response:", chat_response)
<think>
好,我需要帮用户想一下李世民在玄武门之变结束当天深夜写下的独白。首先,我要了解玄武门之变的背景。这场变故发生在626年,李世民为了争夺皇位,发动了政变,杀死了自己的兄弟李建成和李元吉。
李世民当时的心理应该是复杂的。一方面,他成功地巩固了自己的地位,成为太子,为后来的皇帝铺平了道路。另一方面,他也背负了兄弟相残的罪名,内心一定有很多挣扎和反思。
所以,他的独白可能会反映出他的复杂情绪。他可能会表达对权力的渴望,同时也感到内心的压力和不安。他可能会提到自己曾经的犹豫,以及最终做出决定的艰难。
另外,李世民后来成为了唐太宗,开创了“贞观之治”,这说明他有治国理政的雄心。因此,他的独白中也可能流露出对未来的期望和责任感。
我还需要考虑独白的语气。他可能会用一种反思和严肃的口吻,既不掩饰自己的野心,也不回避内心的矛盾。
综合这些因素,我可以构思一段独白,既展现他的决心,也反映他的内心挣扎,同时为未来的统治埋下伏笔。
最后,我需要确保这段独白符合历史背景和人物性格,让读者能够感受到李世民当时的复杂心境。
</think>
## 《权力的代价》
寒风刺骨,我独自站在玄武门城楼上,看着地上斑驳的血迹。远处的宫灯明明灭灭,像是无数双眼睛在注视着这个改变命运的夜晚。
长叹息一声,我点燃了桌上的蜡烛。火苗在黑暗中摇曳,将我的影子投射在墙上,那影子比往日还要高大几分。
"权力...终究是要用鲜血来浇灌的。"我喃喃自语,指尖无意识地摩挲着腰间的佩剑。剑鞘上还残留着清晨的血迹,那是我亲手抹去的。
脑海中又浮现出那个场景:大哥李建成端着酒杯,脸上带着一贯的温和笑容;四弟李元吉在一旁促狭地笑着,撺掇大哥多喝几杯。我看着他们,突然觉得这兄弟情义太过虚伪可笑。
"你们以为我愿意这样吗?"我握紧了拳头,指甲深深陷入掌心。记得当年在东宫,我夜以继日地读书,谋划着如何治理国家,如何让百姓安居乐业。可他们,却只顾着争权夺利,甚至想要取我性命。
烛光下,我的影子忽明忽暗。我闭上眼睛,仿佛又回到了那个雨夜。雨声中,我听到大哥在后院与人密谋,要如何除掉我这个眼中钉。那一刻,我握紧了刀柄,浑身的血液仿佛都凝固了。
"对不起,大哥。"我低声说着,泪水在眼眶中打转。可转念一想,若我不先动手,今日的我早已是刀下之鬼。这天下,终究是要由有能力的人来治理。
桌上的茶已经凉了,我端起来一饮而尽。苦涩的味道在口中蔓延,像是这权力带来的所有辛酸。远处传来更夫的梆声,新的一天即将开始。
我站起身,拍了拍衣襟上的尘土。既然已经踏上了这条路,就再无回头的余地。我李世民,定不会辜负这大好河山,也定会让大唐盛世重现人间。
三、部署知识库应用
一般情况下,LLM大模型推理服务与应用服务不会部署在同一台服务器上。
由于知识库应用服务器会调用DeepSeek推理服务,因此需要将DeepSeek推理服务接口开放出来。
通过终端登录知识库应用服务器,执行以下命令后回车(注意:执行后不要关闭该终端窗口):
$ ssh -CNg -L 6006:127.0.0.1:6006 root@[DeepSeek服务器地址] -p [DeepSeek服务器端口号]
如询问yes/no请回答yes,并输入ssh服务的密码。
输入密码回车后无任何其他输出则为正常,如显示Permission denied则可能密码粘贴失败,请手动输入密码 (Win10终端易出现无法粘贴密码问题)
该命令用于建立SSH 隧道实现带端口转发的 SSH 连接,这样可以通过知识库应用服务器上的本地端口( localhost:6006 )访问DeepSeek推理服务器上的API接口。
这通常用于保护对DeepSeek服务器上运行的推理服务的访问,或访问那些无法直接从互联网访问的DeepSeek服务器。
为了测试DeepSeek推理服务接口是否成功开放,请在知识库应用服务器上打开浏览器,访问 http://localhost:6006/docs ,查看是否成功显示如前文所述的DeepSeek接口页面。
如在本地电脑上远程访问知识库应用服务器验证DeepSeek推理服务的的话,需要通过以下命令打开知识库应用服务器的防火墙端口。
$ sudo ufw allow 6006/tcp
前文完成DeepSeek推理服务的部署后,下一步就是部署和运行LLM应用——知识库。
知识库应用服务器配置要求:
-
-
-
- 操作系统:Ubuntu 22.04 / CentOS 7.6 64 位系统
- CPU/内存:4核/8GB 以上
- 磁盘空间:100GB
- Docker:27.3.1 以上
-
-
$ docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb
待所有容器状态显示为healthy后,可通过浏览器访问知识库。
打开浏览器,输入以下地址:
http://[知识库应用服务器地址]:8080
默认登录信息
-
-
-
- 用户名:admin
- 默认密码:MaxKB@123..
-
-
如在本地电脑上远程访问知识库应用服务器的话,需要通过以下命令打开知识库应用服务器的防火墙端口。
$ sudo ufw allow 8080/tcp
知识库应用的使用操作流程一般可分为四步:添加模型、创建知识库、创建应用、发布应用。
在高级编排应用中还可以通过函数库的功能,实现数据处理、逻辑判断、信息提取等功能,提供更加强大、灵活的能力。
模型管理用于对接供应商的大语言模型,支持对接主流的大模型,包括本地私有大模型(DeepSeek / Llama 等)。
除了DeepSeek等文本生成模型外,还支持向量模型、重排、语音识别、语音合成、视觉模型、图片生成等模型。
登录 MaxKB 系统后,在供应商列表中选择vLLM,然后点击【添加模型】,进入模型配置表单配置参数如下:
-
-
-
- 模型名称:MaxKB 中自定义的模型名称。
- 权限:分为私有和公用两种权限,私有模型仅当前用户可用,公用模型即系统内所有用户均可使用,但其它用户不能编辑。
- 模型类型:大语言模型。
- 基础模型:输入前文中部署的DeepSeek地址。
- API Key:输入前文启动DeepSeek推理服务时所设置的API Key。
-
-
如前文所述,由于DeepSeek的官方模型库的一个bug,需要通过修改DeepSeek的分词器配置文件予以解决。
这种解决bug的workaround方式,需要在提示词中显式增加<think></think>
标签,才能启动DeepSeek模型的推理Reasoning能力。
点击【创建应用】,输入应用名称,选择【高级编排】,点击【创建】,进入工作流编排页面。
-
-
- 基本信息:应用的基本信息设置节点,如应用名称、描述、开场白等设置,每个应用只有一个基本信息节点,不能删除和复制。
- 开始节点:工作流程的开始,每个应用只能有一个开始节点,不能删除和复制。
-
-
-
-
- AI对话:与AI大模型进行对话节点。
- 图片理解:识别并理解图片所包含的信息。
- 图片生成:根据提供的文本内容生成图片。
- 知识库检索:关联知识库,检索与问题相关分段的节点。
- 多路召回:使用重排模型队多个知识库的检索结果进行二次召回。
- 判断器:根据不同条件执行不同的节点。
- 指定回复:直接指定回复内容。
- 表单收集:通过表单的方式收集问答所需要的必要信息。
- 问题优化:AI对话的一种,设定了默认的角色和提示词,根据上下文优化问题。
- 文档内容提取:提取文档中的内容。
- 语音转文本:将音频转换为文本。
- 文本转语音:将文本转换为语音。
-
-
-
-
-
- 图片理解模型:图片理解模型名称。
- 角色设定:回答的角色或身份设定。
- 提示词:引导模型生成特定输出的详细描述。
- 历史聊天记录:
- 选择图片:待理解和分析的图片,默认为当前用户上传的图片文件。
- 返回内容:是否在对话中显示该节点返回的内容。
-
-
-
-
-
- AI回答内容{answer}:根据上传的图片以及角色、提示词等信息图片理解模型返回的内容。
-
-
-
-
-
- 图片生成模型:图片生成模型名称。
- 提示词(正向):引导模型生成积极、建设性输出的文字输入。
- 提示词(负向):不应该包含在生成输出中的元素、主题或特征的描述。
- 返回内容:是否在对话中显示该节点返回的内容。
-
-
-
-
-
- AI回答内容 {answer}:即图片生成模型根据文本输入生成的图片。
- 图片 {image}: 生成图片的详细信息。
-
-
-
-
-
- 知识库:待检索的知识库。
- 检索参数:包括检索模式、相似度阈值、引用分段数量以及最大引用字符数。
- 检索问题:一般是开始节点的用户问题。
-
-
-
-
-
- 检索结果的分段列表 {paragraph_list}:数组类型,指根据检索问题、检索参数进行检索后命中的分段列表,包含了分段的所有属性;
- 满足直接回答的分段列表 {is_hit_handling_method_list}:数组类型,指根据检索问题、检索参数进行检索后命中的分段中满足直接回答的所有分段列表,包含了分段的所有属性;
- 检索结果 {data}:字符串类型,指根据检索问题、检索参数进行检索后命中的分段内容;
- 满足直接回答的分段内容 {directly_return}:字符串类型,指根据检索问题、检索参数进行检索后命中的分段中满足直接回答的所有分段内容。
-
-
-
-
-
- 重排内容:待重排的多个内容,一般是多个不同知识库的检索结果。
- 检索参数:包括 score 阈值、引用分段数以及最大引用字符数。
- 检索问题:根据检索问题进行重排,一般为用户问题或问题优化后的结果。
- 重排模型:需要使用的重排模型名称。
-
-
-
-
-
- 重排结果列表 {result_list}:数组类型,指根据重排后的结果列表。
- 重排结果 {result}:字符串类型,指根据检索参数后的重排结果。
-
-
-
-
-
- 分支名称{branch_name}:每个判断分支的名称。
-
-
-
-
-
- 内容{answer}: 指定回复输出的内容。
-
-
-
-
-
- 表单输出内容:表单提示说明以及表单内容,可以单项输入,也可以输入多项信息。
- 表单配置:通过添加不同的组件进行表单的设计。
-
-
-
-
-
- 表单全部内容{form_data}:表单的全部内容。
- 表单全部内容将作为固定的输出,对于各个表单项也都进行参数化输出。
-
-
-
-
-
- AI 模型:大语言模型的名称以及参数控制。
- 角色设定:大语言模型回答的角色或身份设定。
- 提示词:引导模型生成特定输出的详细描述。
- 历史聊天记录:在当前对话中有关联的历史会话内容。例如,历史聊天记录为1,表示当前问题以及上一次的对话内容一起输送给大模型。
- 返回内容:是否在对话中显示该节点返回的内容。
-
-
-
-
-
- 问题优化结果 {answer}:通过大模型优化后的问题。
-
-
-
-
-
- 选择文档:即用户上传的文档,需要在基本信息节点开启对文件上传的支持。
-
-
-
-
-
- 文档输出 {content}:对用户上传文件进行的总结输出。
-
-
-
-
-
- 语音识别模型:选择语音识别模型的名称。
- 语音文件:即上传的音频文件,支持的格式包括:mp3、wav、ogg、acc。
- 返回内容:是否在对话中显示该节点返回的内容。
-
-
-
-
-
- 结果 {result}:语音转换后的文本内容。
-
-
-
-
-
- 语音合成模型:选择可用语音合成模型的名称。
- 文本内容:选择待合成的文本内容。
- 返回内容:是否在对话中显示该节点返回的内容。
-
-
-
-
-
- 结果 {result}:将文本转成的音频内容。
-
-
-
-
-
- 即函数的输入参数。
-
-
-
-
-
- 即函数的返回结果。
-
-
-
-
-
- 用户问题:对子应用的提问信息。
- 返回内容:开启后在对话过程中将子应用的返回结果。
-
-
-
-
-
- 结果:即子应用的返回结果。
-
-
四、知识库核心业务逻辑
-
-
-
- 前端:基于 Vue.js 和 LogicFlow 的工作流编辑器。用户可以在图形化界面中通过拖拽方式配置工作流程,比如设置“开始节点”、“问答节点”、“搜索节点”等,从而构建不同的使用场景。
- 后端:使用 Django 作为 Web 框架,通过 Python 实现后端服务。利用 LangChain 框架对接大语言模型(LLM)的推理服务,支持流式和非流式输出。
- 数据处理:使用 PostgreSQL 作为数据库,并借助 PG Vector 插件实现文档向量化存储与相似度搜索,为知识召回、语义匹配等提供支持。
- 模型服务:构建了对接 LLM 推理服务的各个模块,包括直接调用 LLM 模型生成回答、对搜索结果进行重新排序(reranker)、图像生成与理解等业务逻辑。各个节点模块之间通过共享上下文数据,实现了整个工作流的衔接。
-
-
root
├── apps # Django后端代码根目录
│ ├── application # 主要应用模块
│ │ ├── flow # 工作流引擎相关模块
│ │ │ ├── step_node # 工作流节点定义目录
│ │ │ │ ├── ai_chat_step_node # AI 对话节点相关代码 (例如: base_chat_node.py)
│ │ │ │ │ └── impl # AI 对话节点实现 (例如: base_chat_node.py 的具体实现)
│ │ │ │ ├── application_node # 应用节点相关代码 (例如: base_application_node.py)
│ │ │ │ │ └── impl # 应用节点实现 (例如: base_application_node.py 的具体实现)
│ │ │ │ ├── image_generate_step_node # 图像生成节点相关代码 (例如: base_image_generate_node.py)
│ │ │ │ │ └── impl # 图像生成节点实现 (例如: base_image_generate_node.py 的具体实现)
│ │ │ │ ├── image_understand_step_node # 图像理解节点相关代码 (例如: base_image_understand_node.py)
│ │ │ │ │ └── impl # 图像理解节点实现 (例如: base_image_understand_node.py 的具体实现)
│ │ │ │ ├── question_node # 问题节点/通用对话节点相关代码 (例如: base_question_node.py)
│ │ │ │ │ └── impl # 问题节点实现 (例如: base_question_node.py 的具体实现)
│ │ │ │ ├── reranker_node # 重排序节点相关代码 (例如: base_reranker_node.py)
│ │ │ │ │ └── impl # 重排序节点实现 (例如: base_reranker_node.py 的具体实现)
│ │ │ │ ├── search_dataset_node # 知识库搜索节点相关代码 (例如: base_search_dataset_node.py)
│ │ │ │ │ └── impl # 知识库搜索节点实现 (例如: base_search_dataset_node.py 的具体实现)
│ │ │ │ └── i_step_node.py # 工作流节点接口定义 (例如: INode 接口)
│ │ │ ├── impl # 工作流引擎核心实现
│ │ │ │ └── workflow_manage.py # 工作流管理类 (WorkflowManage)
│ │ │ ├── tools.py # 工作流工具类 (例如: Reasoning, 流式响应工具)
│ │ │ └── default_workflow.json # 默认工作流配置示例
│ │ ├── tools.py # 应用级别的工具模块
│ │ └── flow.py # 工作流定义或相关类
│ ├── common # 通用模块
│ │ ├── config # 通用配置 (例如: embedding_config.py 向量配置)
│ │ ├── db # 数据库相关模块
│ │ │ └── search.py # 数据库搜索相关功能 (例如: native_search)
│ │ ├── response # 通用响应处理 (例如: result 函数)
│ │ │ └── base_response.py # 基础响应类定义
│ │ ├── util # 通用工具函数
│ │ │ ├── common.py # 常用工具函数 (例如: bytes_to_uploaded_file)
│ │ │ └── file_util.py # 文件操作工具 (例如: get_file_content)
│ │ └── response.py # 响应处理相关
│ ├── dataset # 数据集/知识库相关模块
│ │ ├── models.py # 数据集模型定义 (例如: Document, Paragraph, DataSet, File)
│ │ └── serializers # 数据集序列化器 (例如: file_serializers.py FileSerializer)
│ │ └── file_serializers.py
│ ├── embedding # 向量嵌入相关模块
│ │ └── models.py # 嵌入模型相关定义 (例如: SearchMode)
│ ├── setting # 系统设置/模型设置模块
│ │ ├── models.py # 设置相关模型 (例如: Model, ModelCredential)
│ │ └── models_provider # 模型提供者相关
│ │ └── tools.py # 模型提供工具函数 (例如: get_model_instance_by_model_user_id, get_model_credential)
│ └── smartdoc # 项目根目录或智能文档相关模块
│ └── conf # 项目配置
│ └── __init__.py
└── manage.py # Django 项目管理脚本
ui/src # Vue前端代码根目录
├── assets # 静态资源目录
│ ├── images # 图片资源
│ └── styles # 全局样式文件 (如 CSS, SCSS)
├── components # 通用组件目录
│ ├── Common # 通用UI组件 (例如按钮,对话框,表格等)
│ ├── Specific # 特定业务场景组件 (例如工作流节点组件,图表组件)
├── layouts # 布局组件目录 (例如头部,侧边栏,页脚等)
├── views # 视图页面目录 (每个页面通常对应一个路由)
│ ├── Home.vue # 首页视图
│ ├── Workflow.vue # 工作流管理视图
│ ├── Dataset.vue # 数据集管理视图
│ └── ... # 其他业务视图页面
├── router # 路由配置目录
│ └── index.js # 路由配置文件
├── store # Vuex 状态管理目录 (如果项目使用了 Vuex)
│ ├── modules # 模块化的状态管理
│ │ ├── user.js # 用户状态管理模块
│ │ └── app.js # 应用全局状态管理模块
│ └── index.js # Vuex store 入口文件
├── utils # 工具函数库目录
│ ├── request.js # HTTP 请求封装
│ ├── helpers.js # 通用辅助函数
│ └── ... # 其他工具函数
├── App.vue # 根组件
├── main.js # 入口文件,Vue 应用初始化
└── index.html # HTML 模板文件
-
-
- LogicFlow 与 Vue.js
- 前端基于 Vue 组件构建,利用 LogicFlow 实现工作流图形展示与操作。开发者可以在页面上构建一个包含多个节点的流程图,每个节点对应后端中不同的业务处理逻辑。例如:
- “开始节点”:收集用户输入(提问、时间、全局信息等)。
- “AI Chat 节点”:调用 LLM 模型进行回答生成。
- “搜索数据集节点”:根据用户问题,利用向量匹配技术检索相关文档。
- 前端基于 Vue 组件构建,利用 LogicFlow 实现工作流图形展示与操作。开发者可以在页面上构建一个包含多个节点的流程图,每个节点对应后端中不同的业务处理逻辑。例如:
- 实时反馈与交互
- 前端利用 LogicFlow 提供的拖拽、缩放等功能,让用户可以直观地配置工作流,同时也支持响应式展示大模型返回的流式回答内容。
- LogicFlow 与 Vue.js
-
-
-
-
- 搜索数据集节点
- 在
BaseSearchDatasetNode
中,逻辑主要包括:- 根据用户问题,获取相应知识库的嵌入模型 ID。
- 调用嵌入模型将问题转为向量,并用 PG Vector 实现向量查询,检索与问题语义最相近的文档段落。
- 对检索结果进行过滤与排序,然后返回给下游节点使用。
- 在
- 问答节点 / AI Chat 节点
- 在
BaseChatNode
和BaseQuestionNode
中:- 先从节点上下文中获取历史对话记录,以及当前问题。
- 调用 LangChain 封装的 LLM 模型,通过
invoke
或stream
方法得到模型回答。 - 回答过程中支持流式输出,通过分块拼接内容,并对答案进行 token 计数、上下文保存等操作。
- 每个节点还实现了
save_context
方法,将节点执行信息(如回答、耗时、 token 数量)保存到上下文中,方便后续结果组装与调试。
- 在
- 重新排序节点 (Reranker)
- 在
BaseRerankerNode
中:- 将检索出的候选文档利用一个重排模型(如 SiliconCloud 或本地重排模型)进行压缩与重新排序,提高最终答案的相关性。
- 返回的结果通常经过进一步聚合,形成最终的答案或文档摘要。
- 在
- 图像生成与理解节点
- 例如在
BaseImageGenerateNode
与BaseImageUnderstandNode
中:- 根据用户描述调用相应模型生成图像,或对上传的图像进行理解处理。
- 生成的图像会经过文件处理,上传后返回 URL,供前端展示。
- 例如在
- 应用节点
- 应用节点(Application Node)将前面各个节点的处理结果进行整合,最终生成整体回答。它不仅保存了回答文本,还汇总了各个节点的详细信息、token 使用情况、上下文数据等。
- 工作流整体管理
- 工作流各节点之间通过上下文数据(例如
node_chunk
、workflow_manage
等)共享信息。整体流程从用户输入开始,依次经过各个节点处理,最终在返回答案同时也保存一个详细的执行日志,便于追踪整个会话流程。
- 工作流各节点之间通过上下文数据(例如
- 搜索数据集节点
-
-
-
-
- 向量存储与检索
- 利用 PostgreSQL 与 PG Vector 插件,对知识库中的文档进行向量化存储。用户输入的问题首先会通过嵌入模型转换成一个向量,再在向量库中搜索语义相似的文档。
- 嵌入模型与向量库逻辑
- 在相关模块如
pg_vector.py
、search_dataset_node
中,可以看到:- 对文档的嵌入进行更新、删除操作。
- 构建查询语句,使用向量距离作为排序依据,返回最匹配的结果。
- 在相关模块如
- 向量存储与检索
-
1.获取模型实例
利用 get_model_instance_by_model_user_id
根据模型 ID 和用户信息获取具体的模型包装类实例。
2.调用推理或流式 API
根据业务场景,调用模型实例的 invoke
(同步)或 stream
(流式)方法进行推理,返回回答文本或分块回答。
3.上下文组装与输出
_write_context
或类似方法保存,并经过 Reasoning 类处理附加 “思考” 逻辑,最后组装成完整回答返回给前端。五、核心代码解析
下面介绍该知识库系统中如何利用 LangChain 框架来连接大语言模型(LLM)、调用辅助函数、以及基于向量数据库的知识检索等核心组件。
整个系统采用模块化设计,每个“节点”负责完成特定的任务,节点内部通过调用 get_model_instance_by_model_user_id 等工厂方法,获取由 LangChain 封装的模型实例,然后调用相应的方法(如 invoke、stream、embed_query 等)实现推理、流式响应以及向量搜索等操作。
下面给出几个关键模块的示例代码及详细说明。
-
-
- 利用工厂函数 get_model_instance_by_model_user_id 根据模型 ID 和用户信息获取具体的模型实例;
- 构造系统提示、历史对话消息、用户提问等,拼装成消息列表(消息类型使用 HumanMessage、SystemMessage 等);
- 根据是否需要流式响应,调用模型的 invoke(同步)或 stream(流式)方法;
- 在返回结果前调用 _write_context 将节点执行过程和 token 统计信息写入上下文。
-
def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,
model_params_setting=None, dialogue_type=None, model_setting=None, **kwargs) -> NodeResult:
if dialogue_type isNone:
dialogue_type = 'WORKFLOW'
if model_params_setting isNone:
model_params_setting = get_default_model_params_setting(model_id)
if model_setting isNone:
model_setting = {
'reasoning_content_enable': False,
'reasoning_content_end': '</think>',
'reasoning_content_start': '<think>'
}
self.context['model_setting'] = model_setting
# 获取 LLM 模型实例,底层借助 LangChain 封装
chat_model = get_model_instance_by_model_user_id(
model_id, self.flow_params_serializer.data.get('user_id'), **model_params_setting)
# 构建历史消息
history_message = self.get_history_message(history_chat_record, dialogue_number, dialogue_type, self.runtime_node_id)
self.context['history_message'] = history_message
# 生成当前问题的提示信息
question = self.generate_prompt_question(prompt)
self.context['question'] = question.content
system = self.workflow_manage.generate_prompt(system)
self.context['system'] = system
# 构造最终交互的消息列表,利用 LangChain 中的消息类型进行封装
message_list = self.generate_message_list(system, prompt, history_message)
self.context['message_list'] = message_list
if stream:
response = chat_model.stream(message_list)
return NodeResult({
'result': response,
'chat_model': chat_model,
'message_list': message_list,
'history_message': history_message,
'question': question.content
}, {}, _write_context=write_context_stream)
else:
response = chat_model.invoke(message_list)
return NodeResult({
'result': response,
'chat_model': chat_model,
'message_list': message_list,
'history_message': history_message,
'question': question.content
}, {}, _write_context=write_context)
-
-
-
-
- get_model_instance_by_model_user_id 用于获取具体的 LLM 模型实例,内部可能针对不同的模型调用不同的 API;
- generate_message_list 将系统提示、历史信息和用户提问结合,生成符合 LangChain 格式的消息列表;
- 根据 stream 参数选择同步或流式调用,返回的 NodeResult 对象中包含后续处理需要的上下文信息。
-
-
-
-
-
-
- 通过 get_embedding_id 确定使用哪个嵌入模型;
- 通过 get_model_instance_by_model_user_id 获取嵌入模型实例,并调用 embed_query 将自然语言问题转换成向量表示;
- 调用 VectorStore 中封装好的向量检索方法(例如 vector.query)进行向量相似度搜索,检索与问题语义相近的文档段落。
-
-
def execute(self, dataset_id_list, dataset_setting, question, exclude_paragraph_id_list=None, **kwargs) -> NodeResult:
self.context['question'] = question
iflen(dataset_id_list) == 0:
return get_none_result(question)
# 根据知识库列表,获取统一的嵌入模型ID
model_id = get_embedding_id(dataset_id_list)
# 获取嵌入模型实例,基于 LangChain 调用嵌入接口
embedding_model = get_model_instance_by_model_user_id(
model_id, self.flow_params_serializer.data.get('user_id'))
# 将问题转换为向量
embedding_value = embedding_model.embed_query(question)
# 获取向量数据库实例,此处基于 PG Vector 封装
vector = VectorStore.get_embedding_vector()
exclude_document_id_list = [
str(document.id) for document in QuerySet(Document).filter(
dataset_id__in=dataset_id_list, is_active=False)]
# 调用向量检索接口,根据向量距离返回相似候选文档
embedding_list = vector.query(
question, embedding_value, dataset_id_list, exclude_document_id_list,
exclude_paragraph_id_list, True, dataset_setting.get('top_n'),
dataset_setting.get('similarity'), SearchMode(dataset_setting.get('search_mode'))
)
# 手动关闭数据库连接
connection.close()
if embedding_list isNone:
return get_none_result(question)
paragraph_list = self.list_paragraph(embedding_list, vector)
result = [self.reset_paragraph(paragraph, embedding_list) for paragraph in paragraph_list]
result = sorted(result, key=lambda p: p.get('similarity'), reverse=True)
return NodeResult({'result': result, 'question': question}, {})
-
-
-
-
- 首先通过嵌入模型将问题编码成向量,再调用 PG Vector 提供的查询接口;
- 向量检索返回的是一系列候选段落,之后经过过滤和排序得到最匹配的结果;
- 这些匹配结果可以后续传递给重排节点或直接用于回答生成。
-
-
-
-
-
-
- 整合多个节点输入的内容,通过 merge_reranker_list 合并;
- 通过 get_model_instance_by_model_user_id 获取重排模型实例;
- 调用模型的 compress_documents 方法,对候选文档进行内容压缩和重新排序。
-
-
def execute(self, question, reranker_setting, reranker_list, reranker_model_id, **kwargs) -> NodeResult:
# 将不同来源的候选文本合并
documents = merge_reranker_list(reranker_list)
top_n = reranker_setting.get('top_n', 3)
self.context['document_list'] = [
{'page_content': document.page_content, 'metadata': document.metadata}
for document in documents
]
self.context['question'] = question
# 获取重排模型,并调用相关函数实现文档压缩
reranker_model = get_model_instance_by_model_user_id(
reranker_model_id, self.flow_params_serializer.data.get('user_id'), top_n=top_n)
result = reranker_model.compress_documents(documents, question)
similarity = reranker_setting.get('similarity', 0.6)
max_paragraph_char_number = reranker_setting.get('max_paragraph_char_number', 5000)
result = reset_result_list(result, documents)
filtered_result = filter_result(result, max_paragraph_char_number, top_n, similarity)
return NodeResult({
'result_list': filtered_result,
'result': ''.join([item.get('page_content') for item in filtered_result])
}, {})
-
-
-
-
- merge_reranker_list 会把不同节点返回的候选文档整合成统一的 Document 对象列表;
- 重排操作通过调用模型实例中的 compress_documents 实现,从而得到更加准确和简洁的回答内容。
-
-
-
文章评论