SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-23

在 VPS 上运行 llama.cpp server:构建与 systemd 配置

从固定标签构建 llama-server,加载 GGUF 模型并提供 OpenAI 兼容 API。本文说明 localhost 绑定、systemd 托管及内存限制,避免 OOM killer 终止服务。

构建内容

在 VPS 上运行 llama.cpp server,只需要一个二进制文件 llama-server。该文件加载一个 GGUF 模型文件,并通过兼容 OpenAI 的 API 响应 HTTP 请求。将任意 OpenAI 客户端指向 http://127.0.0.1:8080/v1 即可使用。安装只是较简单的部分。

其余工作属于运维:固定版本,将端口限制为仅监听 localhost,编写 systemd unit,并决定服务器内存耗尽时的处理方式。本指南将介绍这些内容。如果您尚未在两个明显的选项之间做出决定,请先阅读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 下载模型。如果缺少这些头文件,configure 步骤会失败。

-DBUILD_SHARED_LIBS=OFF 会生成一个自包含的二进制文件。默认构建会将共享库放在可执行文件旁边,因此只将可执行文件复制到 /usr/local/bin 后,启动会因 error while loading shared libraries: libllama.so 失败。

-t llama-server 只构建 server 目标。默认构建还会编译其他工具和测试。在双核 VPS 上,这会额外花费数分钟,而这些文件你不会运行。

-j 2 是有意设置的。每个并行编译任务都会占用自己的工作集,因此在小型套餐上使用 -j $(nproc),最终会触发 c++: fatal error: Killed signal terminated program cc1plus,也就是内核的 OOM killer 终止编译器。请降低任务数,或为构建过程添加 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 8080

LLAMA_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 的已发布文件大小。

图表GGUF file size on disk, published figures, 18 August 2026
此图表背后的数据
[
  {
    "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。许多入门级套餐的磁盘无法容纳它,而且下载后还必须将其读入内存。如果你关注某个特定模型系列,GLM 的相同大小评估可以说明,较大的模型很快会超出 VPS 的承受范围,而较小的同系列模型仍然可以运行。

每次下载前检查 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 对象表示服务器已正常运行。由于 OpenAI 客户端始终会发送 model 字段,因此这里保留该字段。此服务器只加载了一个模型,因此不会使用该值进行选择。

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-pager

enable --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。这是正确行为:每 5 秒重新读取一个 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 和 "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,这样即使代理配置出现错误,也不会让任何人都能访问模型。