通过llama.cpp + FastAPI构建本地GGUF模型推理服务,根据GPU显存自动路由到合适大小的模型,提供OpenAI兼容接口。
云端 LLM 很强大——直到账单来敲门。或者你需要处理不能发给第三方的数据。或者凌晨三点你遇到了限流。
这就是我构建的架构:用 drop-in OpenAI 兼容 API 在本地运行任意 GGUF 模型,最酷的是:它能自动为你的 GPU 选择合适的模型。
llama.cpp 做推理(编译时带 CUDA 或 Metal 支持)
FastAPI 做服务器(OpenAI 兼容端点)
GGUF 模型存本地目录
NVML(NVIDIA)或 Metal(Apple)做 VRAM 检测
User app (uses openai SDK)
v
http://localhost:8080/v1/chat/completions
v
FastAPI server
+- VRAM router (picks model by query)
+- llama.cpp subprocess (loads GGUF)
+- Stream tokens back
v
Your GPU
Step 1: 下载模型
从 HuggingFace 拉取 GGUF 模型。我常备几种规格以适配不同硬件:
# 7B 模型 - 8GB VRAM 够用
huggingface-cli download TheBloke/Llama-2-7B-Chat-GGUF llama-2-7b-chat.Q4_K_M.gguf
# 13B 模型 - 需要 12GB
huggingface-cli download TheBloke/Llama-2-13B-Chat-GGUF llama-2-13b-chat.Q4_K_M.gguf
# 70B 模型 - 需要 40GB(用 Q3 则 24GB)
huggingface-cli download TheBloke/Llama-2-70B-Chat-GGUF llama-2-70b-chat.Q3_K_M.gguf
mkdir -p ~/models
mv *.gguf ~/models/
Step 2: 服务器骨架
# server.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import subprocess, json, asyncio
app = FastAPI(title="Local LLM Server")
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
model: str = "auto" # < magic
messages: List[Message]
max_tokens: int = 512
temperature: float = 0.7
stream: bool = False
class ChatResponse(BaseModel):
id: str
object: str = "chat.completion"
model: str
choices: list
usage: dict
Step 3: VRAM 感知的路由器
这就是精髓所在。路由器根据以下条件选择合适的模型:
模型是否已经加载
# router.py
import pynvml
def get_available_vram_mb() -> int:
"""Returns free VRAM in MB. -1 if no NVIDIA GPU."""
try:
pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
info = pynvml.nvmlDeviceGetMemoryInfo(handle)
return info.free // 1024 // 1024
except Exception:
return -1 # CPU-only or Apple Silicon
# Model VRAM requirements (rough, for Q4_K_M quantization)
MODELS = {
"llama-2-7b": {"file": "llama-2-7b-chat.Q4_K_M.gguf", "vram_mb": 6000},
"llama-2-13b": {"file": "llama-2-13b-chat.Q4_K_M.gguf", "vram_mb": 11000},
"llama-2-70b": {"file": "llama-2-70b-chat.Q3_K_M.gguf", "vram_mb": 28000},
"mistral-7b": {"file": "mistral-7b-instruct.Q4_K_M.gguf","vram_mb": 6000},
"mixtral-8x7b": {"file": "mixtral-8x7b-instruct.Q4_K_M.gguf","vram_mb": 30000},
}
def pick_model(requested: str, max_tokens: int) -> str:
free_vram = get_available_vram_mb()
needed = max_tokens * 2 # rough KV cache estimate
if requested == "auto":
# Pick the largest model that fits in VRAM
for name in sorted(MODELS.keys(), key=lambda m: -MODELS[m]["vram_mb"]):
if MODELS[name]["vram_mb"] + needed < free_vram:
return name
return "llama-2-7b" # fallback to smallest
return requested
Step 4: 执行推理
我用 llama-cpp-python 做推理层——它封装了 llama.cpp,支持流式输出:
# inference.py
from llama_cpp import Llama
import asyncio
class ModelPool:
def __init__(self, model_dir: str = "~/models"):
self.pool: dict[str, Llama] = {}
self.model_dir = model_dir
def get(self, model_name: str) -> Llama:
if model_name not in self.pool:
cfg = MODELS[model_name]
self.pool[model_name] = Llama(
model_path=f"{self.model_dir}/{cfg['file']}",
n_ctx=4096,
n_gpu_layers=-1, # all layers on GPU
n_threads=8,
)
return self.pool[model_name]
pool = ModelPool()
Step 5: OpenAI 兼容端点
这里就是精髓所在——任何 OpenAI SDK 调用都能用:
# app.py
@app.post("/v1/chat/completions")
async def chat_completions(req: ChatRequest):
model_name = pick_model(req.model, req.max_tokens)
if model_name not in MODELS:
raise HTTPException(404, f"Model {model_name} not found")
llm = pool.get(model_name)
# Run inference (streaming or batch)
response = llm.create_chat_completion(
messages=[m.dict() for m in req.messages],
max_tokens=req.max_tokens,
temperature=req.temperature,
stream=req.stream,
)
return {
"id": f"chatcmpl-{hash(req.messages)}",
"object": "chat.completion",
"model": model_name,
"choices": response["choices"],
"usage": response["usage"],
}
Step 6: 配合 OpenAI SDK 使用
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="not-needed",
)
response = client.chat.completions.create(
model="auto", # router picks the right model
messages=[{"role": "user", "content": "Explain VRAM-aware routing"}],
max_tokens=512,
)
print(response.choices[0].message.content)
从 OpenAI 切换到本地,零代码改动。这意味着我可以:
本地开发(无 API 费用)
CI 测试(确定性,无限流)
部署到生产(需要隐私的用户用本地模型,愿意用 OpenAI 的用户继续用云端)
性能优化心得(踩坑总结)
不要同时加载多个模型。每个模型都要占用 VRAM 存 KV cache。按需加载,空闲 N 分钟后卸载。
大多数情况用 Q4_K_M。Q5 提升微乎其微,Q8 体积翻倍但收益甚少。
投机解码(Speculative decoding)能把速度提升 2-3 倍:小模型起草,大模型验证。
启用 Mlock(use_mlock=True)防止换出——在磁盘慢的机器上加速效果明显。
Apple Silicon 用户:用 Metal 支持编译 llama.cpp,M1/M2/M3 上性能非常出色。
我已经把这套方案打包成了 Strata——一款桌面应用,有聊天 UI、模型浏览器,底层跑的就是这套服务器。
[link] github.com/Omerfaruk-aydn (Strata repo)
桌面应用用 Tauri 2 + React 构建;服务器层是 Python + FastAPI + llama.cpp 这套组合。
最初发布于 omerfarukaydn.com——更多关于桌面 UI、模型市场以及 WebSocket 流式实现的细节。