在 VPS 上部署 llama.cpp server
从固定标签构建 llama-server,加载 GGUF 模型并提供 OpenAI 兼容 API。本文还介绍 localhost 绑定、systemd 服务和内存限制,避免小型 VPS 因并行编译触发 OOM。
构建内容
在 VPS 上运行 llama.cpp server,只需要一个二进制文件 llama-server。该文件加载一个 GGUF 模型文件,并通过兼容 OpenAI 的 API 响应 HTTP 请求。将任意 OpenAI 客户端指向 http://127.0.0.1:8080/v1 即可使用。安装只是其中较简单的一部分。
其余工作属于运维:固定版本,将端口限制为 localhost,编写 systemd 单元,并决定服务器内存耗尽时的处理方式。本指南将介绍这些内容。如果您还未在两个显而易见的选项之间做出选择,请先阅读Ollama 与 llama.cpp 的权衡,因为该对比文章有意不涵盖本文的操作步骤。
选择一个发行版标签并记录下来
llama.cpp 几乎会为每次合并都标记一个发行版,因此这些标签就是构建编号。截至 18 August 2026,b10488 是最新标签。该项目没有长期维护的稳定分支,因此“最新版本”会不断变化;您测试过的版本才是唯一可以支持的版本。选择一个标签,记录下来,并在克隆仓库、命名二进制文件和编写笔记时始终使用完全相同的字符串。
每个标签还会提供预构建归档文件。对于仅使用 CPU 的 x86 VPS,应使用 llama-b10488-bin-ubuntu-x64.tar.gz;如果您使用的是 ARM VPS 而非 x86 VPS,则旁边还会有 arm64 归档文件。
curl -LO https://github.com/ggml-org/llama.cpp/releases/download/b10488/llama-b10488-bin-ubuntu-x64.tar.gz
tar tf llama-b10488-bin-ubuntu-x64.tar.gz | head解压前先列出归档内容,以确认文件将被放置在哪里。这些二进制文件链接到构建它们的镜像所使用的 C 库,因此在较旧的发行版上启动时,可能会报错,指出未安装所需的 GLIBC_ 版本。在小型 VPS 上从源代码构建只需几分钟,并可避免这一整类问题,因此下面采用这种方式。
从固定标签构建 llama-server
sudo apt update
sudo apt install -y build-essential cmake git libssl-dev
git clone --depth 1 --branch b10488 https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2--branch b10488 使用 --depth 1 克隆时只会检出该标签,不会检出其他内容,因此构建期间不会发生版本漂移。
libssl-dev 很重要,因为 LLAMA_OPENSSL 选项默认启用。该选项让二进制文件之后可以通过 HTTPS 下载模型。如果缺少这些头文件,配置步骤会失败。
-DBUILD_SHARED_LIBS=OFF 会生成一个自包含的二进制文件。默认构建会将共享库放在可执行文件旁边,因此只将可执行文件复制到 /usr/local/bin 后,会因缺少 error while loading shared libraries: libllama.so 而失败。
-t llama-server 只构建服务器目标。默认构建还会编译其他工具和测试。在双核 VPS 上,这会额外耗费几分钟,而这些文件你不会运行。
-j 2 是有意这样设置的。每个并行编译任务都会占用自己的工作集,因此在小型套餐上,-j $(nproc) 最终会导致 c++: fatal error: Killed signal terminated program cc1plus,即内核的内存不足终止程序停止编译器。请降低任务数,或为构建过程添加 swap。
你可能需要更改一个选项:GGML_NATIVE 默认启用,因此编译器会针对执行构建的 CPU 生成代码。如果构建所在的机器就是运行该程序的机器,这正是所需设置。如果只构建一次,然后将二进制文件复制到其他主机,请添加 -DGGML_NATIVE=OFF。否则,二进制文件使用了另一台 CPU 不支持的指令时,会在第一次推理时因 Illegal instruction (core dumped) 退出。
请使用包含标签的名称安装它。
./build/bin/llama-server --version
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-b10488
sudo ln -sfn /usr/local/bin/llama-server-b10488 /usr/local/bin/llama-server--version 会输出构建编号和提交记录。它们必须与检出的标签匹配。如果不匹配,说明构建的不是目标版本。将编号保留在文件名中,并让符号链接指向该文件后,升级只需执行一个 ln -sfn 并重启一次;回滚时使用同一条命令指向旧编号即可。
获取 GGUF 模型,并先检查磁盘
GGUF 是 llama.cpp 加载的单文件格式。一个文件包含权重、分词器和元数据,因此无需安装其他内容。文件名后缀表示量化方式,也就是权重存储时使用的精度:Q4_K_M 是 4-bit 混合量化,Q8_0 是 8-bit,f16 是未量化的半精度文件。
在下载任何内容之前,先创建服务账户和模型目录。
sudo useradd --system --home /srv/llama --create-home --shell /usr/sbin/nologin llama
sudo install -d -o llama -g llama /srv/models
df -h /srv服务器可以使用 -hf 自行获取模型。这是验证构建是否正常工作的最快方式。
sudo -u llama env LLAMA_CACHE=/srv/models /usr/local/bin/llama-server \
-hf ggml-org/gemma-3-1b-it-GGUF:Q4_K_M --host 127.0.0.1 --port 8080LLAMA_CACHE 设置下载目录。如果不设置,文件会保存到执行命令的账户对应的 ~/.cache/llama.cpp 下。对于即将把 home 目录设为不可读的服务来说,这个位置不正确。之后运行 ls -lh /srv/models,因为缓存文件名根据仓库名称生成,而不是根据普通文件名生成。
对于服务,应将模型下载到选定的路径,以便 unit 文件可以稳定地引用该路径。
sudo -u llama curl -L --output-dir /srv/models -O \
https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf首先遇到的限制通常是磁盘空间。以下是两个模型的已发布文件大小,数据核对日期为 18 August 2026。
The data behind this chart
[
{
"label": "gemma-3-1b-it Q4_K_M",
"size_gb": 0.81
},
{
"label": "gemma-3-1b-it Q8_0",
"size_gb": 1.07
},
{
"label": "gemma-3-1b-it f16",
"size_gb": 2.01
},
{
"label": "gpt-oss-20b MXFP4",
"size_gb": 12.11
}
]1B 模型的 4-bit 文件大小为 0.81 GB。同一模型未量化时大小为 2.01 GB,因此量化方式会使文件大小相差两倍以上。采用 MXFP4 的 20B 模型大小为 12.11 GB,许多入门级方案的磁盘无法容纳该文件,而且之后仍需将其读入内存。
每次下载前都检查 df -h。如果 root 文件系统在传输 12 GB 数据时写满,所有其他需要写入的组件都会出错,包括 journal。
手动运行一次,然后检查结果
sudo -u llama /usr/local/bin/llama-server \
--model /srv/models/gemma-3-1b-it-Q4_K_M.gguf \
--host 127.0.0.1 --port 8080 \
--ctx-size 4096 --parallel 1 --threads 2 --no-webui在第二个会话中,向服务器询问其是否已就绪。
curl -s http://127.0.0.1:8080/health文件加载期间,您会收到 HTTP 503 响应,响应正文如下:
{"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}服务器就绪后,响应正文为 {"status": "ok" }。然后发送实际请求。
curl -s http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"local","messages":[{"role":"user","content":"Say hello in five words."}]}'包含 choices 数组的 JSON 对象表示服务器已正常运行。model 字段之所以存在,是因为 OpenAI 客户端始终会发送该字段。此服务器只加载了一个模型,因此不会使用该值进行选择。
OpenAI 兼容 API,以及端口上的其他内容
POST /v1/chat/completions、POST /v1/completions 和 POST /v1/embeddings 是 OpenAI 兼容路由,GET /v1/models 报告已加载的模型。GET /health 是上面的就绪检查,GET /props 返回服务器当前设置,使用 --metrics 启动时,GET /metrics 会公开 Prometheus 计数器。
设置基础 URL 为 http://127.0.0.1:8080/v1,并传入非空的 API 密钥字符串后,任何 OpenAI SDK 都可以使用。在您自行设置 --api-key 之前,系统不会验证该密钥。
不要将他人声称的吞吐量直接作为您自己方案的依据。CPU 推理速度取决于核心数、内存带宽,以及与您共享主机的其他租户,因此请在自己的主机上测量每秒生成的 token 数,并以该结果为准。嘈杂邻居占用的 CPU 时间会表现为生成速度随时间变化,甚至每小时都可能不同。
将其保持在 127.0.0.1 上,并在前面配置代理
--host 默认已绑定到 127.0.0.1,因此在更改配置前,服务器无法从外部访问。保持默认设置。llama-server 没有用户模型、速率限制或有用的审计日志,唯一内置的控制项是 --api-key,它只比较一个字符串。开放的推理端口会为任何发现它的人提供免费的计算资源。对 Ollama 采取同样的错误时,情况也相同:锁定自托管模型 API中的方法在这里同样适用。
在 nginx 中终止 TLS(传输层安全),然后将请求代理到回环端口。
server {
listen 443 ssl;
server_name llm.example.com;
location /v1/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 600s;
}
}流式传输需要 proxy_buffering off。启用缓冲后,nginx 会一直保存服务器发送事件(SSE),直到响应完成。因此,客户端会长时间没有响应,随后一次性收到完整答案。proxy_read_timeout 600s 可覆盖长时间生成,因为默认的 60 秒会将缓慢的响应变为 504 Gateway Time-out。使用 在 nginx 上配置 Certbot 和 Let's Encrypt 获取证书。
systemd 单元
编写 /etc/systemd/system/llama-server.service。
[Unit]
Description=llama.cpp server
After=network-online.target
Wants=network-online.target
[Service]
User=llama
Group=llama
Environment=LLAMA_ARG_MODEL=/srv/models/gemma-3-1b-it-Q4_K_M.gguf
Environment=LLAMA_ARG_HOST=127.0.0.1
Environment=LLAMA_ARG_PORT=8080
Environment=LLAMA_ARG_CTX_SIZE=4096
Environment=LLAMA_ARG_N_PARALLEL=1
Environment=LLAMA_ARG_THREADS=2
ExecStart=/usr/local/bin/llama-server --no-webui
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
MemoryHigh=3G
MemoryMax=3500M
OOMPolicy=stop
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
[Install]
WantedBy=multi-user.target这些设置写在 Environment= 中,因为 llama-server 会读取 LLAMA_ARG_* 中的变量来处理大多数选项,而命令行参数会覆盖对应的变量。这样只需在一个位置修改上下文大小,同时可让 ExecStart 保持足够简短,便于快速查看。
ProtectSystem=strict 会使整个文件系统对该单元只读。这没有问题,因为服务器只读取模型。如果希望服务自行使用 -hf 下载模型,请添加 ReadWritePaths=/srv/models。ProtectHome=yes 会隐藏 /home 和 /root。这也是应将模型存放在 /srv 中的第二个原因:启用 ProtectHome 后,进程根本看不到默认的 ~/.cache/llama.cpp 路径。
sudo systemctl daemon-reload
sudo systemctl enable --now llama-server
systemctl status llama-server
curl -s http://127.0.0.1:8080/health
journalctl -u llama-server -n 50 --no-pagerenable --now 是人们经常跳过的后半部分。如果没有 enable,服务器会在下次重启后消失。如果希望围绕该服务安排定时任务,例如每晚检查新版本,可以使用 systemd 服务和计时器。
在 OOM 发生前决定应采取的措施
内存使用分为两部分,在限制下的行为不同。模型文件默认使用内存映射,因此其页面由文件提供后备:内核可以丢弃这些页面,并在需要时从磁盘重新读取。KV cache 是服务器为每个活动会话保存的逐 token 状态,属于匿名内存,不能被丢弃,因此进程最终会因它而被终止。
因此,单元中的两个限制用途不同。MemoryHigh=3G 是软限制:超过该值后,内核会对 cgroup 施加回收压力,因此已映射的模型页面会被逐出,并在生成下一个 token 时从磁盘重新读取。服务仍会继续运行,但速度会变慢。MemoryMax=3500M 是硬限制:超过该值后,进程会被终止,journal 会明确记录这一点。
llama-server.service: A process of this unit has been killed by the OOM killer.请自行设置 --ctx-size。默认值为 0,表示模型训练时使用的上下文长度。对于现代长上下文模型,这会在启动时分配非常大的 KV cache,导致服务在处理第一个请求前就退出。--parallel 会按相同的成本增加内存使用,因为每个 slot 都保存独立的会话状态。因此,在确认需要并发之前,请将其保持为 1。
启用 Restart=on-failure 后,被终止的服务会自动恢复。如果服务每次启动都会被终止,systemd 最终会放弃重启,systemctl status 会输出 start request repeated too quickly。这是正确的行为:每五秒重新读取一个 12 GB 文件的重启循环,比服务中断更糟糕。修正限制或上下文长度,然后使用 sudo systemctl reset-failed llama-server 清除状态。
请求运行期间,使用 systemctl show llama-server -p MemoryCurrent 查看实际数值。使用 systemd 限制进程内存和 CPU 对这些指令有更详细的说明。
避免为此工作负载使用 swap。模型被换出后,每个 token 都会变成随机偏移的磁盘读取。对模型文件使用内存映射可以实现相同效果,同时降低影响,因为内核会直接从文件读取所需页面。
Ollama 更适合的场景
这是一个选择点。如果您需要通过指定参数运行单个进程,固定构建版本,并使用选定的文件,同时不希望后台有其他组件改变运行状态,请选择 llama-server。
如果您需要模型管理功能,例如按名称拉取模型、在磁盘上保留多个模型、卸载闲置模型,以及通过单条命令升级而无需重新构建,请选择 Ollama。这些工作如果不使用 Ollama,通常需要自行编写脚本完成。在 VPS 上运行 Ollama采用相同的方式,但做出了相反的取舍。两者都提供兼容 OpenAI 的 API,因此无论向哪个方向切换,客户端代码都可以继续使用。
升级固定版本的构建
将 bNNNNN 替换为要迁移到的标签。
cd llama.cpp
git fetch --tags
git checkout bNNNNN
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-bNNNNN
sudo ln -sfn /usr/local/bin/llama-server-bNNNNN /usr/local/bin/llama-server
sudo systemctl restart llama-server旧二进制文件仍保留在磁盘上,因此回滚只需执行一次 ln -sfn,返回到 llama-server-b10488,然后重启服务。迁移前请阅读发行说明。GGUF 文件带有版本信息,旧文件仍可加载,但标志确实会重命名:--mlock 和 --no-mmap 已弃用,改用 --load-mode;如果单元文件传入已移除的标志,服务会在启动时失败,并显示无法识别参数的消息。
故障模式以及您将看到的字符串
error while loading shared libraries: libllama.so:将二进制文件复制到其他位置后出现此错误。默认构建会在二进制文件旁边生成共享库。使用 -DBUILD_SHARED_LIBS=OFF 重新构建,或复制整个 build/bin 目录。
Illegal instruction (core dumped):启动时或首次请求时出现此错误。二进制文件启用了 GGML_NATIVE,但编译目标 CPU 与当前运行它的 CPU 不同。请在此计算机上重新构建,或使用 -DGGML_NATIVE=OFF 配置。
c++: fatal error: Killed signal terminated program cc1plus:构建期间出现此错误。编译器因内存使用过多而被终止。降低 -j,或为构建过程添加 swap,并在完成后将其移除。
curl: (7) Failed to connect ... Connection refused:从笔记本电脑访问时出现此错误。这是正常现象:服务器监听 VPS 的 loopback 地址。请直接在 VPS 上测试,或使用 ssh -L 8080:127.0.0.1:8080 user@your-vps 建立隧道,然后在本地使用 http://127.0.0.1:8080。
HTTP 503 with "message":"Loading model":重启后的前几秒或几分钟内出现此错误。读取数 GB 的文件需要时间,而 systemd 会在进程启动后立即将单元报告为 active,此时模型尚未加载到内存中。
请求挂起,随后返回 504 Gateway Time-out。 代理在模型完成处理前就放弃了。提高 proxy_read_timeout,并关闭 proxy_buffering,这样生成的 token 就会立即发送到客户端。
单元反复启动后停止,并显示 start request repeated too quickly。某个因素会在每次启动时终止进程。检查 journalctl -u llama-server,查找 OOM killer 相关行,然后降低 --ctx-size、降低 --parallel,或提高 MemoryMax。
FAQ
我应该在 VPS 上运行 llama.cpp 的服务器,还是 Ollama?
如果您需要固定确切的构建版本、传入确切的参数,并让一个模型固定保存在单个文件中且不会被后台自动更新,请运行 llama-server。如果您需要模型管理和一条命令完成升级,请运行 Ollama;按名称拉取模型、在磁盘上保留多个模型以及卸载空闲模型,原本都需要您自行编写脚本。两者都提供兼容 OpenAI 的 API,因此日后切换时无需修改客户端代码。
我应该固定哪个 llama.cpp 版本?
固定您实际构建并测试过的任意标签。llama.cpp 几乎会为每次合并提交创建标签,名称通常是构建编号,例如 b10488;截至 18 August 2026,这是最新版本。它没有单独的稳定分支,因此“当前版本”每天会变化数次。使用 --branch <tag> 克隆,将二进制文件安装为包含该标签的文件名,再让符号链接指向它。这样升级和回滚各需一条命令。
llama-server 需要多少 RAM?
先从 GGUF 文件的大小开始计算,再加上 KV cache 的大小。KV cache 会随 --ctx-size 和 --parallel 槽位数量增加。已发布的数据不能替代对您自己环境的测量,因为总占用取决于模型、量化方式以及您允许的上下文长度。在请求处理期间运行 systemctl show llama-server -p MemoryCurrent,并使用显示的数值。
为什么 /health 返回 503 和“Loading model”?
进程已经启动,但模型文件尚未加载到内存中,因此服务器返回 {"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}。每次重启后都会出现这种情况,持续时间取决于读取文件所需的时间。只有当客户端或代理将首次返回的 503 视为硬失败时,这才会成为问题。轮询 /health,直到它返回 {"status": "ok" }。
我可以将 llama-server 直接暴露到互联网吗?
不要将其绑定到 0.0.0.0 并开放端口。它没有账户、速率限制,也没有足以供审计的请求日志;唯一内置的检查是 --api-key,它只比较单个字符串。保持默认的 127.0.0.1 绑定,在前面部署带 TLS 的 nginx,并同时设置 --api-key,这样即使代理配置出现错误,也不会让所有人都能访问模型。