AI 工程基础体系 · 第 28/100 篇。内容覆盖机器学习、深度学习与生成式 AI;模型、数据、评测、权限和成本会作为同一生产系统处理。

本地模型与推理引擎:Ollama、llama.cpp、vLLM、量化和部署取舍

一、先区分“模型”和“推理引擎”

模型是参数、网络结构、分词器、配置和对话模板等静态或半静态工件。以自回归语言模型为例,给定输入 token 序列 x1:tx_{1:t},模型估计下一个 token 的条件概率:

P(xt+1x1:t)P(x_{t+1}\mid x_{1:t})

生成过程从已有上下文开始,每次选择或采样一个 token,再把它追加到上下文中:

xt+1P(x1:t)x_{t+1}\sim P(\cdot\mid x_{1:t})

重复这个过程,直到生成停止 token、达到最大输出长度或被服务端截断。

推理引擎负责把这个数学过程变成可执行的系统。它至少要完成:

  1. 加载模型权重和 tokenizer;
  2. 把文本转换为 token;
  3. 按模型要求构造 chat template;
  4. 执行矩阵乘法、归一化、位置编码等算子;
  5. 管理 KV Cache;
  6. 执行贪心、temperature、top-p 等解码策略;
  7. 在 CPU、GPU 或多 GPU 上调度请求;
  8. 输出 token、错误、指标和取消结果。

因此,“同一个模型换一个引擎”并不一定得到完全相同的结果。差异可能来自:

  • tokenizer 或 chat template 不同;
  • temperature=0 时的浮点计算和 tie-breaking 不同;
  • 量化方式不同;
  • RoPE、FlashAttention、采样器实现不同;
  • 停止 token 或最大上下文长度不同;
  • 引擎对模型结构的支持不完整。

Hugging Face 的模型仓库通常包含权重、config.json、tokenizer 文件和生成配置,但这些文件并不自动意味着任何引擎都能加载。模型架构、权重格式和 tokenizer 必须同时兼容。


二、一次生成请求内部发生了什么

1. Prefill 与 Decode

推理通常分为两个阶段。

Prefill 阶段一次性处理已有输入。例如用户输入 2,000 个 token,模型会依次计算这些 token 的隐藏状态,并建立 KV Cache。这个阶段的计算量与输入长度相关,通常适合 GPU 并行,因此经常影响首 token 延迟。

Decode 阶段每次只生成一个新 token。新 token 会读取已有 KV Cache,而不重新计算所有历史 token。这个阶段受内存带宽、KV Cache 访问和调度影响明显,生成速度通常以 tokens/s 表示。

可以用以下流程表示:

sequenceDiagram
    participant C as 客户端
    participant S as 推理服务
    participant T as Tokenizer
    participant M as 模型执行器
    participant K as KV Cache
    participant D as Decoder

    C->>S: chat 请求
    S->>T: 文本 + chat template
    T-->>S: input_ids
    S->>M: Prefill(input_ids)
    M->>K: 写入历史 K/V
    M-->>S: 首个 logits
    S->>D: 采样下一个 token
    loop 直到停止
        S->>M: Decode(新 token, K)
        M->>K: 追加 K/V
        M-->>S: logits
        S->>D: 采样 token
        D-->>C: 流式输出 token
    end

这里的关键状态不是“请求是否收到”,而是:

  • 输入 token 是否完成;
  • Prefill 是否完成;
  • KV Cache 是否分配成功;
  • 请求是否进入 decode 调度;
  • 是否被客户端取消;
  • 是否达到停止条件;
  • 是否因 OOM、超时或模型错误终止。

一个服务即使模型加载成功,也可能在长上下文或并发请求开始后失败,因为权重显存和 KV Cache 显存是两类不同资源。

2. KV Cache 的容量推导

对典型 Transformer,KV Cache 保存每层注意力中的 Key 和 Value。单个请求的缓存大小可近似写为:

MKV=L×2×T×HKV×Dhead×BM_{\mathrm{KV}} = L \times 2 \times T \times H_{\mathrm{KV}} \times D_{\mathrm{head}} \times B

其中:

  • LL:Transformer 层数;
  • 22:分别保存 K 和 V;
  • TT:当前上下文 token 数;
  • HKVH_{\mathrm{KV}}:KV head 数;
  • DheadD_{\mathrm{head}}:每个 head 的维度;
  • BB:每个元素的字节数,例如 FP16 为 2。

假设模型有:

  • 32 层;
  • 8 个 KV heads;
  • 每个 head 维度为 128;
  • 上下文长度为 4096;
  • KV 使用 FP16。

则单个请求约占:

32×2×4096×8×128×2=536,870,912 bytes32\times2\times4096\times8\times128\times2 =536{,}870{,}912\ \text{bytes}

约为 512 MiB。8 个同时保留 4096 token 上下文的请求,仅 KV Cache 就可能需要约 4 GiB,尚未计算模型权重、激活、CUDA workspace 和运行时预留。

这解释了两个常见现象:

  • 把模型从 FP16 量化到 4 bit 后仍然可能在高并发下 OOM;
  • 限制最大上下文长度,往往比继续压缩权重更直接地降低显存峰值。

使用 GQA 或 MQA 的模型时,HKVH_{\mathrm{KV}} 小于 query head 数,因此 KV Cache 会比普通多头注意力小。但不能根据模型参数量直接推断 KV Cache 大小,必须查看配置中的层数、KV head 数和 head dimension。


三、Ollama:以本地开发和单机服务为中心的封装

1. 它解决什么问题

Ollama 提供模型拉取、模型文件管理、运行命令和本地 HTTP 服务。它的价值不主要在于发明新的 Transformer 推理算法,而在于把以下工作封装起来:

  • 下载和缓存模型;
  • 选择适合本机的运行方式;
  • 通过 Modelfile 定义模型参数和模板;
  • 启动本地模型服务;
  • 提供相对简单的 CLI 和 API。

Ollama 底层使用的具体执行后端和支持范围会随版本变化,不能把 Ollama API 等同于某一个固定版本的 llama.cpp。工程上应把它看成“模型管理与服务封装层”,而不是直接假设其内部实现永远不变。

2. 可执行的最小调用

安装 Ollama 后,先运行一个模型:

ollama run llama3.2

随后可以通过本地 API 调用:

curl http://localhost:11434/api/chat \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "llama3.2",
    "messages": [
      {"role": "user", "content": "用一句话解释 KV Cache"}
    ],
    "stream": false
  }'

预期返回 JSON,其中通常包含模型名称、消息内容、是否结束以及耗时统计字段。字段名称和具体统计项应以所安装版本的官方 API 为准;不要在生产代码中无条件依赖未声明的附加字段。

stream: false 适合脚本和测试。流式输出则会让服务分多次返回结果,客户端必须按协议逐段读取,不能把整个响应简单当作一个 JSON 文档处理。

一个常见的 Modelfile 形态如下:

FROM llama3.2

PARAMETER temperature 0.2
PARAMETER num_ctx 4096

SYSTEM 你是一个严谨的中文技术助手。

创建并运行:

ollama create tech-assistant -f Modelfile
ollama run tech-assistant

这里的 num_ctx 会影响最大上下文和 KV Cache 预算,但不代表每个请求都会实际使用这么多 token。设置过大可能增加可用内存压力,设置过小则可能截断输入或无法完成长任务。

3. Ollama 的边界

Ollama 适合:

  • 本地开发和功能验证;
  • 单机个人工具;
  • 低并发内网服务;
  • 希望减少模型转换和启动配置工作的场景。

它不自动解决:

  • 多租户认证和授权;
  • 细粒度配额;
  • 高并发排队;
  • 请求级优先级;
  • 生产级指标、追踪和审计;
  • 多副本流量切换;
  • 模型版本审批。

本地监听地址也不等于安全边界。若把服务暴露到局域网或公网,必须在反向代理或 API 网关增加认证、TLS、请求大小限制、速率限制和审计。不能因为接口路径简单、没有云账单,就认为它没有安全风险。


四、llama.cpp:轻量、可移植和 GGUF 生态

1. llama.cpp 的定位

llama.cpp 是以 C/C++ 为核心的本地推理项目,重点是:

  • CPU 推理;
  • Apple Silicon 等统一内存设备;
  • CUDA、Metal、HIP、Vulkan 等不同后端;
  • 量化模型;
  • 低依赖部署;
  • 通过命令行或 HTTP server 提供推理。

它最常见的模型交换格式是 GGUF。GGUF 不只是“把权重压缩成一个文件”,还可以携带 tokenizer、模型配置和其他元数据,使推理程序更容易正确恢复模型信息。

但 GGUF 是格式,不等于某一种量化算法。Q4_K_MQ5_K_M 等名称表示特定实现中的量化类型和混合策略,不能把它们简单解释为“所有参数严格使用 4 bit”或与任意 GPTQ/AWQ 文件互换。

2. 编译和启动示例

具体二进制名称会随仓库版本变化。一个常见的构建过程是:

git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
cmake -B build
cmake --build build --config Release -j

如果使用 CUDA,通常需要按该版本文档启用 CUDA 构建选项;不同操作系统和 CUDA 版本的 CMake 参数可能不同,不能直接把某个机器上的构建命令当作普适规范。

加载一个 GGUF 文件时,常见命令形态为:

./build/bin/llama-cli \
  -m /models/model.Q4_K_M.gguf \
  -p "解释 Prefill 和 Decode 的区别。" \
  -n 128 \
  -c 4096

参数含义是:

  • -m:模型文件;
  • -p:提示词;
  • -n:最多生成的 token 数;
  • -c:上下文容量。

HTTP 服务通常使用仓库提供的 server 程序,命令名和参数在版本间可能变化。启动前应执行:

./build/bin/llama-server --help

再依据当前版本的帮助文本配置端口、模型路径、上下文长度和 GPU offload 层数。验证服务时,可以先用健康检查或最小请求,再逐步增加上下文和并发;不要一开始就用长上下文压测,否则难以区分模型不兼容、显存不足和服务参数错误。

3. GPU offload 的取舍

在 CPU 主机或显存不足时,llama.cpp 可以把部分层放到 GPU,其他层留在 CPU。这样:

  • GPU 显存不足问题可能缓解;
  • 但 CPU-GPU 之间的数据传输会增加;
  • 每 token 延迟可能明显变大;
  • 性能取决于 PCIe、统一内存架构、后端实现和模型结构。

“能启动”不等于“适合服务”。必须同时测量:

  • 首 token 延迟;
  • decode tokens/s;
  • 并发下的 p50/p95 延迟;
  • 内存峰值;
  • 长上下文时的退化;
  • CPU 占用和温度。

llama.cpp 适合把一个模型可靠地放进边缘设备、开发机或资源受限环境,但在大量并发 GPU 服务上,通常需要更专门的批处理和显存调度能力。


五、vLLM:面向 GPU 服务吞吐的运行时

1. vLLM 解决的核心问题

vLLM 主要面向 GPU 上的模型服务。它的关键机制包括:

  • PagedAttention 或类似的分页式 KV Cache 管理;
  • 连续批次(continuous batching);
  • 对多个请求进行动态调度;
  • 对 Hugging Face 模型格式提供较直接的加载路径;
  • OpenAI 风格的 HTTP API;
  • 在适用版本和硬件上支持张量并行、量化和分布式部署。

这里的“连续批次”不是把一批请求静态收齐后一起执行,而是允许请求在不同时间进入和退出批次。一个请求完成后,调度器可以把新请求插入正在运行的批次,从而减少 GPU 空转。

静态批处理的简化过程是:

等待 N 个请求 -> 一起执行 -> 等最慢请求结束 -> 才接收下一批

连续批次更接近:

请求 A/B 正在 Decode
请求 C 到达并完成 Prefill
A 结束后释放块
请求 D 使用释放的块进入调度

这通常提高 GPU 利用率,但调度器更复杂,公平性、优先级、长请求对短请求的影响也需要额外治理。

2. 启动和调用

一个常见启动形式如下:

pip install vllm
vllm serve Qwen/Qwen2.5-7B-Instruct \
  --host 0.0.0.0 \
  --port 8000

实际可用参数取决于 vLLM 版本、GPU、CUDA、模型架构和安装方式。启动前应查看:

vllm serve --help

例如,某些版本支持通过参数限制 GPU 显存使用比例或指定张量并行规模,但不能假设所有参数在所有版本均存在。对于需要量化的模型,也必须确认该量化格式被当前版本和当前 GPU 后端支持。

vLLM 通常提供 OpenAI 风格接口。调用示例:

curl http://localhost:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer local-token' \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [
      {"role": "user", "content": "解释连续批次为什么能提高吞吐"}
    ],
    "temperature": 0.2,
    "max_tokens": 128,
    "stream": false
  }'

这里的 Authorization 是否真正被验证,取决于前置网关或服务配置。接口长得像 OpenAI API,不代表它自动具备 OpenAI 的认证、计费、内容策略和全部参数语义。客户端应依据实际部署的 API 文档处理错误码、模型名称、流式协议和可用字段。OpenAI API 的请求结构可参考其官方文档,但兼容实现仍需单独验证。

3. vLLM 的边界

vLLM 适合:

  • NVIDIA GPU 上的高吞吐在线服务;
  • 多请求并发;
  • 希望利用连续批次和集中式调度;
  • 需要与 OpenAI 风格客户端集成的团队。

它并不意味着:

  • 任意模型都能无修改加载;
  • 任意 GGUF 文件都能直接使用;
  • 启动后自动获得生产级网关;
  • 量化后一定比 FP16 更快;
  • 多 GPU 一定线性扩展。

模型结构、权重格式、量化 kernel、GPU 架构和 CUDA 版本共同决定可用性。若启动时报“不支持架构”或找不到 kernel,应先确认模型类型和版本矩阵,而不是盲目增加显存参数。


六、量化:压缩权重,不是免费获得更多算力

1. 量化的数学含义

量化把高精度数值映射到较少的离散表示。最简单的对称线性量化可以写为:

q=round(x/s),x^=sqq=\operatorname{round}(x/s),\qquad \hat{x}=s q

其中:

  • xx 是原始 FP16 或 FP32 权重;
  • ss 是缩放因子;
  • qq 是低 bit 整数;
  • x^\hat{x} 是反量化后的近似值。

量化误差为:

e=xx^e=x-\hat{x}

误差并不是均匀影响所有层。某些层、通道或权重离群值对模型质量更敏感,因此实际方法常按 group 或 channel 使用不同 scale,甚至对不同层采用不同 bit 数。

常见概念包括:

  • 权重量化:主要压缩模型权重;
  • 激活量化:压缩运行中产生的激活;
  • KV Cache 量化:减少长上下文和高并发下的缓存占用;
  • 训练后量化(PTQ):训练完成后转换;
  • 量化感知训练(QAT):训练阶段模拟量化误差。

“4 bit 模型”通常首先指权重存储精度,不表示输入、激活、KV Cache 和所有算子都以 4 bit 执行。

2. 一个容量算例

假设模型有 7 billion 个参数:

  • FP16 权重理论大小约为

    7×109×214 GB7\times10^9\times2\approx14\text{ GB}

  • 4 bit 权重理论大小约为

    7×109×0.53.5 GB7\times10^9\times0.5\approx3.5\text{ GB}

但实际文件还包括 scale、zero point、分组元数据、对齐空间,以及运行时 workspace。因此不能简单用“参数量 × bit 数”作为最终显存预算。

如果 4 bit 只压缩权重,而 KV Cache 仍为 FP16,前面算出的单请求 4096 token 约 512 MiB 仍然存在。8 个请求就是约 4 GiB。于是可能出现:

模型文件只有 4 GB
启动单请求成功
并发到 8 个长上下文请求时 OOM

这不是量化失效,而是容量由权重主导转变为 KV Cache 主导。

3. 量化的速度反例

量化可能减少显存读写,但不保证更快:

  • GPU 没有适合该格式的高效 kernel;
  • 推理时需要频繁反量化;
  • 算子仍在 FP16 中执行;
  • CPU 解码受限于其他瓶颈;
  • batch 太小,权重带宽优势无法体现。

因此量化选择必须同时验证质量和性能。一个合理的评测集合应至少包含:

  1. 业务任务准确率或人工可接受率;
  2. 长上下文检索和引用正确率;
  3. 首 token 延迟;
  4. 单请求生成速度;
  5. 并发吞吐;
  6. 显存峰值;
  7. 错误率和服务重启次数。

不能只看模型文件大小,也不能只看单次回答样例。


七、三种方案的运行时取舍

可以把三者放在不同抽象层理解:

方案 主要抽象 常见硬件 优势 主要代价
Ollama 模型管理与易用服务封装 开发机、单机 CPU/GPU 上手快、模型管理简单 调度、治理和高级性能控制有限
llama.cpp 轻量本地推理运行时 CPU、Apple Silicon、边缘设备、混合 CPU/GPU 可移植、低依赖、GGUF 生态 高并发 GPU 调度能力通常不如专用服务引擎
vLLM GPU 高吞吐服务运行时 NVIDIA GPU 集群 连续批次、KV 管理、服务吞吐 环境复杂,版本和模型兼容性要求更高

实际选择应从流量和约束反推:

  • 个人开发工具首先关心安装和模型切换,Ollama 往往足够;
  • 设备端离线推理首先关心二进制体积、内存和 CPU 性能,llama.cpp 更自然;
  • 在线多用户服务首先关心排队、连续批次、KV Cache 和 GPU 利用率,vLLM 更适合;
  • 复杂场景可以分层:开发环境使用 Ollama,边缘节点使用 llama.cpp,中心 GPU 集群使用 vLLM。

不能把“本地模型”理解为一种部署形态。它可能是:

  • 开发者笔记本上的单进程;
  • 内网中的共享模型服务;
  • 工厂边缘节点;
  • 没有公网访问的 GPU 集群;
  • 通过网关提供给多个业务系统的生产平台。

部署形态决定权限、数据流和故障边界。


八、从请求到生产系统的完整数据流

一个有治理要求的生产系统通常不是客户端直接访问模型,而是:

flowchart LR
    U[业务客户端] --> G[API 网关]
    G --> A[认证与授权]
    A --> R[路由与配额]
    R --> Q[队列/限流]
    Q --> M[模型服务]
    M --> K[KV Cache]
    R --> V[模型版本注册表]
    M --> S[对象存储/日志]
    M --> O[指标与追踪]
    G --> O

关键路径如下:

  1. 客户端提交请求;
  2. 网关验证身份、租户和权限;
  3. 路由根据模型版本、任务类型和 GPU 容量选择后端;
  4. 队列执行限流、超时和优先级调度;
  5. 模型服务完成 tokenizer、Prefill、Decode 和流式返回;
  6. 指标系统记录延迟、token 数、排队时间、OOM 和取消;
  7. 审计系统按数据分类策略记录必要元数据,而不是无条件保存全部敏感提示词。

模型权限数据权限不是一回事。一个用户可能有权调用某模型,但无权把某类机密数据发送给该模型;一个服务账号可能可以读取模型仓库,但不能下载训练数据。生产系统应分别控制:

  • 谁能发布模型;
  • 谁能调用模型;
  • 谁能访问模型权重;
  • 谁能访问提示词和输出日志;
  • 谁能修改系统提示词和采样参数;
  • 谁能回滚版本。

九、并发、队列和错误路径

1. 并发不是简单的线程数

模型服务的有效并发受以下资源共同限制:

Cmin(CGPU,CKV,Cqueue,Crate)C \leq \min(C_{\mathrm{GPU}}, C_{\mathrm{KV}}, C_{\mathrm{queue}}, C_{\mathrm{rate}})

其中:

  • CGPUC_{\mathrm{GPU}}:算力和显存允许的并发;
  • CKVC_{\mathrm{KV}}:KV Cache 容量允许的并发;
  • CqueueC_{\mathrm{queue}}:系统可接受的排队长度;
  • CrateC_{\mathrm{rate}}:租户和网关限流。

如果无限增加并发,请求不会无限变快。通常会先出现排队增长,然后出现 p95 延迟恶化,最后因 KV Cache 或 workspace 分配失败而 OOM。

2. 常见失败表现

表现 可能原因 验证方法
模型启动即失败 权重格式或架构不支持 查看启动日志、模型 config、引擎版本
单请求正常,并发后 OOM KV Cache 或 workspace 不足 降低上下文和并发,观察显存峰值
输出乱码或角色错乱 tokenizer/chat template 不匹配 对比官方模板和实际 input token
首 token 很慢 输入过长、Prefill 排队或 CPU offload 分离排队、Prefill、Decode 指标
生成速度逐步下降 长上下文 KV 访问、显存带宽或热降频 固定输出长度,比较不同上下文
流式请求断开后 GPU 仍繁忙 取消没有传播到推理引擎 检查服务端取消日志和活跃请求数
HTTP 兼容但参数无效 兼容层只实现了部分字段 用最小请求和错误请求验证字段语义

客户端必须处理至少四类错误:

  • 连接错误:服务不可达或进程崩溃;
  • 超时:排队、Prefill 或生成过慢;
  • 资源错误:OOM、并发达到上限;
  • 输入错误:模型名、消息格式、上下文过长或参数非法。

对于流式响应,客户端断开时,网关应尝试向后端传播取消;否则模型服务可能继续生成,造成“用户已经退出、GPU 仍在消耗”的隐性成本。


十、模型发布、验证和回滚

模型发布不应只是把一个新文件复制到服务器。至少要绑定以下版本:

  • 模型权重版本;
  • tokenizer 版本;
  • chat template;
  • 推理引擎版本;
  • 量化方法和参数;
  • 启动配置;
  • 安全策略;
  • 评测结果。

可以为每个候选版本建立固定验证流程:

# 1. 计算文件哈希
sha256sum /models/model.Q4_K_M.gguf

# 2. 启动候选实例
# 使用当前引擎版本和固定配置

# 3. 发送固定回归集
# 包括短输入、长输入、中文、结构化输出、拒答和工具调用样例

# 4. 记录:
# 首 token 延迟、生成速度、显存峰值、输出质量、错误率

验证时应固定随机种子、temperature、top-p、最大输出长度和停止条件;对于非零 temperature 的生成,仍不能要求逐 token 完全一致,而应比较任务级指标和可接受输出范围。

发布可以采用:

  1. 新版本先启动为独立实例;
  2. 运行健康检查和回归集;
  3. 小比例流量灰度;
  4. 比较延迟、错误率、质量反馈和 GPU 成本;
  5. 达标后扩大流量;
  6. 保留上一版本实例用于快速回滚。

回滚的本质是恢复“模型版本 + tokenizer + 模板 + 引擎配置”的一致组合,而不是只替换权重文件。若新旧版本使用不同上下文配置或 API 行为,单独回退权重可能仍然无法恢复原服务。


十一、成本模型:不要只计算 GPU 租赁费

生成式 AI 服务的单位成本可以粗略分解为:

Crequest=Ccompute+Cstorage+Cnetwork+Cops+CfailureC_{\mathrm{request}} = C_{\mathrm{compute}} + C_{\mathrm{storage}} + C_{\mathrm{network}} + C_{\mathrm{ops}} + C_{\mathrm{failure}}

其中:

  • CcomputeC_{\mathrm{compute}}:GPU/CPU 运行时间;
  • CstorageC_{\mathrm{storage}}:模型权重、缓存和日志;
  • CnetworkC_{\mathrm{network}}:模型下载、跨节点传输和 API 流量;
  • CopsC_{\mathrm{ops}}:部署、监控、升级和人工维护;
  • CfailureC_{\mathrm{failure}}:错误输出、重试、数据泄露和停机损失。

本地模型可能降低外部 API 调用费,但增加:

  • 模型下载和升级成本;
  • GPU 空闲成本;
  • 安全补丁和驱动维护;
  • 质量评测与回归成本;
  • 高峰期容量预留成本。

批处理任务通常可以牺牲延迟来提高 GPU 利用率;交互式服务则要控制排队和首 token 延迟。把离线批任务和在线对话放在同一队列中,可能导致长批任务阻塞交互请求,因此应至少进行队列隔离或优先级调度。


十二、常见误解与正确判断

误解一:量化 bit 数越低越好

低 bit 通常节省权重空间,但可能损失任务质量、增加实现复杂度,甚至因为缺少高效 kernel 而变慢。应以业务评测和目标硬件实测为准。

误解二:模型能加载就代表兼容

加载成功只说明权重和部分算子能运行。chat template 错误、停止 token 错误、工具调用格式错误,仍可能让线上行为失效。

误解三:OpenAI 兼容 API 就是完全兼容

兼容通常指请求路径和部分字段相似。模型名、流式事件格式、错误码、工具调用、JSON Schema、采样参数和认证行为都可能不同。生产客户端要针对实际后端做契约测试。

误解四:显存只需要容纳模型文件

显存还要容纳 KV Cache、激活、临时 workspace、CUDA 上下文、通信缓冲区和其他进程。容量规划必须按“权重 + 最大并发 KV + 运行时余量”估算。

误解五:本地部署天然更安全

本地部署减少了部分外部传输,但新的风险包括模型服务未认证、日志保存敏感数据、模型文件供应链被替换、内网横向访问和错误配置暴露端口。安全性取决于完整数据流和权限控制,而不是“模型在本地”这一事实。


结语:按约束选择引擎,而不是按名称选择引擎

Ollama、llama.cpp 和 vLLM 不是简单的同类替代品:

  • Ollama 优先降低本地使用和模型管理门槛;
  • llama.cpp 优先解决轻量、可移植和资源受限设备上的推理;
  • vLLM 优先解决 GPU 在线服务中的批处理、KV Cache 管理和吞吐问题。

量化解决的是权重表示和部分内存带宽问题,不能自动解决 KV Cache、并发调度、模型质量或生产治理。真正的部署决策应同时回答:

  1. 模型格式和架构是否兼容;
  2. tokenizer 和 chat template 是否一致;
  3. 权重、KV Cache 和 workspace 是否有容量预算;
  4. 目标是低延迟、低成本还是高吞吐;
  5. 请求取消、超时、排队和 OOM 如何处理;
  6. 模型、数据、权限、日志和版本如何审计;
  7. 出现质量或稳定性问题时能否快速回滚。

当这些问题都能用可验证的配置、指标和恢复流程回答时,本地模型才真正成为生产系统的一部分,而不只是运行在开发机上的一个命令。


系列导航与关联阅读

官方资料

本文依据研究论文、标准组织与主流框架官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。