
大概半年前我把本地一台闲置工作站翻出来跑大模型最开始只是用 Ollama 起个 llama3.1自己在终端里聊两句图个新鲜。后来要跑的东西越来越多vLLM 部署的 Qwen、embedding 模型、甚至微调后的专用模型全散在不同的端口上团队里其他人想用我得挨个告诉他们连这个 IP 的 11434另一个服务在 8000。最崩溃的是有人把 API key 贴在代码里被我骂了一顿之后我才意识到本地大模型确实需要一个网关层来统一收口。这篇文章就是把本地大模型网关 CLI从原理到落地讲透它到底解决什么问题、核心转发链路怎么设计的、怎么用 CLI 完成部署和日常管理、以及我在这半年里踩过的那些坑。内容基于我用 LiteLLM 作为网关底座、配合 Ollama/vLLM 后端、再对接 Codex CLI 等工具的真实经验适合已经在本机跑通过大模型、想进一步规范化管理和对外提供 API 的开发者。不管你是刚接触本地部署的新手还是已经在折腾多模型路由的老手照着这篇做至少能少走两周弯路。1. 为什么本地部署也需要一个网关层1.1 从单机直连到多模型混跑的痛点大多数人最开始接触本地大模型路径基本一致装个 Ollamaollama run llama3.1然后写几行 Python 调http://localhost:11434。这个阶段完全不需要网关直连又快又简单。但一旦模型变多、使用者变多直连就开始难受了。我遇到过几个非常具体的场景同事 A 要调 Qwen2.5-72BvLLM 部署在 8000 端口同事 B 要调 embedding 模型跑在另一个 Python 服务里我自己的脚本还在用 llama3.1。每个调用方都得知道不同的地址和端口。有人直接把内部服务的地址写死在代码里模型一换端口全部报错。想给不同人分配不同权限和配额完全做不到。vLLM 那个服务偶尔 OOM调用方直接看到 Connection refused没有任何重试和降级。这些问题叠加在一起就是一个信号你需要一个中间层让所有调用方只认一个入口、一个协议、一套鉴权至于背后是哪个模型、跑在哪台机器上由网关来路由。1.2 网关到底帮你干了哪几件事本地大模型网关不是多此一举它做的事情可以拆成五件统一入口。所有模型请求都打到同一个 IP:端口调用方不需要关心后端具体部署位置。我自己的习惯是网关固定 8000 端口后面接 Ollama 的 11434、vLLM 的 8001、embedding 服务的 8002调用方只跟网关说话。协议转换。现在主流客户端基本都认 OpenAI 的/v1/chat/completions协议但 Ollama 的原生接口、vLLM 的接口细节并不完全一样。网关把这些差异吞掉对外只暴露一套 OpenAI 兼容 API这样 Codex CLI、Dify、LangChain 这些工具就能无缝接入。模型路由。一个model字段对应到后端真实的模型实例。比如调用方请求local-llama3网关转发给 Ollama 的llama3.1:8b请求qwen-72b转发给 vLLM 的Qwen2.5-72B。甚至可以做模型别名和灰度切换同一个名字背后可以换不同的实际模型。鉴权与配额。网关统一管理 API key可以给不同的人生成不同 key设置每分钟请求数限制或每月 token 配额。这个在团队协作时几乎是刚需。重试、超时与日志。后端偶发故障时网关可以自动重试或 fallback 到备用模型所有请求都有日志出问题时能查到是谁、在什么时间、请求了什么模型。1.3 本地网关与云网关的取舍我知道有人会问直接用云厂商的模型网关不是更省事吗确实云网关在免运维、弹性扩容上有天然优势但本地网关在几个场景下不可替代对比项本地网关云网关数据隐私数据不出内网适合敏感业务数据经过第三方有合规风险延迟内网 5ms无公网波动公网往返延迟不稳定成本一次性硬件投入无按量计费按 token 计费长期成本高可控性完全掌控配置和策略依赖厂商控制台和能力边界运维成本自己维护需一定技术能力基本零运维我的结论是如果只是自己一个人折腾网关确实不是必需但只要有两个以上的人或系统要接入本地模型网关带来的收益就远远大于配置成本。而且本地网关这套思路和将来上云并不冲突——网关本身就支持配置多个后端本地跑不动的超大模型可以配一个云端地址作为 fallback。2. 核心原理与选型思路2.1 网关转发一次请求的完整链路先理解原理再动手部署。一次完整的请求在网关里走这样的链路客户端/CLI 发起请求 ↓ 网关入口 (8000端口, OpenAI兼容协议) ↓ 鉴权校验 (API key 是否有效, 是否超配额) ↓ 模型路由 (根据 model 字段查找配置) ↓ 转发到真实后端 (Ollama/vLLM/其他HTTP服务) ↓ 后端推理, 返回结果 (可能包含流式 SSE) ↓ 网关将结果转换回 OpenAI 兼容格式, 回给客户端这条链路里最容易被忽略的是协议转换这一步。比如 Ollama 原生接口的流式返回格式和 OpenAI 的 SSE 格式差异不小网关要把字段做映射vLLM 的max_tokens参数命名、temperature的取值范围也可能有差异。如果自己写一层转发光处理这些边界情况就要花很多时间。另外网关还有一个常被低估的功能请求/响应的拦截与改写。比如某些后端需要额外的 header 或鉴权信息网关可以在转发时自动注入调用方完全无感。这个能力后面在避坑章节我会举一个实际例子。2.2 三个主流方案对比我调研过几类实施路径最终筛选出三个有代表性的方案方案部署难度资源占用模型路由流式支持适合场景LiteLLM Proxy低pip 一个命令极低纯 Python 进程强YAML 配置即可好个人/小团队的本地网关首选Higress AI 插件中高需要 K8s中网关本身占资源强但配置复杂好K8s 集群内的统一入口Kong AI 插件高依赖 Postgres高中好已有 Kong 体系的企业还有一个非常常见的选项是自己写一个 Flask/FastAPI 转发服务我身边真有朋友这么干过。我的看法很直接如果不涉及二次开发定制别自己造轮子。协议转换、鉴权、限流、fallback、日志这些能力看似简单真正做好很花时间。LiteLLM 这类开源项目已经沉淀了好几年直接站在它的肩膀上更划算。2.3 我为什么最终选 LiteLLM 打底选型时我的核心诉求是轻量、OpenAI 兼容、配置驱动、CLI 友好。LiteLLM 几乎每一条都命中。先说轻量。它是个纯 Python 进程不依赖数据库一条pip install就能跑起来对一台还要跑大模型的工作站来说非常友好。对比之下Higress 和 Kong 都太重了对本地场景是杀鸡用牛刀。再说 CLI。LiteLLM 本身提供litellm命令启动网关、测试配置、查看帮助都很直接正好符合CLI 使用教程这个主题。而且它原生支持 Ollama、vLLM、OpenAI 等上百种后端意味着我之前已经跑起来的服务不用做任何改动只需要在网关配置文件里登记一下。最后是生态。LiteLLM 的社区活跃度很高GitHub 上 issue 响应快文档也比较全。遇到问题搜索一下基本都能找到答案这对本地自建方案来说很重要——你不想卡在一个没人回答的问题上。3. 从零部署环境准备与网关安装3.1 硬件与系统要求先明确一点本地大模型网关本身对硬件要求极低它不做推理只是转发请求。真正的资源消耗在后面的模型后端上。组件最低要求推荐配置CPU2 核4 核及以上内存2 GB4 GB不含模型后端存储1 GB 可用空间10 GB 以上含日志系统Ubuntu 20.04 / macOS 12 / Windows WSL2Linux 服务器或本机 WSL2Python3.93.10/3.11我自己是在一台 Ubuntu 22.04 的机器上跑的内存 64GBGPU 是 RTX 4090。但如果你只是想在 MacBook 上把 Ollama 接入网关也完全跑得动。3.2 最小安装步骤LiteLLM 的安装非常简单核心就是一条命令。不过有两个前置条件容易踩坑Python 版本要 3.9建议用虚拟环境避免污染系统 Python。如果网络条件一般pip 下载依赖可能会比较慢建议先设置国内镜像源。# 创建虚拟环境推荐 python3 -m venv ~/litellm-env source ~/litellm-env/bin/activate # 安装 LiteLLM Proxy 版本 pip install litellm[proxy] # 验证安装 litellm --version看到版本号输出就说明安装成功了。如果litellm命令找不到多半是虚拟环境没激活或者 Python Scripts 目录不在 PATH 里。这里的[proxy]额外依赖很重要它包含了跑 Proxy Server 所需的 FastAPI、uvicorn 等组件。如果只装litellm本体litellm --config启动时大概率会报缺少模块。3.3 编写第一份网关配置LiteLLM 的配置是 YAML 文件核心是model_list字段。我贴一份实际用过的配置并解释每个字段的作用model_list: - model_name: local-llama3 litellm_params: model: ollama_chat/llama3.1:8b api_base: http://127.0.0.1:11434 - model_name: qwen-72b litellm_params: model: openai/qwen2.5:72b api_base: http://192.168.1.50:8000/v1 api_key: sk-dummy-key-not-used - model_name: local-embedding litellm_params: model: openai/bge-m3 api_base: http://127.0.0.1:8002/v1 api_key: sk-dummy-key-not-used litellm_settings: drop_params: true set_verbose: true general_settings: master_key: sk-local-master-key-2024 database_url: sqlite:///litellm.db逐段解释model_name是外部调用方看到的模型名可以自己随便起但最好能让人一眼看懂。litellm_params.model是实际转发使用的模型标识。ollama_chat/前缀告诉 LiteLLM 走 Ollama 的 chat 协议openai/前缀表示走 OpenAI 兼容协议vLLM 默认就是这种。api_base是后端服务的真实地址。vLLM 的地址要带上/v1Ollama 不需要。api_key对本地后端通常是 dummy但字段不能省否则 LiteLLM 会试图从环境变量找 key 然后报错。litellm_settings.drop_params设置为 true表示后端不支持的参数会被自动丢弃而不是报错。这个非常重要因为不同后端的参数支持度不同。general_settings.master_key是网关的超级管理员 key调用时需要在 header 里带上Authorization: Bearer sk-local-master-key-2024。database_url用 SQLite 记录 key 和日志团队多人使用建议开启。3.4 启动网关并用 curl 验证配置写好后启动网关litellm --config config.yaml --port 8000 --num_workers 4--num_workers是并发 worker 数一般设置为 CPU 核心数的 1-2 倍。启动日志里看到Uvicorn running on http://0.0.0.0:8000就说明成功了。此时打开另一个终端用 curl 做三个验证# 验证模型列表 curl http://localhost:8000/v1/models \ -H Authorization: Bearer sk-local-master-key-2024 # 验证对话接口 curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-local-master-key-2024 \ -d { model: local-llama3, messages: [{role: user, content: 你好请自我介绍一下}], max_tokens: 256 } # 验证流式输出 curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-local-master-key-2024 \ -d { model: local-llama3, messages: [{role: user, content: 写一首关于秋天的诗}], stream: true }第一个请求返回 JSON 格式的模型列表第二个请求返回完整的对话结果第三个请求如果不加--no-buffer会看到 SSE 流式输出的原始格式。这一步能跑通网关的核心链路就通了。特意强调--host的问题litellm --config ...默认绑定0.0.0.0但有些版本会只绑127.0.0.1。如果你发现局域网其他机器访问不了启动命令里加一个--host 0.0.0.0强制指定。4. CLI 核心命令实战4.1 认证与 Key 管理启动网关之后第一件事不是立刻调接口而是把鉴权体系配置好。使用 master key 调用接口虽然方便但所有调用方共用一个 key出问题时无法区分是谁。LiteLLM 的 key 管理可以通过 UI 操作但命令行场景下我用得最多的是通过管理接口创建。下面是创建一个 test key 的完整过程# 生成新 key curl http://localhost:8000/key/generate \ -H Authorization: Bearer sk-local-master-key-2024 \ -H Content-Type: application/json \ -d { models: [local-llama3, qwen-72b], max_budget: 10.0 }返回结果里key字段就是新生成的 key。值得留意几个参数的用途models: 限制这个 key 只能调用哪些模型不传则默认所有模型都可访问。max_budget: 美元额度限制这里是 10 美元按 token 用量计费本地模型通常不真计费但可以用它做配额控制。max_parallel_requests: 限制并发请求数防止某个人把后端打满。创建好 key 后每次请求时替换 Authorization header 即可。如果 key 泄漏可以用/key/delete接口删掉不需要重启网关。4.2 最常用的几条 CLI 命令LiteLLM 的 CLI 命令不算多真正每天都用的更少。我把最高频的几条整理在这里# 查看全部命令帮助 litellm --help # 带配置启动网关最核心 litellm --config config.yaml --port 8000 --num_workers 4 # 测试配置文件中所有模型是否可用 litellm --test --config config.yaml # 以调试模式启动输出每个请求的详细日志 litellm --config config.yaml --detailed_log--test是我非常推荐的一个命令。它会对model_list里每个模型各发一次测试请求并在最后输出每个模型的响应时长和状态码。改完配置不知道有没有写错先跑一遍--test是最稳的验证方式。--detailed_log则是在排查问题时很有用。默认日志只记录请求级别但打开详细日志后每个请求的 body、响应、耗时、token 用量都会输出定位问题快了不止一倍。唯一的问题是日志量很大不建议常开。4.3 与 Codex CLI / DeepSeek CLI 等工具对接这部分是很多人真正关心的怎么让 Codex CLI 这类现代化 AI 编程工具用上本地网关的模型。OpenAI 系的 CLI 工具普遍支持两个环境变量OPENAI_BASE_URL和OPENAI_API_KEY。前者指定 API 地址后者指定认证 key。把它们指向本地网关即可# 指向本地网关 export OPENAI_BASE_URLhttp://localhost:8000/v1 export OPENAI_API_KEYsk-local-master-key-2024 # 启动 Codex CLI codex在 Codex CLI 的交互界面里选择模型时选local-llama3或qwen-72b就能让编程助手跑在本地模型上。实测下来llama3.1:8b 做简单的代码补全和解释勉强够用但复杂任务确实不如云端大模型。所以我通常把qwen-72b这类更大的本地模型作为主力小模型当 fallback。DeepSeek CLI、Trae CLI 等工具的接入思路完全一样查一下它读哪些环境变量把 base URL 指向网关节点的/v1路径。值得注意的是有些 CLI 工具内置了模型列表白名单只允许官方模型名。这种情况下可以在网关配置里给模型起一个白名单内的别名比如就叫gpt-4o让网关自动路由到本地模型。这个方法也适用于那些不支持自定义模型的 SaaS 工具。4.4 用 Shell 脚本批量请求和日志CLI 场景下我经常写一些一次性脚本做批量测试。比如验证多个模型在同一组问题上的表现差异#!/bin/bash GATEWAY_URLhttp://localhost:8000/v1 API_KEYsk-local-master-key-2024 MODELS(local-llama3 qwen-72b) QUESTION用一句话解释什么是大模型推理加速 for model in ${MODELS[]}; do echo Testing $model curl -s $GATEWAY_URL/chat/completions \ -H Authorization: Bearer $API_KEY \ -H Content-Type: application/json \ -d { \model\: \$model\, \messages\: [{\role\: \user\, \content\: \$QUESTION\}] } | python3 -m json.tool --no-ensure-ascii | grep content | head -5 done这个脚本唯一的坑在于 JSON 转义。-d参数里嵌套引号很容易出错我的习惯是先写成变量再用python3 -m json.tool做格式化输出避免中文乱码。日志方面如果database_url配置了 SQLiteLiteLLM 会自动记录所有请求明细。查询最近 20 条请求sqlite3 litellm.db SELECT model, total_tokens, response_time_s FROM LiteLLM_SpendLogs ORDER BY startTime DESC LIMIT 20;团队场景下这个日志表就是最简单实用的用量统计工具。5. 性能调优与高可用5.1 并发与排队策略本地网关最容易被忽略的性能瓶颈不在网关本身而在后端模型服务。Ollama 默认只能同时处理一个请求后面的请求会排队vLLM 虽然支持高并发但如果并发数超过 batch 上限照样会 OOM 或拒绝服务。LiteLLM 提供了几个实用参数来控制流量litellm_settings: rpm: 60 # 全局每分钟最多 60 个请求 cooldown_time: 30 # 后端失败后冷却 30 秒再重试 max_retries: 3 # 单次请求最多重试 3 次rpm(requests per minute) 是全局限流防止瞬间打爆后端。cooldown_time会在后端连续出错时自动摘掉它等冷却期过了再恢复流量。这两个参数配合使用能解决大部分网关一启动后端就被打挂的尴尬。如果是多用户场景建议在创建 key 时通过max_parallel_requests限制单人并发。实测中这个参数比 rpm 更有效因为不同用户的使用节奏差异很大。5.2 缓存与上下文优化本地模型推理速度本来就比云端慢而很多请求其实高度相似。LiteLLM 支持 Redis 缓存可以对完全相同的请求直接返回缓存结果不用再经过后端推理。litellm_settings: cache: true cache_params: type: redis host: localhost port: 6379 ttl: 3600如果你的测试 prompt 固定、只是批量跑评估这个缓存能把整体耗时降一个数量级。但要注意cache: true默认只缓存完全相同的请求只要 prompt 里多一个空格就会 miss。另外对需要实时性的对话场景不要开缓存会让用户觉得模型变笨了。还有一个更细粒度的手段设置max_input_tokens和max_output_tokens防止单次请求把上下文窗口塞满。vLLM 后端如果不做限制大 prompt 会挤占所有 GPU 显存其他请求全部排队。5.3 日志和监控网关跑起来之后最怕的是半夜静默挂掉。我配置了两个层面的监控第一层是进程守护。用 systemd 管理网关进程崩溃自动拉起# /etc/systemd/system/litellm.service [Unit] DescriptionLiteLLM Proxy Afternetwork.target [Service] Useryourname WorkingDirectory/home/yourname/litellm ExecStart/home/yourname/litellm-env/bin/litellm --config /home/yourname/litellm/config.yaml --port 8000 Restartalways RestartSec10 [Install] WantedBymulti-user.target第二层是健康检查。每 30 秒探一次/health/liveliness接口连续失败就告警while true; do code$(curl -s -o /dev/null -w %{http_code} http://localhost:8000/health/liveliness) if [ $code ! 200 ]; then echo $(date): gateway down, resp code $code /var/log/gateway-monitor.log fi sleep 30 done这个脚本粗糙但有效配合 systemd 的自动拉起网关基本能做到无人值守。6. 我在实际使用中踩过的坑6.1 unable to locate the codex cli binary 这类报错的排查链路这个报错很典型很多人在本地跑 Codex CLI 找模型对接时都会遇到。表面看是找不到 binary 或运行时组件但实际上有几种完全不同的根因我的排查顺序是第一步确认安装完整性。which codex能不能找到可执行文件如果 PATH 里没有说明安装目录没加入 PATH或者安装过程本身中断了。这一步能排除一半的情况。第二步确认运行时依赖。Codex CLI 依赖 Node.js 运行时版本不对同样会报这个错。用node --version验证低于 18 的都建议升级。如果 PATH 里有多个 Node 版本还可能发生版本冲突——我遇到过装了两个 Node 后 CLI 起来又崩溃的情况。第三步确认网关配置没把 CLI 带偏。如果你跟我一样设置了OPENAI_BASE_URL指向本地网关而网关此时没启动或模型名不匹配CLI 可能会在初始化阶段直接报错。先把网关curl通了再启动 CLI能省很多排查时间。这个报错的本质是CLI 自身运行时组件不全却被很多人误以为是网关配置问题。记住一句话CLI 报错先查自身再查网关。6.2 CLI 无法登录 / 401 鉴权失败我遇到过一种情况curl调网关接口一切正常但 Codex CLI 一登录就 401。仔细排查后发现CLI 读的OPENAI_API_KEY和我 master key 的值不一样——CLI 优先读它自己的配置文件环境变量反而排后面。解决方案是直接改 CLI 的配置文件或者在启动前显式 export 环境变量并确认没有其他配置文件覆盖。这也解释了为什么很多人明明设置了环境变量却没用。还有一种情况是网关的 master key 包含特殊字符被 shell 转义了。建议 key 只用字母、数字、连字符避免$、#、空格等字符。6.3 网关配置了但请求全部超时这个问题最常见的根源是后端模型服务根本没有注册到网关期望的地址上。LiteLLM 转发请求时不会做服务发现它只是按配置的api_base把请求打过去。如果 Ollama 监听在局域网 IP 而不是 127.0.0.1或者 vLLM 服务没启动网关不会报配置错误只会表现为请求发出去很久没响应。排查时先分别 curl 后端地址确认后端本身能通再看网关日志里的具体报错。另一个坑是超时设置太短大模型首 token 时间可能在 10-30 秒尤其是 70B 级别的模型在 CPU 推理时默认 5 秒超时根本不够。建议把网关的request_timeout设到 120 秒以上。6.4 网络层面的坑ping 不通网关、页面空白、默认路由丢失这四个问题看起来是纯网络问题但我在本地网关调试时都碰到过简单记录一下排查方向ping 不通网关 IP如果是跨网段先查路由表。Linux 下ip routeWindows 下route print确认目标网段的 route 存在。我遇到过工作站重启后默认网关丢失所有跨网段访问全部不通的情况临时用sudo ip route add default via 192.168.1.1解决。页面空白LiteLLM 的 UI 打不开或白屏多数是静态资源加载失败或者 worker 进程崩溃。先看启动日志有没有 traceback再确认端口是否被防火墙拦截。Windows 下设置 IP 不填网关保存不了这是 Windows 的固定行为必须先填一个网关才能保存配置。你可以在同一网段内随便填个不存在的 IP后续再改。这不算 bug但很多人第一次遇到会卡住。这些问题的共性是本地网关排错要先物理层、再网络层、再应用层顺序反了会浪费大量时间。6.5 自定义请求头注入一个容易被忽视的需求最后一类坑是网关层需要改请求头。我遇到的实际场景是内部有一个老系统要求所有请求必须带X-Internal-Token头才能访问模型服务。如果让每个调用方都加不仅麻烦而且这个 token 就不能放在客户端了。LiteLLM 支持在后端配置里注入固定 headermodel_list: - model_name: internal-qwen litellm_params: model: openai/qwen2.5:72b api_base: http://192.168.1.50:8000/v1 api_key: sk-dummy extra_headers: X-Internal-Token: s3cr3t-token-value这样调用方只需要访问网关网关在转发时自动带上内部鉴权头老系统的安全要求也满足密钥也不会暴露给终端用户。类似的用法还有注入X-Request-ID做链路追踪、注入租户 ID 做多租户隔离都是这个思路。我在实际操作中比较深的一个体会是本地网关这个组件单看好像只是多了一层转发但它改变的是你使用本地模型的方式。以前我本地部署的模型是自己玩的东西有了网关之后它可以是一个真正可以被多个业务系统稳定依赖的基础设施。Codex CLI、Dify、内部脚本全都走同一个入口权限、日志、限流都是统一控制的。数据不出内网本地模型也能做到像 SaaS 一样地被使用。如果你现在本地已经跑了 Ollama 或 vLLM哪怕只多一个人要从你这儿调模型都建议花半小时把这个网关层搭起来。先单模型跑通再加第二个模型再开 key 给其他人用——循序渐进不要一上来就配一堆模型和复杂的限流策略否则出问题的时候反而不知道从哪查起。