
上个月帮团队搭建一套隔离网络里的 GPU 推理平台选型落在 GPUStack 上。本以为把安装包拷进内网、pip install 一把梭就完事结果真正卡住我的不是 GPUStack 本身的部署而是推理引擎镜像拉不下来、模型文件下载中断、NVIDIA 容器工具依赖缺失。整套离线部署的准备工作量比部署本身多出好几倍。这篇就把我在 GPUStack 离线部署中关于镜像准备和国内加速源的全部经验写清楚包括需要准备哪几类物料、怎么用国内镜像源快速把公网资源“搬”出来、镜像如何导出和转存、内网侧怎么装怎么导模型以及几个值得记下来的坑。GPUStack 是面向大语言模型的 GPU 集群管理平台概念上很像“GPU 版 Kubernetes”但专注推理场景。它提供统一 Web 界面、OpenAI 兼容 API、多用户与 API Key 管理天然适合内网 AI 团队、私有化推理服务和中小企业自建模型底座。如果你是第一次接触也别慌这篇从物料清单讲到实际操作按部就班照做就能复现。1. 离线部署前先搞清 GPUStack 要准备哪几类“镜像”很多教程会告诉你“离线部署只要把安装包传到内网就行”我一开始也信了。真正动手才发现GPUStack 的架构决定了它的离线准备复杂度绝不是一个安装包能覆盖的。1.1 GPUStack 的部署形态server 与 worker 的拆分GPUStack 的核心分两个角色server控制面和 worker计算面。server 负责提供 Web UI、REST APIOpenAI 兼容、模型仓库管理、用户与 API Key 管理worker 则实际接管 GPU 资源并运行推理引擎。生产环境里server 和 worker 可能是两台不同的机器也可能一台机器兼任两个角色。这个拆分直接影响离线准备的颗粒度。server 只需要安装 GPUStack 本体和依赖而 worker 除了安装 GPUStack还需要准备好底层的推理引擎环境。我在实际项目中是把 server 和 worker 分开部署的因为内网里 GPU 机器不只一台worker 的离线准备是否充分直接决定了集群能不能真正跑起来。1.2 推理引擎镜像llama-box / vLLM / SGLangGPUStack 的设计特点是推理引擎并不是编译进二进制的而是以容器镜像的形式由 worker 动态拉起。默认引擎是 llama-boxllama.cpp 系也可以按模型类型选择 vLLM 或 SGLang。这里就出现了第一个关键点这些推理引擎镜像不在 Docker Hub而是在 ghcr.io 的 GPUStack 命名空间下以 ghcr.io/gpustack/ 开头的镜像形式分发。国内直接访问 ghcr.io 的速度非常不稳定隔离网络更是直接失效。所以“把推理引擎镜像搬到内网”是离线部署绕不开的一步。而且SGLang 这类偏重型的引擎镜像体积明显大于 llama-box从几百 MB 到几个 GB 不等准备阶段就得有心理预期。1.3 把需要准备的东西分成三层以我实操视角GPUStack 离线部署要准备的物料可以切成三层层级具体物料典型来源为什么离线必须自备安装层GPUStack 安装包及其 Python 依赖PyPI、GitHub Release内网没有 PyPIpip 装不了运行时层推理引擎镜像llama-box、vLLM、SGLangghcr.io/gpustack/*ghcr.io 在内网不可达数据层模型权重文件GGUF / SafetensorsHugging Face、ModelScope模型文件动辄几个 GB 起这个分层是我后续所有准备工作的主心骨。只要三层都备齐内网部署就是纯执行缺任何一层部署现场就会陷入“翻来覆去找依赖”的泥潭。2. 离线部署物料清单对着它打勾就行与其临场想“还缺什么”不如在公网准备机上先把清单列全。我整理了一份可以直接照做的物料清单包含来源、体量参考和离线保存路径。2.1 一份能照抄的完整物料登记表序号物料来源体量参考离线保存路径示例1GPUStack 安装包wheel 或官方安装包PyPI / GitHub Release几十 MB/data/packages/wheels/2GPUStack Python 依赖 wheelsPyPI几百 MB/data/packages/wheels/3推理引擎镜像 llama-boxghcr.io/gpustack几百 MB/data/packages/images/4推理引擎镜像 vLLM / SGLang按需ghcr.io/gpustack1~数 GB/data/packages/images/5模型文件Qwen3-8B 等Hugging Face / ModelScope5~20 GB 不等/data/models/6NVIDIA 驱动与 nvidia-container-toolkitNVIDIA 官网 / 内网软件库几百 MB/data/packages/drivers/7私有镜像仓库镜像registry:2 或 HarborDocker Hub / 内网已有仓库几十 MB/data/packages/images/做清单时有一点一定要锚定先确定内网要装的 GPUStack 版本、Python 版本、NVIDIA 驱动版本再去下载对应版本物料。GPUStack 迭代很快不同小版本对推理引擎的版本要求有差异公网下载时如果“顺手拿了最新版”进了内网可能出现版本不匹配、API 行为不一致的问题。2.2 模型文件是最容易被忽视的“大头”不少人在准备阶段重点盯 docker 镜像却忽略了模型文件。GPUStack 虽然支持从 HuggingFace 仓库 ID 直接拉模型创建实例但那依赖在线网络在内网环境模型文件的搬运才是最大头。以一个 8B 参数模型为例GGUF 量化版通常 5~10 GBFP16 的 Safetensors 格式约 16 GB。如果内网要跑好几个模型传输和磁盘规划都会成为硬约束。我的建议是在公网准备机上建独立的 /data/models 目录按模型分别存放下载完立刻对每个文件算 sha256 并归档这个哈希清单后面内网核对时极其有用。3. 用国内加速源把公网物料“搬”出来离线准备的核心思路是让所有公网下载都发生在“有外网的准备机”上但前提是准备机本身的下载速度要快。国内网络环境直连 GitHub、Hugging Face、ghcr.io 经常超时或中断这时候国内加速源就是救命的。3.1 pip 换源与离线 wheel 导出GPUStack 安装最稳的路径是通过 pip 装 wheel 包所以先配置国内 PyPI 源。常用的有清华、阿里、腾讯、中科大等开源镜像站。配置方式很简单pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/也可以用清华源pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/配置好源之后在准备机上安装 GPUStack确认版本正确pip install gpustack接着用 pip download 一次性把 GPUStack 及其全部依赖下载到本地 wheels 目录作为内网离线安装的输入mkdir -p /data/packages/wheels pip download gpustack -d /data/packages/wheels这里有个经验pip download 默认会解析完整依赖树基本能把依赖下全但如果你准备机和内网目标机的 Python 版本不一致有些 wheel 装不上。最稳妥的做法是在“和部署环境相同”的机器上做这一步省得后面在目标机上逐个补依赖。3.2 GitHub Release 与 ghcr.io 镜像的国内加速GPUStack 的安装包和部分工具会在 GitHub Release 上发布。国内访问 GitHub Release 偶尔也会很慢两种常见加速方式一是用 GitHub 加速下载服务搜“GitHub 镜像站、GitHub 加速下载”这类关键词找一个当前可用的把原始下载链接的前缀拼上二是如果有代理环境直接走代理下载后转存。注意这类加速服务的稳定性参差不齐下载完记得用 sha256 校验文件完整性。ghcr.io 上的推理引擎镜像比 GitHub Release 更麻烦。目前没有一个广泛稳定可用的 ghcr 公共镜像加速站与其在“拉取这步”死磕我的建议是老老实实在准备机上完成 docker pull再导出为 tar 包带走。离线部署的通用思路就是“把拉取问题消化在公网侧内网只做搬运和导入”。只要准备机能稳定访问 ghcr.io速度慢没关系断点重试也行后面就会顺利很多。3.3 Hugging Face 模型下载的国内路径模型文件下载是体量最大的环节。Hugging Face 直连在国内很不稳定用镜像站是最省事的方式。hf-mirror.com 是一个社区维护的 HF 镜像用法很简单设置环境变量后 huggingface-cli 就会自动走镜像export HF_ENDPOINThttps://hf-mirror.com huggingface-cli download Qwen/Qwen3-8B-GGUF --local-dir /data/models/qwen3-8b-gguf如果不习惯 huggingface-cli也可以用国产的 ModelScope 魔搭社区它对国内网络更加友好下载速度通常更快pip install modelscope modelscope download --model Qwen/Qwen3-8B-GGUF --local_dir /data/models/qwen3-8b-gguf需要注意的是ModelScope 上的仓库 ID 和 Hugging Face 不一定一一对应需要先在魔搭网站上搜到准确的模型 ID 再下载。我实际下载 Qwen3 系列时ModelScope 的速度明显优于 hf-mirror但部分小众模型在 ModelScope 上没有托管所以两条路都要会。3.4 Docker Hub 加速与 registry mirror 配置虽然 GPUStack 的推理引擎镜像在 ghcr.io但内网若需要跑私有镜像仓库比如 registry:2还是得从 Docker Hub 拉镜像。Docker Hub 在国内同样需要加速。常见的加速地址有 DaoCloud、中科大、网易等公开加速器配置在 Docker daemon 的 registry-mirrors 中{ registry-mirrors: [ https://docker.m.daocloud.io, https://docker.mirrors.ustc.edu.cn ] }修改完执行 systemctl restart docker 生效。需要提醒的是公共加速地址的可用性随时可能变化失效就换一个。这类加速器只对 Docker Hub 有效对 ghcr.io 基本无效所以 ghcr.io 的镜像仍然要在准备机上提前拉好。4. 镜像导出、转存与内网仓库落地物料下载完成后面临的下一道工序是镜像怎么高效、无损坏地“搬”进内网。体积大、平台多、文件名乱这三个问题我都踩过。4.1 docker save/load 的正确用法与平台架构在准备机上先拉取推理引擎镜像再导出为 tar 文件docker pull ghcr.io/gpustack/sglang:latest docker save ghcr.io/gpustack/sglang:latest -o sglang.tartar 文件通常体积不小建议压缩后再拷贝。用 gzip 或 pigz 并行压缩效率更高docker save ghcr.io/gpustack/sglang:latest -o sglang.tar pigz -p 16 sglang.tar到了内网先解压再导入gzip -d sglang.tar.gz docker load -i sglang.tar这步有个容易出事的细节如果目标环境包含 ARM 架构比如 NVIDIA Jetson准备机上拉镜像是走 x86 还是 ARM 要区分开。默认 docker pull 会拉当前平台的镜像保存的 tar 也只含当前平台层。跨架构部署时用 --platform 显式指定再保存且一个 tar 只装一个平台避免导入老版本 Docker 时识别不了多平台 manifestdocker pull --platform linux/arm64 ghcr.io/gpustack/llama-box:latest docker tag ghcr.io/gpustack/llama-box:latest llama-box-arm64 docker save llama-box-arm64 -o llama-box-arm64.tar4.2 用私有 Registry 把镜像统一存到内网内网机器多的情况下逐个 docker load 又慢又乱。我的习惯是在内网先搭一个极简私有镜像仓库所有节点统一从仓库拉取。最简单的方式是用 registry 容器docker run -d --name registry --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2然后在准备机上把镜像重命名并推送到内网仓库docker tag ghcr.io/gpustack/sglang:latest registry.local:5000/gpustack/sglang:latest docker push registry.local:5000/gpustack/sglang:latest内网各节点若没配证书用 http 方式访问私有仓库需要把仓库地址加到 docker 的 insecure-registries{ insecure-registries: [registry.local:5000] }改完同样要重启 docker。接下来的关键点是GPUStack 默认会去 ghcr.io 拉推理引擎镜像要让它改用内网仓库需要把引擎镜像地址通过 GPUStack 的配置项指向 registry.local:5000 的对应镜像。不同版本的 GPUStack 这个配置方式可能有差异以官方文档为准但思路是统一的——“重写镜像仓库地址”。4.3 containerd 侧 mirror 让镜像地址不需要重写部分 GPUStack 部署环境用 containerd 作为容器运行时或者你的内网节点本来就在用 containerd。这时候可以在 containerd 配置里把 ghcr.io 直接指向内网仓库实现“运行时透明改写”。修改 /etc/containerd/config.toml在 registry mirrors 段增加[plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.ghcr.io] endpoint [http://registry.local:5000]这样 GPUStack 在运行时仍然认为自己在拉 ghcr.io/gpustack/xxx实际数据却来自内网仓库tag 不用改GPUStack 侧几乎零感知。这个方案是我后来长期沿用的因为少改一处配置就少一个出错点。5. 内网安装 GPUStack 与模型导入全流程物料到位后内网安装其实相当机械。我把完整流程梳理一遍照着执行即可。5.1 从离线 wheel 到 pip 安装把 /data/packages 整体拷入内网后在内网机器上执行离线安装pip install --no-index --find-links/data/packages/wheels gpustack--no-index 表示完全不访问联网源--find-links 指定本地 wheels 目录。看到 gpustack 成功输出版本号安装层就过关了。启动 servergpustack start --data-dir /data/gpustack启动后终端会打印 Web 管理界面和 API 的访问地址一般默认端口为 80 或可通过参数指定。用浏览器打开管理界面确认 server 在线。worker 节点用同样的方式安装 GPUStack然后在 Web 界面的“节点”页里把 worker 加进来加节点时会要求填 worker 地址和 tokentoken 可以在 server 端生成。这里有一个最容易犯的错server 和 worker 的 GPUStack 版本必须完全一致。离线环境下包一旦拷进内网临时换版本非常麻烦所以准备阶段定版本时要把所有节点统一用同一次导出的 wheels。5.2 模型文件导入用本地路径创建模型实例GPUStack 的模型创建支持三类来源HuggingFace 仓库、模型 URL、本地路径。离线环境只能走本地路径。在 Web 界面的模型页选择“从本地路径创建”然后在表单里填 worker 能访问到的模型文件路径。文件格式决定后端选择。GGUF 格式.gguf 文件选 llama-box 后端Safetensors 目录含 config.json、tokenizer 等文件选 vLLM 或 SGLang 后端。以 Qwen3-8B 为例如果下的是 GGUF 量化版路径指向 .gguf 文件即可如果下的是 Safetensors 原始权重路径要指向包含模型配置的目录。多 worker 节点下模型文件尽量放共享存储NFS 或类似的网络文件系统所有 worker 挂载同一个路径。否则每个 worker 都要存一份完整模型磁盘消耗会成倍增长。权限也要注意跑 GPUStack 进程的系统用户必须对模型路径有读取权限否则实例会一直停留在启动失败状态。5.3 验证推理链路是否完全打通模型实例进入 running 状态后用 curl 请求 OpenAI 兼容接口做验证curl -s http://server-ip/v1/chat/completions \ -H Authorization: Bearer api-key \ -H Content-Type: application/json \ -d {model:qwen3-8b,messages:[{role:user,content:你好}],stream:false}能正常返回内容说明 GPUStack、推理引擎镜像、模型文件、GPU 驱动这条链路全部打通。之后无论是接 LobeChat、Open WebUI 还是 One API都是顺手的事。验证时建议先用短文本确认响应后再测长上下文长上下文更容易暴露显存不足或引擎配置问题。6. 离线环境踩坑记录与补救技巧最后把我在这个过程中真正踩过的坑写出来。这些内容基本不会出现在官方文档里但每一个都足以让部署现场停滞半天。6.1 pip 依赖看着装完了实际还缺pip download 默认会把 Python 依赖下全但有些依赖在安装时需要调用系统库离线环境缺了系统库pip 会报编译错误或安装失败。比如某些与 CUDA、编译链相关的包在目标机上经常出现“找不到 gcc / 找不到系统头文件”的报错。我的补救做法是在目标机形态完全一致的环境里先做一次完整的离线安装验证把所有缺的包补齐确认 pip install 能跑通再把最终的 wheels 目录分发到所有节点。不要在每台机器上单独“补丁式”修依赖那样既慢又容易引入版本漂移。6.2 驱动、CUDA 与推理引擎镜像的版本匹配NVIDIA 驱动和容器工具版本如果与引擎镜像内置的 CUDA 版本不匹配会出现容器启动失败或显存初始化报错。检查命令有两个nvidia-smi nvidia-container-cli infonvidia-smi 显示驱动支持的 CUDA 版本nvidia-container-cli info 显示容器运行时是否正确拿到了 GPU。SGLang 镜像对 CUDA 版本的要求比 llama-box 高老显卡建议直接用 llama-box 配 GGUF 模型能避开大量环境兼容问题。这也是为什么我准备镜像时会先把目标 GPU 型号和驱动版本写进清单再决定拉哪些引擎镜像。6.3 模型文件“看似完整”但推理随机崩溃离线传输大文件最容易出现部分损坏。GGUF 文件损坏的表现很隐蔽——模型加载可能成功但推理时随机崩溃或输出乱码。我也是被这个问题折磨过之后才意识到哈希校验不能省。在准备机上对所有模型文件做校验并归档find /data/models -type f -name *.gguf -exec sha256sum {} \;内网导入前再执行一遍同样的命令和公网归档比对。只要哈希能对上就能基本排除文件损坏后续如果出现问题就可以放心去查配置和驱动排查范围会小很多。6.4 内网时钟同步与自签证书的连锁问题内网机器时间如果偏差过大访问自签 HTTPS 镜像仓库时TLS 握手会直接失败报证书无效或时间错误。这个问题很隐蔽因为表面现象是“仓库连不上”排查半天才发现是时钟漂了。所以内网环境第一步就要部署 NTP 或 chrony 同步再处理镜像仓库。私有仓库如果用自签证书要么把 CA 加到系统信任链要么在 docker/containerd 里配置 insecure-registries 兜底别到了现场再临时找证书怎么装。6.5 镜像 tag 改了GPUStack 还在原地拉 ghcr.io这是离线部署最“冤”的一种情况镜像明明已经在内网仓库但 GPUStack 仍不断报 image pull error因为配置里的镜像地址还是 ghcr.io 原始地址。解决办法分两条路一是在 GPUStack 配置中把引擎镜像仓库地址统一重写为内网仓库地址二是用 containerd 的 mirror 机制把 ghcr.io 透明指向内网仓库。关键是这个决策要在进内网之前定好而不是部署当天临时改。我现在的习惯是统一走 containerd mirror因为 GPUStack 侧完全不用感知仓库地址变化维护成本最低。我个人在实际部署里越来越依赖“物料清单 哈希表”的作业方式。任何隔离网络交付先在准备机上把安装包、镜像、模型、驱动全部按清单准备好生成一份包含路径、版本、sha256 的 manifest进内网后所有操作只对照 manifest 执行不再现场试错。这样看起来准备阶段多花了一两天但交付现场几乎不会卡壳。如果后续要接入更多模型类型还可以把模型目录和镜像仓库都做成标准命名规范配合 GPUStack 多后端特性整个集群的扩展会顺畅很多。