专题十四:知识库问答变慢 / 卡住 / 失败的完整排查与容量规划
本文基于
192.168.110.207(Dify 1.16.1 新 Agent)+116.62.10.183(vLLM Qwen3.6-27B,H20)实战。覆盖三类问题:看起来慢、界面一直思考像卡死、已经失败但页面还停在工具调用。
全文四部分:排查思路、看什么日志怎么定位、分场景报错与解法、按公司人数和硬件给参数建议。
相关专题不要混:专题四是问答期协议/端点类报错,专题五是「模型已起来但 Dify 不通」,专题十一/十二是
max_tokens落库,专题十三是 Rerank 怎么开。本文把它们串成一条可执行的排障主线,并补上容量规划。
运行环境:业务脚本均在 207 固定目录
/opt/workbuddy执行,Python 为/usr/bin/python3。 Windows 开发机仅保留_probe_*调试脚本;同步命令:python scripts_cursor/完整代码约定/00_部署_workbuddy到207.py。
目录
第一部分 原理与排查总图
一、本文解决什么问题
知识库智能体「不好用」时,用户看到的往往只有一句话:慢、卡住、没回答。后台其实可能是完全不同的层坏了。
本文要能做到:
- 先判断这轮是 还在跑、已经失败、还是 成功但就是慢。
- 按时间线把请求对齐到具体容器、具体接口。
- 对上号的场景,用已经验证过的办法修,而不是同时改十个参数。
- 按公司规模和 GPU/CPU/内存给出一套可落地的默认参数,避免把 100 人公司的配法直接抄到 500 人。
验收一条故障是否「定位完成」:
- 能说出失败发生在哪一层(检索 / Rerank / 第二次 LLM / token 上限 / 网络 / 知识库不可用)。
- 能指出该看哪条日志、哪张表、哪个 curl。
- 修复后用 新开对话 复测同一问句,
messages.status=normal,不再只看页面有没有转圈。
二、一条知识库问答实际走哪几层
新 Agent(mode=agent)问制度类问题,典型是两轮模型调用,中间夹一次检索:
用户发问
│
▼
① docker-api-1 收请求,交给 docker-agent_backend-1
│
▼
② 第一次 LLM(qwen3.6-27b / vLLM)
决定调用工具 knowledge_base_search
界面开始显示「思考中」
│
▼
③ POST /inner/api/knowledge/retrieve
Embedding 召回(Xinference bge-m3 + Weaviate)
若开启 Rerank:再 POST Xinference /v1/rerank
界面仍停在 knowledge_base_search
│
▼
④ 第二次 LLM
Prompt = 系统提示 + 历史对话 + 检索到的 Top-K 片段
Qwen3.6 会先 thinking,再写正文
若 thinking 把 max_tokens 用完,界面仍像卡在检索上
│
▼
⑤ 写 messages 表,可选上报 Langfuse关键误区:
- 界面停在
knowledge_base_search不等于 检索还在跑。检索 2 秒结束、后面 LLM 想了 2 分钟,页面照样停在这个工具名上。 - 知识库页改了 Rerank,不等于 对话已经走 Rerank。新 Agent 读的是快照里
knowledge.sets[].retrieval(专题十三)。 max_tokens对 Qwen3.6 不只是「回答能写多长」,thinking 也算进这同一笔预算。
三、先把表象分成四类(再动手)
| 类型 | 页面上看 | 库表 messages.status | 真正含义 |
|---|---|---|---|
| 还在跑 | 思考、有工具名、时间在增加 | 还没有最终行,或 latency 仍在涨 | 等;同时看日志确认卡在哪一层 |
| 已失败,界面没刷新 | 空泡、3/3、工具框一直在 | error | 已经结束。继续点发送只会更乱 |
| 成功但慢 | 最终有正文 | normal,provider_response_latency 大 | 性能问题,不是故障 |
| 明确报错文案 | 红色错误 / 400 | error,error 字段有原文 | 按文案进第三部分对号入座 |
人数再多,排查顺序也不变:先定性,再分层,最后才改参数。
四、排查总流程(不要一上来改配置)
A. 这轮对话的 message_id / 时间点是什么?
│
▼
B. SELECT status, error, message_tokens, answer_tokens
已是 error → 读 error 原文,跳到第三部分对应场景
仍像 running → 看 agent_backend 当前卡在 chat 还是 retrieve
│
▼
C. 用同一秒去对齐四类日志
api / agent_backend / plugin_daemon / xinference(或 vLLM)
│
▼
D. 确认 Prompt 体积和 max_tokens
message_tokens 是否已经接近或超过 max_tokens
│
▼
E. 只改一个根因对应的项
改完不要点发布;新开对话验证和已有专题的分工:
| 你看到的 | 先看 |
|---|---|
System message must be at the beginning | 专题四 |
| 模型容器 Up,Dify 测连通失败 | 专题五 |
| Agent 里选不到知识库 | 专题三 |
| Max Tokens 改了又变回 8192 | 专题十一 |
| 空泡 + token limit 2048 | 专题十二 |
| 命中测试排序差、要开 Rerank | 专题十三 |
| 开了 Rerank 后变慢、思考停住、8192 thinking 耗尽 | 本文 |
五、本次环境(对照用,换机器请重查)
| 项 | 值 |
|---|---|
| Dify | 1.16.1,http://192.168.110.207 |
| Dify 机 | 无 GPU,内存约 31Gi,同时跑 Dify + Weaviate + Xinference + Langfuse |
| 应用 | 播城行政问答智能体 |
| 应用 ID | 40e95fd3-e7f5-44f2-a03b-dc3e60413540 |
| Agent ID | 01a012e7-bed3-7574-b0d1-a5cc79a0d3de |
| 活动快照 | 01a0135c-320e-79dc-97a0-94da9b904c13 |
| 编辑草稿 | 01a012e7-bf60-7673-a894-64551d984a23 |
| 知识库 | ee2495bf-c370-49d1-8268-10c51f650fa6 |
| Embedding | Xinference bge-m3,CPU,http://192.168.110.207:9997 |
| Rerank | Xinference bge-reranker-v2-m3,CPU |
| LLM | qwen3.6-27b,vLLM,http://116.62.10.183:8000/v1 |
| GPU | H20 约 96GB,与业务 mxh_env / mxh_live 共享,--gpu-memory-utilization 0.75,--max-model-len 65536,--max-num-seqs 8 |
| Dify 中 context_size | 64000 |
| 当前 max_tokens | 32768(2026-08-19 因 thinking 耗尽 8192 而改;改前是 8192) |
| 当前检索 | 语义检索 + Rerank,Top-K=5 |
UUID、密钥、IP 都是本环境实录。换环境必须重查,不要直接抄进生产脚本当常量。
第二部分 详细操作:看日志、怎么定位
Python 约定(全库统一)
- 每个操作步骤对应
/opt/workbuddy/scripts_cursor/下一个完整可运行程序(含main()、登录、常量);不要从正文里抄片段拼程序。- 有配置改动时提供 修改前 / 修改后 两个文件(文件名带
_修改前/_修改后),正文附 改动对比 表。- 换环境只改
/opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/_config.py。在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/脚本名.py- 程序索引见下文「本专题 Python 程序一览」;插件源码级补丁见各步骤正文(补丁也做成完整 apply 脚本)。
下面按真实顺序写。人在 207 上可直接 docker exec;从 Windows 工作区过去,普通 ssh 容易卡密码,用现有 dify_api.py(Paramiko)即可。
排查步骤速查(1~7)
| 步 | 干什么 | 做完应得到什么 | 卡住 → 第三部分场景 |
|---|---|---|---|
| 1 | 库表定性 | 知 status/error/latency | — |
| 2 | 四容器日志对齐时间线 | 知卡在哪一层 | A~J 场景表 |
| 3 | 分层探针 | embedding/rerank/vLLM 分层 OK | C/F/G |
| 4 | 看 Token 预算 | 是否 prompt 吃光 | A/B |
| 5 | 检索+Rerank 计时 | 知慢在检索还是 LLM | C/I |
| 6 | 新开对话 | 排除会话污染 | B/H |
| 7 | 收尾检查单 | 可交变更单 | 第四部分容量 |
步骤 1 · 用库表给这轮对话定性
不要先猜「是不是 Rerank」。先问数据库:这轮到底成功、失败,还是还在写。
docker exec docker-db_postgres-1 psql -U postgres -d dify -c "
SELECT id, created_at, status,
message_tokens, answer_tokens,
round(provider_response_latency::numeric, 1) AS lat_s,
left(query, 40) AS q,
length(coalesce(answer,'')) AS ans_len,
left(coalesce(error,''), 180) AS err
FROM messages
ORDER BY created_at DESC
LIMIT 8;
"怎么读:
| 字段 | 含义 |
|---|---|
status=normal | 已经成功。慢就看 lat_s 和 message_tokens |
status=error | 已经失败。页面还在转,只是前端没跟上 |
error 含 token limit (2048) | 专题十二,生成预算太小 |
error 含 token limit (8192) 且 answer_tokens=0 | 多半是 thinking 把预算用光,见场景 B |
message_tokens | 这轮 Prompt 体积。知识库问答经常 6k–15k |
answer_tokens=0 且 latency 很大 | 模型跑了很久但没产出可见正文 |
lat_s 2–30 | 正常单轮知识问答区间(本环境) |
lat_s 120+ | 卡在第二次 LLM 或排队,不是检索 |
查某一个 Agent 当前 max_tokens 和检索配置:
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_01.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_01.sh
#!/usr/bin/env bash
set -euo pipefail
docker exec docker-db_postgres-1 psql -U postgres -d dify -c "
SELECT
config_snapshot::json -> 'model' -> 'model_settings' -> 'max_tokens' AS max_tokens,
config_snapshot::json -> 'knowledge' -> 'sets' -> 0 -> 'retrieval' AS retrieval
FROM agent_config_snapshots
WHERE id = '01a0135c-320e-79dc-97a0-94da9b904c13';
"注意:config_snapshot 在库里经常是 text,查询时要 ::json 或 ::jsonb,不要直接对 text 用 ->。
步骤 2 · 对齐时间线:四个容器各看什么
把步骤 1 的 created_at(UTC)记下来。207 上 Docker 日志一般也是 UTC。例如失败发生在 02:27:09,就按前后 3 分钟滤。
2.1 docker-agent_backend-1 —— 新 Agent 的主时间线
这是定位「卡在第几步」的第一日志。
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_02.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_02.sh
#!/usr/bin/env bash
set -euo pipefail
docker logs docker-agent_backend-1 2>&1 | grep -E '02:27:|knowledge_base_search|chat qwen|token limit|retrieve'本环境一次真实失败的时间线(问「管理人员需要钉钉打卡吗」):
02:27:09.307 agent run
02:27:09.309 chat qwen3.6-27b ← 第一次 LLM,决定用工具
02:27:20.825 running tool: knowledge_base_search
02:27:20.825 POST .../knowledge/retrieve
02:27:22.936 chat qwen3.6-27b ← 检索已结束,第二次 LLM 开始
02:27:22 ~ 02:29:54 ← 这里空了 150 秒,页面仍显示检索
02:29:54 token limit (8192) exceeded before any response was generated读法:
running tool到下一次chat qwen只有 2 秒 → 检索/Rerank 不是瓶颈。- 第二次
chat qwen之后长时间没有agent_message正文 → 卡在 LLM thinking 或排队。 - 最后抛
pydantic_ai.exceptions.UnexpectedModelBehavior且文案含before any response was generated→ thinking 或空输出顶满max_tokens。
源码对应(容器内,只读,用于理解,不要改):
/app/api/.venv/lib/python3.12/site-packages/pydantic_ai/_agent_graph.py判断逻辑是:响应只有 ThinkingPart、或 parts 为空,且 finish_reason == 'length',就报上述错误。注释写明:*possibly during thinking*。
2.2 docker-api-1 —— 控制台看到的失败包装
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_03.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_03.sh
#!/usr/bin/env bash
set -euo pipefail
docker logs --since 30m docker-api-1 2>&1 | grep -Ei 'Unknown Error|token limit|Agent App generate'典型:
Unknown Error in Agent App generate worker
AgentBackendRunFailedError: Model token limit (8192) exceeded before any response was generated.
(agent_run_id=...)agent_run_id 可拿去 agent_backend 日志里精确搜这一轮。
2.3 xinference —— Embedding / Rerank 是否慢、是否 403
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_04.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_04.sh
#!/usr/bin/env bash
set -euo pipefail
docker logs --since 20m xinference 2>&1 | grep -E 'POST /v1/rerank|POST /v1/embeddings|/v1/rerank|ERROR|403'正常知识问答会看到:
Rerank with kwargs: {}
"POST /v1/rerank HTTP/1.1" 200本环境短文档 Rerank 约 0.15s,真实制度片段约 2s,10 条长文档 CPU 可达 16s。超过 10s 才优先怀疑 Rerank;2s 级不要先关 Rerank。
列模型必须带推理 Key,否则 Could not validate credentials:
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_05.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_05.sh
#!/usr/bin/env bash
set -euo pipefail
curl -s -H 'Authorization: Bearer xf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI' \
http://127.0.0.1:9997/v1/models期望同时有 bge-m3(embedding)和 bge-reranker-v2-m3(rerank)。重启 Xinference 后实例会丢,见场景 G。
2.4 docker-plugin_daemon-1 —— LLM / Embedding 插件调用
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_06.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_06.sh
#!/usr/bin/env bash
set -euo pipefail
docker logs --since 20m docker-plugin_daemon-1 2>&1 | grep -Ei 'xinference|openai|timeout|error|session'这里确认请求有没有真正 dispatch 到 langgenius/openai 或 langgenius/xinference。session 缓存 WARN 可以忽略。
2.5 GPU 机 vLLM —— 是否在排队、是否 OOM
在 116.62.10.183 上:
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_07.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_07.sh
#!/usr/bin/env bash
set -euo pipefail
docker logs --since 20m vllm-qwen3.6-27b 2>&1 | tail -n 80
nvidia-smi
curl -s -H 'Authorization: Bearer 123456' http://127.0.0.1:8000/v1/models看:
- 容器是否
Up - 显存是否把业务进程挤爆(本环境必须给
mxh_env/mxh_live留余量) - 是否出现
CUDA out of memory、EngineDead、KV cache 不足
--max-num-seqs 8 表示大约 8 路并发生成。超过就会排队,用户感觉「突然全面变慢」,日志上检索却很快。
2.6 Weaviate / 中继 —— 检索本身失败
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_08.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_08.sh
#!/usr/bin/env bash
set -euo pipefail
docker logs --since 20m docker-weaviate-1 2>&1 | tail -n 50
docker logs --since 20m docker-weaviate_relay-1 2>&1 | tail -n 50知识库突然从 Agent 列表消失,优先查 Embedding 实例是否还在(专题三),不要先重建知识库。
2.7 Langfuse —— 看本轮 Trace,不要用来看召回名次
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_09.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_09.sh
#!/usr/bin/env bash
set -euo pipefail
curl -s -u 'pk-lf-...:sk-lf-...' \
'http://127.0.0.1:3000/api/public/traces?limit=8'本环境 Generation Input 经常只有用户原话,看不到逐条 chunk。召回名次以知识库命中测试为准(专题十三)。Langfuse 适合确认:这轮有没有记到、总 Token、是否 error。
步骤 3 · 分层探针(绕过页面,确认哪一层活着)
按这个顺序,哪一层 curl 失败就停在哪一层,不要跳着改。
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_10.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_10.sh
#!/usr/bin/env bash
set -euo pipefail
# 1) Embedding / Rerank 还在不在
curl -s -m 15 -H 'Authorization: Bearer xf-...' http://127.0.0.1:9997/v1/models
# 2) Rerank 能不能打分(应在 1 秒内)
curl -s -m 30 -H 'Authorization: Bearer xf-...' \
-H 'Content-Type: application/json' \
-d '{"model":"bge-reranker-v2-m3","query":"管理人员需要钉钉打卡吗","documents":["管理人员应通过钉钉打卡。","班车时间以通知为准。"]}' \
http://127.0.0.1:9997/v1/rerank
# 3) 知识库命中测试(含 Rerank 的完整检索)
# 用 Dify 控制台或 dify_api.py POST /datasets/<id>/hit-testing
# 本环境该问句约 2.4s HTTP 200
# 4) vLLM 还在不在(在 207 上测跨机)
curl -s -m 10 -H 'Authorization: Bearer 123456' \
http://116.62.10.183:8000/v1/models
# 5) 最短对话(不走知识库)
curl -s -m 60 -X POST http://116.62.10.183:8000/v1/chat/completions \
-H 'Authorization: Bearer 123456' -H 'Content-Type: application/json' \
-d '{"model":"qwen3.6-27b","messages":[{"role":"user","content":"只回:pong"}],"max_tokens":16}'判断:
| 探针 | 失败 | 结论 |
|---|---|---|
/v1/models 无 rerank | Xinference 实例丢了 | 场景 G |
/v1/rerank 超时 | CPU 过载或模型没加载完 | 场景 C |
| 命中测试慢、对话也慢 | 检索侧 | 查 Weaviate / 文档切块过长 |
| 命中测试快、对话慢或失败 | LLM / token / 会话历史 | 场景 A/B |
vLLM /v1/models 不通 | 专题五 B 层 | 场景 F |
| 最短对话通、知识问答失败 | Prompt 体积或工具链路 | 继续步骤 4 |
步骤 4 · 看 Token:是不是 Prompt 已经把预算吃光
Qwen3.6-27B 在 Dify 里 context_size=64000,vLLM --max-model-len 65536。真正容易踩的是 Agent 的 max_tokens(生成预算,含 thinking)。
关系:
可用生成预算 ≈ max_tokens
thinking + 正文 都从这里扣
若 thinking 先把 max_tokens 用完
→ finish_reason=length
→ 页面像卡在知识库
→ error: token limit (N) exceeded before any response was generated本环境对照:
| 问句 | 会话 | message_tokens | max_tokens | 结果 |
|---|---|---|---|---|
| 法定假日加班费怎么算 | 新对话 | 7325 | 8192 | 成功,约 23s |
| 管理人员需要钉钉打卡吗 | 旧会话第 3 轮 | 失败时未写入 | 8192 | 想了 150s 后失败 |
| 同上(修复后新开对话) | 新对话 | 13927 | 32768 | 成功,约 21s |
注意:打卡条款原文长,即使新开对话 Prompt 也有 1.4 万,已经超过旧的 8192。所以不是「只有长会话才会炸」,长文档 + Top-K=5 一样会炸。
改 max_tokens 必须改两张表,且 不要点发布(专题十一、十二):
BEGIN;
UPDATE agent_config_snapshots
SET config_snapshot = jsonb_set(
config_snapshot::jsonb,
'{model,model_settings,max_tokens}',
'32768'::jsonb
)::text
WHERE id = '01a0135c-320e-79dc-97a0-94da9b904c13';
UPDATE agent_config_drafts
SET config_snapshot = jsonb_set(
config_snapshot::jsonb,
'{model,model_settings,max_tokens}',
'32768'::jsonb
)::text
WHERE id = '01a012e7-bf60-7673-a894-64551d984a23';
COMMIT;约束:prompt_tokens + max_tokens 不应长期顶满 65536。Prompt 1.4 万、max_tokens 32768,合计约 4.7 万,仍低于 65536,这是本环境可接受的组合。
步骤 5 · 单独给检索和 Rerank 计时
命中测试(走知识库当前检索配置):
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_01.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_01.py
from dify_api import Dify
import time
d = Dify("38205036@qq.com", "Difyai123456")
d.login()
t0 = time.time()
r = d.post(
"/datasets/ee2495bf-c370-49d1-8268-10c51f650fa6/hit-testing",
body={"query": "管理人员需要钉钉打卡吗"},
extra="-m 180 --max-time 180",
t=200,
)
print(r["code"], round(time.time() - t0, 2), "s")本环境约 2.4s。若这里就要 20s+,先优化切块或把 Rerank 换 GPU,不要先加 max_tokens。
CPU 上 10 条中等长度文档的 Rerank 实录约 16.6s。并发一高,207 的 CPU Rerank 会成为全员变慢的原因。容量建议见第四部分。
步骤 6 · 新开对话验证(不要在失败会话里继续点)
失败会话会带着超长历史和 error 气泡。验证必须:
- 强刷页面。
- 新开对话。
- 问同一句。
- 再跑步骤 1 的 SQL,确认
status=normal。
用 API 新开(conversation_id 留空):
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_02.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_02.py
payload = {
"inputs": {},
"query": "管理人员需要钉钉打卡吗",
"response_mode": "streaming",
"files": [],
"conversation_id": "",
}
r = d.post(
"/agent/01a012e7-bed3-7574-b0d1-a5cc79a0d3de/chat-messages",
body=payload,
extra="-m 420 --max-time 420",
t=450,
)步骤 7 · 收尾检查单
- [ ]
messages.status已定性(normal / error),不是靠页面猜 - [ ] agent_backend 时间线能分清:第一次 LLM / 检索 / 第二次 LLM
- [ ] xinference 同时有 embedding + rerank(若知识库开了 Rerank)
- [ ] vLLM
/v1/models通,nvidia-smi业务显存稳定 - [ ] 快照和草稿的
max_tokens、retrieval一致 - [ ] 用新开对话验证,没有点发布
- [ ] 若只是慢:记录
message_tokens和lat_s,再决定是降 Top-K、关 Rerank 还是加 GPU
本专题 Python 程序一览
| 文件 | 用途 | |---|---|| 专题十四_01_消息表定性.py | 专题十四 · 步骤 1:用 messages 表给最近对话定性。 | | 专题十四_02_读Agent配置.py | 专题十四 · 步骤 1:读 Agent 快照 max_tokens 与 retrieval。 | | 专题十四_03_分层探针.py | 专题十四 · 步骤 3:分层探针(Xinference / Rerank / vLLM / 最短对话)。 | | 专题十四_04_改max_tokens.py | 专题十四 · 步骤 4:快照+草稿 max_tokens 改为 32768(不要点发布)。 | | 专题十四_05_命中测试计时.py | 专题十四 · 步骤 5:命中测试计时(走知识库当前检索配置)。 | | 专题十四_06_新开对话验收.py | 专题十四 · 步骤 6:新开 Agent 对话验收(conversation_id 留空)。 | | 专题十四_07_采样容器日志.py | 专题十四 · 步骤 2:采样 agent_backend / xinference / api 日志。 |
专题十四_01_消息表定性.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_01_消息表定性.py
完整补丁:patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_01.py
运行/引用:见 patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_01.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十四 · 步骤 1:用 messages 表给最近对话定性。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
sql = """
SELECT id, created_at, status,
message_tokens, answer_tokens,
round(provider_response_latency::numeric, 1) AS lat_s,
left(query, 40) AS q,
length(coalesce(answer,'')) AS ans_len,
left(coalesce(error,''), 180) AS err
FROM messages
ORDER BY created_at DESC
LIMIT 8;
"""
out, err = d.sh(
'docker exec %s psql -U postgres -d dify -c "%s"'
% (C.PG_CONTAINER, sql.replace("\n", " "))
)
print(out or err)
print("\n读法:status=error 已失败;answer_tokens=0 + token limit → 场景 B")
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题十四_02_读Agent配置.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_02_读Agent配置.py
完整补丁:patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_02.py
运行/引用:见 patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_02.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十四 · 步骤 1:读 Agent 快照 max_tokens 与 retrieval。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
sql = """
SELECT
config_snapshot::json -> 'model' -> 'model_settings' -> 'max_tokens' AS max_tokens,
config_snapshot::json -> 'knowledge' -> 'sets' -> 0 -> 'retrieval' AS retrieval
FROM agent_config_snapshots
WHERE id = '%s';
""" % C.SNAP_ID
out, err = d.sh(
'docker exec %s psql -U postgres -d dify -c "%s"'
% (C.PG_CONTAINER, sql.replace("\n", " "))
)
print(out or err)
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题十四_03_分层探针.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_03_分层探针.py
完整补丁:patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_03.py
运行/引用:见 patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_03.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十四 · 步骤 3:分层探针(Xinference / Rerank / vLLM / 最短对话)。"""
from __future__ import annotations
import json
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
probes = [
("1) Xinference /v1/models", (
"curl -s -m 15 -H 'Authorization: Bearer %s' %s/v1/models"
% (C.XF_KEY, C.XF_URL)
)),
("2) Xinference /v1/rerank", (
"curl -s -m 30 -H 'Authorization: Bearer %s' -H 'Content-Type: application/json' "
"-d '{\"model\":\"%s\",\"query\":\"%s\",\"documents\":[\"管理人员应通过钉钉打卡。\",\"班车时间以通知为准。\"]}' "
"%s/v1/rerank"
% (C.XF_KEY, C.RERANK_MODEL, C.PROBE_QUERY_KB, C.XF_URL)
)),
("3) vLLM /v1/models", (
"curl -s -m 10 -H 'Authorization: Bearer %s' %s/models"
% (C.VLLM_KEY, C.VLLM_BASE)
)),
("4) vLLM 最短对话", (
"curl -s -m 60 -X POST %s/chat/completions "
"-H 'Authorization: Bearer %s' -H 'Content-Type: application/json' "
"-d '{\"model\":\"%s\",\"messages\":[{\"role\":\"user\",\"content\":\"只回:pong\"}],\"max_tokens\":16}'"
% (C.VLLM_BASE, C.VLLM_KEY, C.QWEN_MODEL)
)),
]
for title, cmd in probes:
print("\n=== %s ===" % title)
out, err = d.sh(cmd)
text = out or err
print(text[:1500] if len(text) > 1500 else text)
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题十四_04_改max_tokens.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_04_改max_tokens.py
完整补丁:patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_04.py
运行/引用:见 patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_04.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十四 · 步骤 4:快照+草稿 max_tokens 改为 32768(不要点发布)。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _agent_cfg as AC
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
out, err = d.sh(
"""docker exec %s psql -U postgres -d dify -c "
SELECT 'snapshot' AS which,
config_snapshot::json -> 'model' -> 'model_settings' -> 'max_tokens' AS max_tokens
FROM agent_config_snapshots WHERE id='%s'
UNION ALL
SELECT 'draft',
config_snapshot::json -> 'model' -> 'model_settings' -> 'max_tokens'
FROM agent_config_drafts WHERE id='%s';
"
"""
% (C.PG_CONTAINER, C.SNAP_ID, C.DRAFT_ID)
)
print("before:\n", out or err)
AC.set_max_tokens(d, C.SNAP_ID, C.DRAFT_ID, C.MAX_TOKENS)
out2, err2 = d.sh(
"""docker exec %s psql -U postgres -d dify -c "
SELECT 'snapshot' AS which,
config_snapshot::json -> 'model' -> 'model_settings' -> 'max_tokens' AS max_tokens
FROM agent_config_snapshots WHERE id='%s'
UNION ALL
SELECT 'draft',
config_snapshot::json -> 'model' -> 'model_settings' -> 'max_tokens'
FROM agent_config_drafts WHERE id='%s';
"
"""
% (C.PG_CONTAINER, C.SNAP_ID, C.DRAFT_ID)
)
print("after:\n", out2 or err2)
print("\n[WARN] 改完不要点「构建/发布」;新开对话验证")
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题十四_05_命中测试计时.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_05_命中测试计时.py
完整补丁:patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_05.py
运行/引用:见 patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_05.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十四 · 步骤 5:命中测试计时(走知识库当前检索配置)。"""
from __future__ import annotations
import sys
import time
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
t0 = time.time()
r = d.post(
"/datasets/%s/hit-testing" % C.DS_ID,
body={"query": C.PROBE_QUERY_KB},
extra="-m 180 --max-time 180",
t=200,
)
elapsed = round(time.time() - t0, 2)
print("HTTP", r.get("code"), "elapsed", elapsed, "s")
j = r.get("json")
if isinstance(j, list):
recs = j
else:
recs = (j or {}).get("records") or []
for i, rec in enumerate(recs[:5], 1):
seg = rec.get("segment") or rec
print(i, rec.get("score"), (seg.get("content") or "")[:80])
return 0 if r.get("code") == "200" else 1
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题十四_06_新开对话验收.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_06_新开对话验收.py
完整补丁:patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_06.py
运行/引用:见 patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_06.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十四 · 步骤 6:新开 Agent 对话验收(conversation_id 留空)。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
payload = {
"inputs": {},
"query": C.PROBE_QUERY_KB,
"response_mode": "streaming",
"files": [],
"conversation_id": "",
}
r = d.post(
"/agent/%s/chat-messages" % C.AGENT_ID,
body=payload,
extra="-m 420 --max-time 420",
t=450,
)
print("HTTP", r.get("code"))
text = r.get("text") or ""
print(text[-1500:] if len(text) > 1500 else text)
return 0 if r.get("code") == "200" else 1
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题十四_07_采样容器日志.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_07_采样容器日志.py
完整补丁:patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_07.py
运行/引用:见 patches_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_07.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十四 · 步骤 2:采样 agent_backend / xinference / api 日志。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
cmds = [
("agent_backend", "docker logs --since 20m docker-agent_backend-1 2>&1 | grep -Ei 'knowledge_base_search|chat qwen|token limit|retrieve' | tail -30"),
("api", "docker logs --since 20m docker-api-1 2>&1 | grep -Ei 'Unknown Error|token limit|Agent App generate' | tail -20"),
("xinference", "docker logs --since 20m %s 2>&1 | grep -E 'POST /v1/rerank|POST /v1/embeddings|ERROR|403' | tail -20" % C.XF_CONTAINER),
("plugin_daemon", "docker logs --since 20m %s 2>&1 | grep -Ei 'xinference|openai|timeout|error' | tail -15" % C.PLUGIN_CONTAINER),
]
for name, cmd in cmds:
print("\n=== %s ===" % name)
out, err = d.sh(cmd)
print((out or err)[-3000:])
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())第三部分 分场景:常见报错与解决办法
每一条都按 现象 → 看什么 → 原因 → 解决 → 不要做什么 写。优先用本环境真实出现过的原文。
场景 A · 空泡 / 3/3 / token limit (2048)
现象:回复是空气泡,角标 3/3,输入框发不出新问题,像卡死。
看什么:
SELECT status, left(error, 160) FROM messages ORDER BY created_at DESC LIMIT 5;status=error,文案:
Model token limit (2048) exceeded before any response was generated.原因:专题十一曾把 max_tokens 写成 2048。带知识库的 Prompt 经常超过 2048,模型还没写出可见正文就 length。页面空泡是失败态,不是还在生成。专题十二。
解决:快照 + 草稿把 max_tokens 改回至少 8192;失败会话弃用;新开对话。不要点发布。
不要:反复在同一会话点发送(会计到 3/3);把页面刷新理解成「模型死了」。
场景 B · 界面停在 knowledge_base_search,最终 token limit (8192)
现象:思考框里工具名是知识库检索,等一两分钟后失败,或一直转。用户感觉「开了 Rerank 更慢、甚至不工作」。
看什么:
- 步骤 1:
error含token limit (8192)且answer_tokens=0。 - 步骤 2.1:
POST knowledge/retrieve两秒后已再次chat qwen,说明检索已结束。 message_tokens:同类成功问句可达 13927,已经大于 8192。
原因:不是 Rerank 死锁。Rerank 约 2s。第二次 LLM 里 Qwen3.6 的 thinking 把 max_tokens=8192 用完,finish_reason=length,pydantic_ai 判定「没有任何正文」。长文档 Top-K=5、多轮历史会加重。
解决:
- 把
max_tokens提到 16384 或 32768(本环境已用 32768)。 - 保证
prompt + max_tokens < 65536。 - 新开对话复测。
- 制度原文很长时,Top-K 用 3–5,不要 10。
本环境复测:同一句新开对话,21s,status=normal,message_tokens=13927,answer_tokens=777。
不要:关掉 Rerank 当第一反应;在失败会话里继续问;点发布。
场景 C · Rerank 本身慢、超时、校验失败
现象:命中测试十几秒才出;或添加 Rerank 凭据失败;或对话报 Rerank 不可用。
看什么:docker logs xinference 的 POST /v1/rerank 状态码和间隔;GET /v1/models 有没有 model_type=rerank。
常见原文与解法:
| 报错 / 现象 | 原因 | 解决 |
|---|---|---|
Could not validate credentials | 没带 API Key | Header 加推理 Key |
API keys can only access model query and inference(403) | 用推理 Key 去 launch | 容器内 xinference login 管理员再 launch |
Cannot find access token | 没 login 就 launch | 同一 bash -lc 里先 login 再 launch |
--n-gpu 0 ValueError | 无 GPU 机器不能传 0 | 不要传 --n-gpu |
| CLI 100% 但列表没有 rerank | wait_ready=False 先返回,worker 随后失败 | 以 /v1/models 和 ModelActor loaded 为准 |
| 添加 Dify 凭据失败 | 模型还没 running | 先 launch 成功再 POST credentials |
| 10 条长文档 16s+ | CPU 上 bge-reranker-v2-m3 正常现象 | 降召回条数、切短块、或把 Rerank 放到 GPU |
详细 launch 命令见专题十三,此处不重复整段。
场景 D · 400 System message must be at the beginning
现象:一发知识库问题就 400,聊天助手有时正常。
看什么:报错原文来自 Qwen chat template,说明 已经打到 vLLM,不是网断。
原因:Qwen3 只接受一条 system,且必须在最前。新 Agent 会塞多条。专题四,补丁在 OpenAI 插件 chat.py。
解决:按专题四合并 system;改完 docker compose restart plugin_daemon。插件升级会丢补丁。
不要:去重启 vLLM 碰运气;把问题当成 Rerank 故障。
场景 E · Agent 里选不到知识库 / 知识库变灰
现象:配置页知识库列表空,或曾经能选后来没了。
看什么:
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_11.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_11.sh
#!/usr/bin/env bash
set -euo pipefail
curl -s -H 'Authorization: Bearer xf-...' http://127.0.0.1:9997/v1/models没有 bge-m3 就会让知识库 unavailable(专题三)。
原因:Xinference 重启后 embedding 实例不持久化。Weaviate 中继坏了也会(专题六)。
解决:重新 login + launch bge-m3(以及若需要的 rerank),uid 保持原名。不必重建知识库。
场景 F · Connection error / 超时 / 模型列表空
现象:保存模型失败、问答 Connection error、创建 Agent 没有模型。
看什么:从 Dify 所在机 curl vLLM;专题五七层;专题十 schema。
原因 常见:vLLM 没 Up;API Base 漏了 /v1;模型名和 --served-model-name 不一致;context_size 与 --max-model-len 差一个数量级导致校验失败。
解决:专题五、九、十。本环境 API Base 应为 http://116.62.10.183:8000/v1,模型名 qwen3.6-27b,context_size=64000。
场景 G · 重启后 Embedding / Rerank 消失
现象:昨天还好,重启 xinference 后命中测试失败、Agent 里知识库没了、Rerank 校验失败。
原因:launch 的实例不写进镜像。Dify 凭据还在,对面 uid 没了。
解决:
完整脚本:scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_12.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十四_知识库问答变慢卡住失败排查与容量规划/专题十四_内联_12.sh
#!/usr/bin/env bash
set -euo pipefail
docker exec xinference bash -lc '
xinference login -e http://127.0.0.1:9997 --username admin --password admin1234
xinference launch -e http://127.0.0.1:9997 -n bge-m3 -t embedding -u bge-m3
xinference launch -e http://127.0.0.1:9997 -n bge-reranker-v2-m3 -t rerank -u bge-reranker-v2-m3
'不要加 --n-gpu 0。不要重加 Dify 凭据(uid 没变的话)。
场景 H · 改完配置一点「发布」,Max Tokens 又乱了
现象:刚改完 Rerank 或 max_tokens,点构建/发布后对话又超限,或抽屉里的值跳回默认。
原因:新 Agent UI 发布不一定把 model.model_settings.max_tokens 正确落库(专题十一)。
解决:改库后只强刷、新开对话。不要发布。
场景 I · 全体突然变慢,但单条命中测试仍约 2 秒
现象:多人同时问,每个请求都排队到 1 分钟;单独命中测试却很快。
看什么:GPU 机 nvidia-smi;vLLM 日志是否在排队;--max-num-seqs(本环境 8)。
原因:并发超过 vLLM 同时生成路数。检索不是瓶颈。
解决:错峰;加副本;或换更小模型做日常问答、27B 只给复杂题。不要先关 Rerank。
场景 J · Langfuse 里看不到 chunk,误以为没检索
现象:Trace 只有用户原话;retriever_resources 为空数组。
原因:当前上报链路经常不把逐条片段做成 Observation。新 Agent 结束事件里 retriever_resources 也经常是 []。
解决:召回看命中测试;对错看答案是否出现制度原文数字;Trace 只用来确认记到了。不要因此重装插件或再 launch 一次。
场景速查表
| 你看到的 | 先打开 | 对应 |
|---|---|---|
空泡 3/3 | messages.error | A / 专题十二 |
| 思考停在知识库工具 | agent_backend 时间线 | B 或 C |
token limit (8192) 且无正文 | max_tokens vs message_tokens | B |
| 命中测试 > 10s | xinference /v1/rerank | C |
| 400 System message | 专题四 | D |
| 选不到知识库 | /v1/models 有无 bge-m3 | E / 专题三 |
| 连不上模型 | 207 上 curl 116:8000 | F / 专题五 |
| 重启后检索挂了 | 重新 launch | G |
| 发布后参数复原 | 两张配置表 | H / 专题十一 |
| 大家一起慢 | vLLM 并发 / GPU | I |
| Langfuse 没有片段 | 命中测试 | J / 专题十三 |
第四部分 按公司人数与硬件的参数建议
人数不等于并发。内部制度问答,高峰同时在问的人数通常远小于花名册:
| 在职人数 | 典型高峰同时提问 | 设计并发(含余量) |
|---|---|---|
| 约 100 | 2–6 | 按 8 路生成来规划 |
| 200–300 | 6–15 | 按 16 路 |
| 300–500 | 10–25 | 按 24–32 路 |
| 500 以上 | 20–50+ | 按 32+ 路,并准备水平扩展 |
本环境 H20 + --max-num-seqs 8,对应「约 100 人公司、内部知识问答」这一档。200 人以上不要默认还能共用这一张卡上的 0.75 显存配额(还要给 mxh_env / mxh_live)。
下面每档都给出:检索参数、生成参数、硬件、明确不要做什么。数值是起点,不是法律。改完用步骤 1 的 message_tokens / lat_s 回看。
通用原则(所有规模都遵守)
- 先定并发,再定 GPU。 27B 权重大约 50–55GB(FP16/BF16),剩下的显存才是 KV cache,才决定能同时跑几路。
- Embedding / Rerank 能上 GPU 就不要长期趴在 Dify 同机 CPU 上。 100 人可以;300 人以上会把 207 这种 31Gi 主机拖成公共瓶颈。
- Top-K 不是越大越准。 K 增大,Prompt 线性变长,thinking 更容易把
max_tokens吃光,也更容易慢。 max_tokens必须大于「最长知识问答的 Prompt」里 thinking 还需要的余量。 本环境打卡类问句 Prompt 已 1.4 万,8192 不够,32768 才过。max_tokens + 典型 Prompt必须小于--max-model-len(65536)。- 新 Agent 改库后不要点发布。
- 切块:制度类中文建议 400–800 汉字一块、重叠 10–15%。一块几千字再 Top-K=5,Prompt 会爆。
硬件速算(Qwen3.6-27B + 知识库)
| 组件 | 最小能跑 | 比较舒服 | 说明 |
|---|---|---|---|
| LLM GPU | 单卡 ≥ 80GB(H20/A100-80)跑 27B + 8K–32K 上下文 | 独占一张 80GB+,或两卡并行 | 24GB 消费卡不要跑 27B 全量 |
| LLM 显存利用率 | 共享卡 0.70–0.75 | 独占卡 0.90 | 本环境因业务进程必须 0.75 |
| KV / 并发 | max-num-seqs=8 配 8 路 | 16–32 路要更多显存或更短上下文 | concurrent 不够就排队 |
| Embedding | CPU 8 核 + 8Gi 也能跑 bge-m3 | GPU 再加 4–8GB 显存 | 生产检索 QPS 高时必须 GPU |
| Rerank | CPU 可以,Top-K=5、低并发 | GPU 2–4GB 或独立小卡 | CPU 10 条长文可到 16s |
| Dify 主机内存 | 16Gi 很紧 | 32Gi 起(Dify+Weaviate+Postgres+Redis) | 本环境 31Gi 已同时塞了 Xinference+Langfuse |
| Dify 主机 CPU | 8 核 | 16 核+ | CPU Rerank 和 Weaviate 都吃 CPU |
| 磁盘 | SSD | NVMe | 模型权重与 Weaviate 数据 |
24GB / 48GB 卡怎么选模型(不要硬上 27B):
| 显存 | 建议 LLM | 知识库 |
|---|---|---|
| 24GB(如 4090) | 7B–14B,上下文 8K–16K | Embedding+Rerank 同卡要精打细算,或放到 CPU |
| 48GB | 14B–32B 量化,或 27B 但缩短 max-model-len | Embedding 可同卡 4GB |
| 80–96GB | 27B BF16,上下文 32K–64K | 可同卡也可拆到 CPU/小卡 |
档位 1 · 约 100 人公司
画像:行政/人事内部问答,同时在线很少,文档量以制度汇编为主(本环境就是这一档)。
硬件建议:
- GPU:1× H20/A100-80 或 1× 4090 但改用 14B 级模型。本环境是 H20 共享,
--gpu-memory-utilization 0.75,--max-num-seqs 8,--max-model-len 65536,--shm-size=8g。 - Dify 机:16–32Gi 内存,8–16 核,无 GPU 也可以。本环境 31Gi + CPU Embedding/Rerank 刚好够。
- 不要在这台 32Gi 机器上再堆训练任务。
软件参数建议:
| 参数 | 建议 | 本环境现状 | 理由 |
|---|---|---|---|
| 检索方式 | 语义检索 | 语义 | 制度问答够用 |
| Top-K | 3–5 | 5 | >5 对 100 人收益小,Prompt 涨得快 |
| Rerank | 建议开 | 已开 CPU | 低并发下 CPU 2s 可接受 |
| Rerank 模型 | bge-reranker-v2-m3 | 同左 | 中文制度合适 |
| score 阈值 | 默认关 | 关 | 先保证能召回 |
context_size | 与 --max-model-len 对齐,64000 | 64000 | 不要用克隆 schema 的 1.05M |
max_tokens | 16384–32768 | 32768 | 含 thinking;8192 会被长条款打爆 |
| 温度 | 0–0.3 | 未强制 | 制度要稳 |
| 会话策略 | 复杂题新开对话 | — | 第三轮历史 + Top-K=5 最容易炸 |
vLLM max-num-seqs | 8 | 8 | 匹配 100 人高峰 |
| Xinference | 可与 Dify 同机 CPU | 同机 | 省卡 |
建议怎么用:开 Rerank、Top-K=5、max_tokens=32768,强调新开对话。这正是当前 207 的配法。
档位 2 · 200–300 人公司
画像:两个部门以上同时用,午休/上班高峰会叠请求。CPU Rerank 开始排队。
硬件建议:
- GPU:尽量 独占 一张 80GB,或 27B 独占 + 业务进程迁走。
gpu-memory-utilization可到 0.90。 max-num-seqs:16。若显存不够,先把--max-model-len降到 32768,再保并发。- Embedding + Rerank:迁到 GPU(占用约 4–8GB)或单独小卡 / 单独 CPU 机,不要和 Postgres/Weaviate 抢 207 那台 31Gi。
- Dify 机:32–64Gi,16 核;Weaviate 与 Postgres 留出内存。
软件参数建议:
| 参数 | 建议 | 不要 |
|---|---|---|
| Top-K | 3–5 | 不要 8–10 |
| Rerank | 开,但必须 GPU 或独立机 | 不要继续和 Dify 挤一台 CPU |
max_tokens | 16384 起步,长文档 32768 | 不要 2048/4096 |
--max-model-len | 32768 或 65536 | 不要 1M |
| 切块 | 400–800 字 | 不要整章当一块 |
| 应用拆分 | 考勤一套、财务一套知识库 | 不要一个库塞所有制度再 Top-K=10 |
| Dify worker | 按官方 compose 增加 worker 副本前,先加 GPU 并发 | 不要只加 API 副本却仍 8 路 vLLM |
建议怎么用:200 人仍可用 27B,但要把检索模型从 Dify 主机挪开,并把 vLLM 并发提到 16,否则会出现场景 I(全体变慢、命中测试却很快)。
档位 3 · 300–500 人公司
画像:多部门、多知识库,高峰 20+ 路生成。单卡 27B + 64K 上下文会不够。
硬件建议:
- 两套 GPU 角色:一张(或一机)专跑 LLM;另一张小卡或同机余量跑 Embedding+Rerank。
- LLM:仍用 27B 则建议 2 卡并行或 2 个 vLLM 副本 + 负载均衡;或者日常 14B、复杂问题才 27B。
max-num-seqs:24–32,通常要把--max-model-len降到 32768,用显存换并发。- Dify:独立 64Gi+ 主机,Postgres / Weaviate / Redis 不要和模型推理混部。
- Langfuse:可以同 Dify 机,但 ClickHouse 要有磁盘和内存,别和 Weaviate 抢盘。
软件参数建议:
| 参数 | 建议 |
|---|---|
| Top-K | 3 为主,个别应用 5 |
| Rerank | 必须开在 GPU;QPS 高时考虑轻量 reranker |
max_tokens | 8192–16384(上下文已降到 32K 时不要再 32768) |
| 知识库 | 按部门拆,Agent 只绑相关库 |
| 会话 | 控制台提示「新话题请新开对话」;必要时限制历史轮数 |
| 限流 | 网关或 Dify 侧按用户限流,避免 8 人同时长文打爆 KV |
建议怎么用:宁可略降上下文、略降 Top-K,也要保证高峰不排队到一分钟。300 人公司最常见的误区是「参数仍按 100 人抄,只是人变多了」。
档位 4 · 500 人以上
画像:全公司入口、多应用、可能对外或跨园区。需要容量规划,而不是再调一个 Top-K。
硬件建议:
- LLM:至少 2 个 vLLM 副本(每副本一张 80GB 或等价),前面加负载均衡。监控 GPU 利用率、排队时延、KV 使用率。
- 检索集群:Weaviate / 向量库独立磁盘和内存;Embedding、Rerank 独立推理服务,副本 ≥ 2。
- Dify:API / worker / plugin_daemon 可水平扩展;Postgres 有备份和连接数规划。
- 日志:集中收集 api、agent_backend、vLLM、xinference,按
message_id/agent_run_id关联(第二部分的时间线方法要产品化)。 - 高可用:模型机和 Dify 机不要同一台;GPU 机不要再混跑无关业务(本环境那种共享 H20 不适合 500 人主路径)。
软件参数建议:
| 参数 | 建议 |
|---|---|
| 模型分层 | 简单 FAQ 小模型;制度精读 27B |
| Top-K | 3;Rerank GPU |
| 上下文 | 16K–32K 为主,64K 只给少数应用 |
max_tokens | 按应用分开:FAQ 2048–4096;制度精读 8192–16384 |
| 知识库 | 严格分域,禁止「一个万能库」 |
| 发布流程 | 新 Agent 参数走改库或修复后的 UI,禁止未验证就点发布 |
| SLO | 例如:命中测试 P95 < 3s,端到端 P95 < 30s(无排队) |
建议怎么用:500 人以上先做监控和副本,再谈「要不要把 Rerank 模型换成更大的」。没有副本时,把 Top-K 调到 10 只会让所有人一起慢。
一张总表(复制到变更单上用)
| 档位 | 高峰并发 | LLM | 检索 | Top-K | Rerank | max_tokens | max-model-len | max-num-seqs | Dify 主机 |
|---|---|---|---|---|---|---|---|---|---|
| ~100 人 | 8 | 1×80GB 27B 可共享 | CPU 可接受 | 5 | 开,CPU 可 | 16384–32768 | 65536 | 8 | 32Gi |
| 200–300 | 16 | 80GB 尽量独占 27B | Embedding/Rerank 离 CPU 主机 | 3–5 | 开,GPU | 16384–32768 | 32K 或 64K | 16 | 32–64Gi |
| 300–500 | 24–32 | 27B 多卡或 14B+27B 分层 | 独立 GPU | 3 | 开,GPU | 8192–16384 | 32768 | 24–32 | 64Gi+ 拆库 |
| 500+ | 32+ | 多副本 27B + 小模型 | 检索集群 | 3 | 开,多副本 | 按应用 | 16K–32K | 每卡 8–16 | 集群 |
和本环境怎么对应
当前 207 + H20 共享卡 = 档位 1(约 100 人内部问答)。
已经按档位 1 做对的:
- 27B + 64K 上下文 +
max-num-seqs 8 - 共享卡 0.75 显存,给业务留余量
- CPU 上 bge-m3 + reranker,低并发可接受
- Top-K=5、Rerank 开、
max_tokens=32768
若公司人数涨到 200+ 仍用这一套,会先出现场景 I(GPU 排队)和场景 C(CPU Rerank 变慢),而不是先出现「答案不准」。那时优先拆检索 GPU / 独占 LLM 卡,而不是把 Top-K 加到 10。
改参数时的顺序(避免一次改五个)
- 用第二部分确认是慢、是炸 token、还是排队。
- token 炸 → 只动
max_tokens(两张表,不发布)。 - 检索慢 → 只动切块 / Top-K / Rerank 所在设备。
- 全体排队 → 只动
max-num-seqs、卡是否独占、是否加副本。 - 每次改完:命中测试计时 + 新开对话同一问句 + 看
messages一行。
附录 · 本次实战如何套进本文
2026-08-19 用户反馈「开了 Rerank 更慢、甚至不工作」,页面停在 knowledge_base_search。
按本文走:
- 步骤 1:
messages.status=error,token limit (8192),不是还在检索。 - 步骤 2:检索 2s 结束,第二次 LLM 想了 150s。
- 场景 B:thinking 耗尽 8192;该问句 Prompt 实际 1.4 万。
- 只改
max_tokens→ 32768,不关 Rerank,不点发布。 - 新开对话 21s 成功。属于档位 1 的正确参数,不是架构不够。
附录 · 结论
- 先看
messages和 agent_backend 时间线,再决定动哪一层。 - 停在知识库工具名上,经常是后面的 LLM thinking,不是 Weaviate 死了。
- 常见爆炸点是
max_tokens相对 Prompt+thinking 太小,以及 Xinference 重启丢实例。 - 100 人可以 CPU Rerank + 单卡共享;200 人以上先把检索和 LLM 的硬件拆开;500 人以上先做副本和监控。
- 改新 Agent 配置只改快照和草稿,不要发布。