本地 vLLM 部署约束
定义
本地 vLLM 部署约束 是指在 Hyper-Extract 中把本地 vLLM 作为 LLM 与 embedding 提供方时,需要同时满足的一组工程限制。
这些限制的核心目标不是“本地模型能响应请求”这么简单,而是让 Hyper-Extract 的结构化抽取流程稳定工作,特别是让受约束解码从第一个 token 起就输出 JSON。
在本文档中的语境
在 Hyper-Extract Provider System 的语境里,Hyper-Extract 支持三类连接方式:OpenAI、阿里百炼和本地 vLLM。三者都通过同一个 create_client() 接口接入,但本地 vLLM 的约束最多,因为服务是用户自己部署,模型行为、输出模板、量化方式和端口配置都要自行保证。
对应的本地连接方式如下:
from hyperextract import create_client
llm, emb = create_client(
llm="vllm:Qwen3.5-9B@http://localhost:8000/v1",
embedder="vllm:bge-m3@http://localhost:8001/v1",
api_key="dummy",
)
这里有两个固定模式:
- LLM 使用
vllm:<model>@<endpoint>。 - embedding 使用
vllm:<embedder>@<endpoint>。 - 即使是本地服务,也仍可在客户端传入
api_key;示例里使用的是dummy。
关键机制与组成
LLM 与 embedding 通常分开部署
本地 vLLM 部署时,LLM 服务与 embedding 服务通常不是同一个进程,而是分开启动、分开监听端口。
典型做法是:
- LLM 服务监听
http://localhost:8000/v1。 - embedding 服务监听
http://localhost:8001/v1。 - Hyper-Extract 在
create_client()时分别传入两个 endpoint。
这种拆分不是可有可无的部署偏好,而是因为 embedding 服务需要显式以嵌入任务启动。
embedding 服务必须使用 --task embed
启动 embedding 服务时,需要显式指定:
vllm serve BAAI/bge-m3 \
--task embed \
--dtype float16 \
--max-model-len 8192 \
--port 8001
这里最关键的参数是 --task embed。没有它,服务就不是按 embedding 接口语义启动,不能按 Hyper-Extract 预期作为向量化提供方使用。
LLM 服务的典型启动参数
一个被文档直接给出的本地 LLM 启动示例如下:
vllm serve /path/to/qwen3.5-9b-gptq-marlin \
--served-model-name Qwen/Qwen3.5-9B \
--trust-remote-code \
--quantization gptq_marlin \
--dtype bfloat16 \
--max-model-len 8192 \
--gpu-memory-utilization 0.90 \
--default-chat-template-kwargs '{"enable_thinking": false}' \
--port 8000 \
--api-key dummy
从这个示例可以提炼出本地部署时最常见、也最值得关注的参数约束:
--served-model-name Qwen/Qwen3.5-9B:对外暴露的模型名,要与客户端中vllm:<model>@<endpoint>的<model>语义保持一致。--port 8000:LLM 服务端口;示例中 embedding 服务单独占用8001。--dtype bfloat16:LLM 服务的数据类型。--max-model-len 8192:上下文长度上限,示例里 LLM 与 embedding 都使用了8192。--gpu-memory-utilization 0.90:GPU 显存利用率阈值。--api-key dummy:即使是本地 OpenAI 兼容服务,也可以配置 API key。--trust-remote-code:示例中启用。--quantization gptq_marlin:量化方案采用 GPTQ-Marlin。--default-chat-template-kwargs '{"enable_thinking": false}':显式关闭 thinking mode。
这些参数并非全部都是 Hyper-Extract 独占要求,但在 Hyper-Extract 的结构化输出场景下,它们共同决定了服务是否可被稳定调用。
为什么强调关闭 thinking mode
推荐使用非 thinking 模型
文档对本地 vLLM 部署给出的明确建议是:优先使用非 thinking 模型。
如果部署的是带 thinking 行为的模型,例如 Qwen3.5 的 thinking mode,那么模型可能在正式答案前先输出:
<think>...</think>
这会直接破坏 Hyper-Extract 依赖的结构化输出前提。
根因:受约束解码要求从第一个 token 开始就是 JSON
Hyper-Extract 的结构化抽取依赖受约束解码,其要求不是“最终能生成 JSON”即可,而是从第一个 token 开始就必须进入 JSON 轨道。
<think> 标签的问题在于:
- 它出现在 JSON 之前。
- 它不是 JSON 的合法起始内容。
- 因而会与受约束解码的“JSON-from-first-token”要求冲突。
这也是为什么本地部署时不能把 thinking 模式仅仅视为“多输出一点推理过程”的无害特性;在结构化抽取场景里,它会让输出从一开始就偏离约束。
关闭方式
如果使用 Qwen3.5-9B 一类模型,文档建议在 vLLM 启动时显式传入:
--default-chat-template-kwargs '{"enable_thinking": false}'
这不是可选美化参数,而是为了避免 <think> 标签打断结构化输出。
量化方案约束
优先 GPTQ-Marlin,不优先 AWQ
文档给出的量化建议很明确:优先使用 GPTQ-Marlin,而不是 AWQ。
推荐原因不是抽象的“效果更好”,而是具体的兼容性问题:
- AWQ 与
vLLM 0.21.0存在兼容性问题。 - GPTQ-Marlin 是文档直接示范并优先推荐的量化方案。
因此,如果本地部署目标是让 Hyper-Extract 稳定完成结构化抽取,那么量化选择不应只看显存占用或通用推理速度,还要考虑与当前 vLLM 版本的实际兼容性。
典型部署形态
除直接运行 vllm serve 外,文档还给出了一个 Docker 启动样例:
docker run --runtime nvidia --gpus all \
--ipc=host \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
vllm/vllm-openai:latest \
--model Qwen/Qwen3.5-9B \
--trust-remote-code
这个示例说明,本地 vLLM 在 Hyper-Extract 语境中本质上是作为 OpenAI 兼容接口来暴露服务的;真正重要的不是采用裸机还是 Docker,而是最终能提供 Hyper-Extract 可连接的 endpoint,并满足上文提到的输出与兼容性约束。
细节、边界与例外
不是所有“能返回 JSON”的模型都适合
本地部署的判断标准不是模型偶尔能生成 JSON,而是它能否在受约束解码条件下稳定从首 token 输出 JSON。只要模型会先吐出 <think>、解释文字或其他前缀,即使后面补成 JSON,也不满足要求。
本地与云端的 thinking 问题边界不同
文档还给出了一个重要对照:DeepSeek-R1 通过阿里百炼接入时被验证为可用,因为百炼后端会过滤 <think> 标签,所以 AutoGraph 与 with_structured_output 可以正常工作。
但这个例外不应被误解为“thinking 模型天然兼容结构化输出”。真正成立的是:
- 通过特定云通道时,服务商后端可能帮你清洗
<think>。 - 通过其他接入渠道时,这种过滤未必存在。
- 在本地 vLLM 场景里,应默认自行承担 thinking 输出带来的风险。
因此,本地 vLLM 部署约束 与云端兼容性规则不能混为一谈。
api_key 在本地仍可能出现
虽然服务部署在本机,示例仍在服务端和客户端都使用了 api_key="dummy" / --api-key dummy。这说明在 OpenAI 兼容接口语境下,本地服务也可能保留 API key 这一层协议字段;不要因为“是本地”就假设客户端一定不传 key。