4.2 推理服务沙箱化与对外暴露:把模型变成稳定可调用的服务 训练沙箱关心的是"跑完就走、数据不丢";推理服务则正好相反——它永远不退出,要 7×24 对外提供 API,对"稳定不崩、崩溃能秒级自愈、资源有上限、端口能被外部访问"极度敏感。这一节我们讲清:如何把训练好的权重,封装成一个生产级、可对外暴露的推理沙箱,并进一步覆盖资源隔离、灰度、可观测等现场才会遇到的工程问题。 推理服务的本质差异:从"任务"到"服务" 训练脚本跑完主循环就 ,容器停止是正常结局;推理服务则是 常驻,容器一旦退出就是事故。这带来三个和训练完全不同的设计重心: 重启策略:训练用一次性 即可;推理必须用 (或 compose 的 ),让进程崩溃后被自动拉起。 资源上限:训练可以短时间吃满显存;
训练沙箱关心的是"跑完就走、数据不丢";推理服务则正好相反——它永远不退出,要 7×24 对外提供 API,对"稳定不崩、崩溃能秒级自愈、资源有上限、端口能被外部访问"极度敏感。这一节我们讲清:如何把训练好的权重,封装成一个生产级、可对外暴露的推理沙箱,并进一步覆盖资源隔离、灰度、可观测等现场才会遇到的工程问题。
训练脚本跑完主循环就 exit 0,容器停止是正常结局;推理服务则是 while True 常驻,容器一旦退出就是事故。这带来三个和训练完全不同的设计重心:
docker run 即可;推理必须用 docker run --restart unless-stopped(或 compose 的 restart: unless-stopped),让进程崩溃后被自动拉起。--gpus '"device=0"' 限定只用能用的卡,并在框架层设 max_batch、max_concurrency 等。/health),让编排系统或负载均衡知道该不该把流量打过来。--gpus 的精确限定:别让推理抢了训练的卡训练时我们常 --gpus all 把卡全给;推理服务通常只需要一张指定卡,而且往往和训练任务共享一台机器。这时应精确选定设备,避免推理进程抢占训练用的卡:
docker run -d --name infer-llm \ --restart unless-stopped \ --gpus '"device=0"' \ -p 8080:8000 \ my-infer-image \ python serve.py --model /models/ckpt --port 8000
--gpus '"device=0"' 表示只把第 0 号 GPU 透传给容器。多卡推理同理写 "device=0,1"。这样即使机器上还有训练任务占了其他卡,推理也不会越界。
写法提醒:
--gpus的 device 指定语法对引号敏感,不同 shell 下转义略有差异,请以你所用 Docker 版本官方文档为准;本文给出的是常见可用形态,不臆造具体返回值。
推理服务在容器里监听 8000,但你从宿主浏览器访问的是 localhost:8080,这靠 -p 8080:8000 完成——把宿主 8080 端口转发到容器 8000。这里有个高频认知误区:-p 是"端口映射",不是"给容器开网络"。容器默认就能出网访问外部(如下载依赖、调上游),p 解决的是"外部怎么进来"。
生产上还要注意两点:
-p 出去,那是攻击面。127.0.0.1(如 -p 127.0.0.1:9090:9090),不让外部直连。推理服务常面对不可控的外部流量。除了 --gpus 限定设备,还要在框架层设并发与批处理上限,避免单个大请求耗尽显存。典型做法是服务启动时声明:
max_concurrency);这些参数因推理框架而异(有的叫 max_num_seqs、有的叫 max_batch_size),具体默认值和支持范围请以你选用的推理框架官方文档为准,本文不编造具体数值。原则是:显存占用 = 模型权重 + 并发批处理开销,只要把"并发批处理"上限卡在显存余量内,服务就不会因流量尖峰 OOM。
配合 --restart unless-stopped,即使偶发 OOM 被 kill,容器也会被立即拉起,把"事故"降级为"几秒抖动"。
训练不需要健康检查;推理必须有。常见做法是服务额外暴露 /health(或 /v1/models 之类),返回 200 表示就绪。借助 Docker 原生健康检查,编排系统能感知实例状态:
docker run -d --name infer-llm \ --restart unless-stopped \ --gpus '"device=0"' \ -p 8080:8000 \ --health-cmd "curl -f http://localhost:8000/health || exit 1" \ --health-interval=30s \ --health-retries=3 \ my-infer-image \ python serve.py --model /models/ckpt --port 8000
上面的 --health-cmd 让 Docker 每 30 秒探一次 /health,连续 3 次失败就标记容器 unhealthy。在更上层的负载均衡 / K8s 里,这个状态能用来摘掉坏节点、避免把请求发给"进程在但服务已死"的实例。
训练把 checkpoint 写到卷、靠它续跑;推理则相反——权重是只读输入,决不能让服务进程有写权限。一旦推理进程(或被攻破的请求)改动了挂载的模型文件,下一次重启就加载了"被污染"的权重,而且因为卷是持久的,污染会一直留着。
正确做法是把模型卷以只读方式挂入:
docker run -d --name infer-llm \ --restart unless-stopped \ --gpus '"device=0"' \ -p 8080:8000 \ -v /models/ckpt:/model:ro \ my-infer-image \ python serve.py --model /model --port 8000
:ro 确保容器内对 /model 只有读权限,从源头消除"推理改坏权重"的可能。这也是训练沙箱和推理沙箱在数据安全上的根本差异:一个要写、一个只读。
单实例撑不住流量时,自然想到"多起几个"。在纯 Docker 下,你可以起多个容器、各自映射不同宿主端口,前面放一个 Nginx/负载均衡做转发。这里有两个工程要点:
--name 不同、-p 用不同宿主端口(如 8080/8081),但容器内都监听同一个 8000;前面负载均衡按权重分发。这一节不展开编排系统细节,只点出"推理服务的可扩展性本质是副本 + 负载均衡 + 健康探活"这条主线,它和沙箱化本身是正交的两件事:沙箱解决"怎么把服务干净地装起来",副本与负载解决"怎么让它扛住流量"。
沙箱把环境固定了,但"环境固定"不等于"服务无故障"。推理服务上线后,至少要能看到三类信号,否则半夜出问题你只能盲猜:
docker logs -f infer-llm 看实时输出;生产上应把日志接到集中收集(如宿主的日志目录卷或外部日志系统),别只靠终端。/health 的状态变化触发告警,而不是等用户投诉才发现服务挂了。可观测性不是沙箱化的范畴,但它是"把模型变成服务"这件事不可分割的一半——沙箱保证"能起来",可观测保证"起得来且你看得见它"。
和训练一样,推理服务的这些 -p、--gpus、--restart、healthcheck 参数很多,手敲极易错。最稳妥是把它们写进一份 compose 文件,作为团队共享的"服务说明书"。举一个 compose 形态的骨架(具体字段名随 compose 版本略有差异,请以官方规范为准):
services.infer.image:推理镜像;deploy.resources.reservations.devices:限定使用的 GPU(等价于 --gpus device=0);ports:"8080:8000";restart: unless-stopped;healthcheck:探 /health;volumes:挂 /models 只读权重卷。把它和训练 compose 放在同一仓库不同文件,团队就能"一份配置跑通训练到上线"的完整链路。
为了让你一眼记住两章的区别,这里给一张对照表(文字描述,避免模板套话):
--gpus all 多卡协同;推理通常只需精确指定单卡、避免抢卡。docker logs 看过程;推理靠健康端点 + 监控端口对外暴露。推理服务一旦 -p 出去,就等于在机器上开了一扇可被外部敲的门。沙箱化本身不提供安全,但能在「边界」上帮你做对几件事:
127.0.0.1。安全是「服务化」绕不开的伴生问题。沙箱化负责让环境干净可复现,安全则要靠端口收敛、权限最小化、网络分层这些工程纪律来补。把它和前面的稳定性、可观测放在一张图里看,才是完整的「生产级推理」:
同样给一张推理侧的速查表,按排错频率排序:
connection refused:先确认 -p 宿主:容器 两端端口是否写反、容器里服务是否真监听在那个容器内端口;再 docker logs 看服务是否启动失败。/health 一直失败:往往是健康探活命令写错(如 curl 在镜像里不存在,可改用 wget 或框架自带探活),或探活路径与实际路由不一致。max_batch/max_concurrency 等上限调低,而不是盲目加卡。:ro,推理进程可能写坏了持久权重。--gpus 的 device 指定是否真正生效、是否和训练任务错开。排错顺序建议:先看「进程起没起」(logs)→ 再看「端口通不通」(端口映射)→ 再看「资源够不够」(OOM / 设备限定)→ 最后看「流量对不对」(负载、灰度)。
推理服务沙箱化,记住四个关键词:限定设备(别抢训练的卡)、映射端口(只暴露必要)、设资源上限(防流量尖峰 OOM)、加健康检查(流量只打给活着的实例),并用 restart: unless-stopped 让它崩溃自愈;同时把权重只读挂载、把可观测性补上。把训练(4.1)和推理(4.2)这两套模板收进 compose 文本,你就拥有了一条从"数据进沙箱训练"到"模型出沙箱服务"的完整、可复现工程链路。
至此第四章实战案例已把真实模型的两类典型负载都装进了沙箱。第五章,我们将站在全局视角,把这套方法论提炼成可迁移的标准化工程实践,并展望它在更大团队、更多场景下的演进方向。