摘要: 本文记录了使用 vLLM 部署本地 GGUF 格式大模型的踩坑过程。从最初的路径格式错误,到参数解析问题,再到 NumPy 版本兼容性引发的 AttributeError
,最终解决了启动问题。然而,启动后 GPU SM 占用率长时间 100%,这并非卡死,而是 vLLM 进行关键优化步骤(内存分析与 CUDA 图捕捉)的正常现象。最终成功启动服务并明确了 API Base URL。
背景: 想要使用强大的 vLLM 框架来部署一个本地的 GGUF 格式量化大模型(例如 all-hands_openhands-lm-32b-v0.1-Q4_K_M.gguf
),并提供 OpenAI 风格的 API 服务。
环境: Conda 环境,vLLM 0.7.2,多 GPU(使用 tensor-parallel-size=2
)
踩坑一:GGUF 路径格式与 Repo ID 混淆
最初尝试使用模型所在的 目录 作为 vllm serve
的参数:
bash
vllm serve /home/kent/_Project/hf_model/bartowski/all-hands_openhands-lm-32b-v0.1-GGUF \
--host 0.0.0.0 --port 8000 \
# ... 其他参数
报错:
huggingface_hub.errors.HFValidationError: Repo id must be in the form 'repo_name' or 'namespace/repo_name': '/home/kent/_Project/hf_model/bartowski/all-hands_openhands-lm-32b-v0.1-GGUF'. Use `repo_type` argument if needed.
原因: vLLM 默认将 serve
后的第一个参数视为 Hugging Face Hub 的仓库 ID 或本地包含 config.json
等文件的模型目录。直接提供 GGUF 文件所在的目录路径不符合预期。
解决: 需要提供指向 .gguf
文件本身的完整路径。
踩坑二:vllm serve
的参数解析 - model_tag
的要求
修正路径后,尝试使用 --model
参数指定 GGUF 文件:
bash
vllm serve \
--model /path/to/your_model.gguf \
--host 0.0.0.0 --port 8000 \
# ... 其他参数
报错:
usage: vllm serve <model_tag> [options]
vllm serve: error: the following arguments are required: model_tag
原因: vLLM 的 serve
子命令强制要求一个位置参数 model_tag
(即 serve
后面的第一个无 --
前缀的参数)来指定模型。即使使用了 --model
,这个位置参数也不能省略。
解决: 将 GGUF 文件的完整路径作为 serve
后面的第一个位置参数 ,并移除 --model
选项。
bash
vllm serve \
/home/kent/_Project/hf_model/bartowski/all-hands_openhands-lm-32b-v0.1-GGUF/all-hands_openhands-lm-32b-v0.1-Q4_K_M.gguf \
--host 0.0.0.0 --port 8000 \
# ... 其他参数
踩坑三:NumPy 版本兼容性问题
修正命令后再次运行,遇到新的报错:
AttributeError: `newbyteorder` was removed from the ndarray class in NumPy 2.0. Use `arr.view(arr.dtype.newbyteorder(order))` instead.
原因: 环境中安装了 NumPy 2.0 或更高版本。而 vLLM 依赖的某个库(如 transformers
或其依赖 gguf-py
)内部代码使用了 NumPy 1.x 版本中存在的 .newbyteorder()
方法,该方法在 NumPy 2.0 中已被移除。
解决: 降级 NumPy 版本到 2.0 之前。推荐使用一个稳定的 1.x 版本,如 1.26.4。
bash
# 在激活的 Conda 环境中执行
pip install "numpy<2.0"
# 或者指定版本
# pip install numpy==1.26.4
踩坑四(解惑):启动后 GPU SM 长时间 100%
在解决了上述所有报错后,vllm serve
命令终于开始正常执行。模型权重成功加载,但观察 nvidia-smi
或 nvtop
发现 GPU 的 SM 占用率长时间维持在 100%,似乎服务卡住了。
原因: 这并非卡死,而是 vLLM 在进行必要的、但非常耗时的启动优化步骤。主要包括:
-
内存分析 (Memory Profiling):
- 目的: 精确计算在当前 GPU 和配置下,除了模型权重外,有多少显存可以安全地分配给 KV Cache,以最大化吞吐量和并发数。
- 表现: 在日志中可以看到类似
Memory profiling takes XXX.XX seconds
的信息。这个过程需要模拟计算,会长时间占用 GPU。在我们的案例中,这步耗时近 4 分钟。
logINFO 04-01 22:18:59 worker.py:267] Memory profiling takes 231.99 seconds INFO 04-01 22:18:59 worker.py:267] ... the rest of the memory reserved for KV Cache is 7.46GiB.
-
CUDA 图捕捉 (Capturing Cudagraphs):
- 目的: 将模型推理过程中的一系列 GPU 操作预先录制成一个"图"(Graph)。后续处理请求时,可以直接重放这个图,大幅减少 CPU 到 GPU 的调度开销,提升性能。
- 表现: 日志中会出现
Capturing cudagraphs for decoding...
和一个进度条。这个过程同样需要实际运行推理,会长时间占用 GPU。在我们的案例中,这步耗时约 1 分 12 秒。
logINFO 04-01 22:19:02 model_runner.py:1434] Capturing cudagraphs for decoding... Capturing CUDA graph shapes: 100%|████████████| 35/35 [01:12<00:00, 2.07s/it] INFO 04-01 22:20:14 model_runner.py:1562] Graph capturing finished in 72 secs...
结论: 启动 vLLM 服务(尤其是对于大型模型、启用 Tensor Parallel 或 Prefix Caching 时)需要耐心等待。内存分析和 CUDA 图捕捉是正常的优化过程,期间 GPU 高负载是预期行为。
最终成功与 Base URL
当所有优化步骤完成后,日志会显示:
log
INFO 04-01 22:20:14 llm_engine.py:431] init engine (...) took 307.74 seconds
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
这表明服务已成功启动,并监听在 8000
端口。
API Base URL:
- 如果从服务器本机访问:
http://127.0.0.1:8000
- 如果从局域网其他机器访问:
http://<服务器的IP地址>:8000
常用的 OpenAI 兼容端点(例如文本补全)的完整 URL 为:http://<服务器的IP地址>:8000/v1/completions
。
总结: 部署 vLLM GGUF 模型需要注意命令行参数的正确格式、检查依赖库(尤其是 NumPy)的版本兼容性,并理解启动过程中的优化步骤会导致暂时的 GPU 高负载。耐心等待后,即可获得高效的推理服务。