ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Colab 无 Docker 部署 Triton 推理服务实战

Colab 无 Docker 部署 Triton 推理服务实战

1. 为什么要在 Colab 上绕开 Docker 跑 Triton

先把结论摆出来:Triton Inference Server 完全可以在不依赖 Docker 的前提下,直接跑在 Google Colab 的运行时里,并且能正常加载 HuggingFace 格式的模型对外提供 HTTP/gRPC 推理服务。这件事的价值不在于“炫技”,而在于 Colab 这种免费 GPU 环境本身不支持嵌套容器,你没法在里面起 Docker daemon,所以想用 Triton 做模型服务验证、性能压测、接口联调,就必须走原生二进制这条路。

我最初碰到这个需求,是因为手上有个 HuggingFace 的文本分类模型要做线上服务的接口验证,本地机器没有独立显卡,跑 CPU 推理慢得离谱,而 Colab 给的 T4 免费额度刚好够用。但 Colab 的运行时是个受限的容器环境,docker命令根本装不起来,systemctl也没有,官方文档里那套docker run --gpus all ...的启动方式直接失效。于是只能退而求其次,把 Triton 的服务端二进制、后端共享库、模型仓库这三样东西手动拼起来。

这里要先讲清楚 Triton 的架构,不然你后面装东西会一头雾水。Triton 本身是个推理调度框架,它自己不实现模型计算,真正干活的是各种backend(后端)。比如 TensorRT、ONNX Runtime、PyTorch、Python 后端等等。服务端主程序负责接收请求、做批处理调度、管理模型生命周期,然后把具体的计算任务分发给对应的 backend 动态库。所以“不用 Docker 构建 Triton”的本质,就是手动把主程序和你要用的那个 backend 放到正确的位置,再配好模型仓库目录结构,让主程序启动时能找到它们。

这套方案适合谁?我认为有三类人最需要:第一类是想在免费 GPU 环境里快速验证 Triton 服务接口的算法同学;第二类是想学习 Triton 模型仓库组织方式、但不想被 Docker 环境问题卡住的新手;第三类是做教学演示或者技术分享,需要一个“开箱即跑”的最小可复现环境的从业者。如果你是要上生产,那还是老老实实用官方容器镜像,本文的场景限定在验证、学习、演示这个范围内。

还有一个关键点得提前说:Colab 的运行时是临时的,断开连接后环境全部重置,所以整套流程必须脚本化、可重复执行。我后面给的步骤都是可以直接粘进 notebook 单元格里跑的,你不需要每次手动点。

2. 环境准备与依赖梳理

2.1 Colab 运行时的基础盘点和版本对齐

动手之前先摸清楚 Colab 给你的是什么。新建一个 notebook,第一件事是确认 GPU 类型和系统信息:

!nvidia-smi !cat /etc/os-release | head -5 !python --version

我实测下来,Colab 免费版通常给的是 Tesla T4(16GB 显存),系统是 Ubuntu 20.04 或 22.04 的变体,Python 版本在 3.10 到 3.11 之间浮动。这三个信息决定了你后面下载哪个版本的 Triton 二进制包——Triton 官方发布的 Linux 二进制是分发行版和架构的,选错了启动会直接报GLIBC版本不匹配。

注意:Colab 的 Python 版本会随时间变化,不要照抄网上老教程里的固定版本号,每次都以!python --version的实际输出为准。

版本对齐这件事我踩过坑。有一次我图省事,直接下了个比较老的 Triton 发行包,结果它依赖的libstdc++版本比 Colab 系统里的还低,启动时报了一堆符号找不到。后来我的做法是:优先选 Triton 官方发布的最新稳定版二进制,因为新版本对系统库的兼容性通常做得更好,而且 backend 的 API 也更稳定。

2.2 不用 Docker 时到底需要哪些组件

把 Docker 镜像拆开看,一个能跑 HuggingFace 模型的 Triton 服务其实只需要四样东西:

组件作用获取方式
Triton 服务端主程序接收请求、调度、管理模型官方 release 的二进制压缩包
Python backend 共享库执行 Python 写的模型逻辑官方 release 里对应 backend 的压缩包
模型仓库目录存放模型配置和权重手动按规范创建
Python 依赖transformers、torch 等pip 安装

这里有个容易混淆的点:HuggingFace 模型在 Triton 里并不是“原生支持”的,Triton 没有叫“HuggingFace backend”的东西。你要么把模型转成 ONNX 或 TensorRT 走对应 backend,要么用Python backend自己写一段加载transformers的推理脚本。本文选的是 Python backend,原因是它最灵活、对模型格式零要求,改几行代码就能换模型,特别适合验证阶段。代价是性能不如 TensorRT 那种编译优化过的后端,但对于接口验证和功能演示完全够用。

2.3 目录结构的规划

在 Colab 里我习惯把所有东西放在/content/triton_workspace下面,结构长这样:

/content/triton_workspace/ ├── triton_server/ # 解压出来的服务端 │ ├── bin/tritonserver │ ├── lib/ │ └── backends/ # backend 动态库放这里 ├── model_repository/ # 模型仓库 │ └── hf_text_classifier/ │ ├── config.pbtxt │ └── 1/ │ └── model.py └── logs/

这个结构不是随便定的。Triton 启动时通过--model-repository参数指定仓库根目录,然后它会扫描每个子目录当作一个模型,模型目录下必须有版本号子目录(比如1/),版本目录里放实际的模型文件。backend 的查找路径则通过--backend-directory指定,默认是服务端目录下的backends/。把 backend 放在服务端目录里,启动时就不用额外传参,省事。

3. 核心实操:从零把服务跑起来

3.1 下载并解压 Triton 服务端二进制

Triton 的二进制发布在官方的 release 页面,文件名类似tritonserver-2.xx.x-linux-x86_64.tar.gz。在 Colab 里用wget直接拉:

import os os.makedirs('/content/triton_workspace', exist_ok=True) %cd /content/triton_workspace # 以实际最新稳定版为准,这里用变量方便替换 TRITON_VERSION="2.46.0" !wget -q https://github.com/triton-inference-server/server/releases/download/v${TRITON_VERSION}/tritonserver-${TRITON_VERSION}-linux-x86_64.tar.gz !mkdir -p triton_server !tar -xzf tritonserver-${TRITON_VERSION}-linux-x86_64.tar.gz -C triton_server --strip-components=1

--strip-components=1这个参数很关键。压缩包里通常有一层顶层目录,不加这个参数解压出来会多套一层,导致bin/tritonserver的路径对不上。解压完先验证一下主程序能不能跑:

!./triton_server/bin/tritonserver --version

如果这一步就报错,八成是缺系统库。Colab 环境相对干净,常见的缺失是libb64、libcurl之类,用apt-get install补上即可。我遇到过一次libnuma缺失,装完就好。

3.2 单独获取 Python backend

服务端主程序不含任何 backend,Python backend 要单独下。它的 release 文件名类似python_backend-vX.Y.Z.tar.gz,解压后里面有个libtriton_python.so,这就是 backend 动态库。把它放到triton_server/backends/python/下面:

PYB_VERSION="0.15.0" !wget -q https://github.com/triton-inference-server/python_backend/releases/download/v${PYB_VERSION}/python_backend-v${PYB_VERSION}.tar.gz !mkdir -p triton_server/backends/python !tar -xzf python_backend-v${PYB_VERSION}.tar.gz -C triton_server/backends/python --strip-components=1

提示:backend 的版本要和服务端主程序大致匹配。差一两个小版本通常没事,但差太多可能出现 API 不兼容,启动时日志里会提示 backend 加载失败。

放好之后可以ls看一眼,确认libtriton_python.so在backends/python/目录下。这个文件就是 Python backend 的全部,Triton 启动时会去这个路径找。

3.3 编写模型仓库配置 config.pbtxt

config.pbtxt是 Triton 的模型配置文件,它告诉服务端这个模型叫什么、用哪个 backend、输入输出是什么形状和类型。对于 Python backend,最小配置大概是这样:

name: "hf_text_classifier" backend: "python" max_batch_size: 8 input [ { name: "TEXT" data_type: TYPE_STRING dims: [ -1 ] } ] output [ { name: "LABEL" data_type: TYPE_STRING dims: [ -1 ] }, { name: "SCORE" data_type: TYPE_FP32 dims: [ -1 ] } ] instance_group [ { count: 1 kind: KIND_GPU } ]

几个参数值得展开说。max_batch_size: 8是批处理上限,Triton 会把多个请求攒成一批一起推理,这个值要根据显存和模型大小调。T4 上跑一个几百兆的文本分类模型,8 到 16 比较稳妥,设太大容易 OOM。dims: [ -1 ]里的-1表示这一维是动态的,对文本来说就是序列长度不固定,Triton 允许这种动态维度。instance_group里的KIND_GPU表示用 GPU 跑,如果你只有 CPU 就改成KIND_CPU。

这里有个细节:Python backend 的输入输出名字(TEXT、LABEL、SCORE)是你在model.py里自己约定的,config 里写什么,代码里就得对应什么,两边必须一致,否则请求进来会报字段找不到。

3.4 写 Python backend 的 model.py

model.py是真正干活的地方,它必须实现一个TritonPythonModel类,并且提供initialize、execute、finalize三个方法。加载 HuggingFace 模型的核心逻辑就在initialize里:

import json import numpy as np import triton_python_backend_utils as pb_utils from transformers import pipeline class TritonPythonModel: def initialize(self, args): self.classifier = pipeline( "text-classification", model="distilbert-base-uncased-finetuned-sst-2-english", device=0, top_k=None ) self.model_config = json.loads(args["model_config"]) def execute(self, requests): responses = [] for request in requests: input_tensor = pb_utils.get_input_tensor_by_name(request, "TEXT") texts = input_tensor.as_numpy().astype(str).reshape(-1).tolist() results = self.classifier(texts) labels = np.array([[r[0]["label"]] for r in results], dtype=object) scores = np.array([[r[0]["score"]] for r in results], dtype=np.float32) out_label = pb_utils.Tensor("LABEL", labels) out_score = pb_utils.Tensor("SCORE", scores) responses.append(pb_utils.InferenceResponse( output_tensors=[out_label, out_score] )) return responses def finalize(self): self.classifier = None

这段代码有几个实操要点。第一,device=0表示用第一块 GPU,Colab 只有一块,写 0 就行。第二,top_k=None让 pipeline 返回所有类别的分数,方便你后面自己取 top-1,如果只要最高分可以去掉这个参数。第三,as_numpy().astype(str)这一步不能省,Triton 传进来的字符串张量是字节串,直接喂给 transformers 会报类型错误,必须先转成 Python 字符串。第四,输出张量的形状要和 config 里声明的dims对得上,-1是动态维,实际填多少由你的数据决定。

注意:triton_python_backend_utils这个模块是 Python backend 运行时注入的,你在普通 Python 环境里 import 会失败,只有在 Triton 启动的模型进程里才能用。所以本地调试 model.py 的逻辑时,要把这部分单独抽出来测。

3.5 启动服务并验证接口

万事俱备,启动命令如下:

!./triton_server/bin/tritonserver \ --model-repository=/content/triton_workspace/model_repository \ --backend-directory=/content/triton_workspace/triton_server/backends \ --log-verbose=1 \ > /content/triton_workspace/logs/server.log 2>&1 &

后台启动后等几秒,看日志确认模型加载成功:

!sleep 15 && grep -E "successfully loaded|error|failed" /content/triton_workspace/logs/server.log

看到successfully loaded 'hf_text_classifier'就说明模型起来了。接着用 HTTP 接口发一个推理请求验证:

import requests, json payload = { "inputs": [{ "name": "TEXT", "shape": [1], "datatype": "BYTES", "data": ["This movie is absolutely wonderful"] }] } resp = requests.post( "http://localhost:8000/v2/models/hf_text_classifier/infer", json=payload ) print(json.dumps(resp.json(), indent=2))

返回里能看到LABEL是POSITIVE、SCORE接近 1,就说明整条链路通了。这里shape: [1]表示一个样本,datatype: "BYTES"对应 config 里的TYPE_STRING,这两个字段写错是最常见的 400 错误来源。

4. 踩坑实录与排查速查表

4.1 模型加载失败的几类典型原因

我在 Colab 上折腾这套东西,模型加载失败占了问题的大头。归纳下来无非几类:backend 找不到、config 语法错、model.py 导入依赖失败、显存不够。日志是最好的排查入口,--log-verbose=1会把每个模型的加载过程打出来,哪一步断的一目了然。

backend 找不到的报错长这样:failed to load backend library: libtriton_python.so: cannot open shared object file。这说明--backend-directory指错了,或者 backend 目录名不是python。Triton 是按目录名匹配 backend 的,config 里写backend: "python",它就去backends/python/找libtriton_python.so,目录名差一个字母都不行。

config 语法错通常是 protobuf 解析失败,报错里会带行号。最常见的是漏了逗号、括号不配对、字段名拼错(比如把max_batch_size写成max_batchsize)。这种错误没有捷径,对着报错行号一行行看。

model.py 导入失败一般是依赖没装全。Python backend 跑在独立的 Python 进程里,它用的是系统 Python 还是某个虚拟环境,取决于启动时的配置。Colab 里默认就是系统 Python,所以你pip install装的包它能直接用。但如果报ModuleNotFoundError: No module named 'transformers',先确认 pip 装到了正确的 Python 环境。

4.2 显存与批处理的平衡

T4 只有 16GB 显存,一个 distilbert 级别的模型加载完大概占 1 到 2GB,剩下的空间要留给推理时的激活值和批处理。max_batch_size设太大,请求一多就 OOM,日志里会出现CUDA out of memory。我的经验是先用小 batch 跑通,再逐步往上加,观察nvidia-smi的显存占用,留出 20% 余量比较安全。

还有一个隐蔽的坑:Python backend 默认可能起多个实例(instance_group里的count),每个实例都会加载一份模型到显存。如果你设了count: 2,显存占用直接翻倍。验证阶段设count: 1就够了,别贪多。

4.3 常见问题速查表

现象可能原因排查动作
启动即退出,无模型加载日志主程序缺系统库看server.log开头的error while loading shared libraries
模型状态为 UNAVAILABLEbackend 路径错或 config 语法错开--log-verbose=1看具体报错行
推理返回 400输入 shape/datatype 与 config 不符对照 config 的 input 定义检查请求体
推理返回 500model.py 内部异常看 server.log 里的 Python traceback
CUDA OOMbatch 太大或实例数太多降max_batch_size,count设 1
请求超时首次推理要加载模型,冷启动慢先发一个预热请求,或调大客户端超时

4.4 几个独家避坑心得

第一个心得:Colab 的端口默认只对 notebook 内部可见,你在 notebook 里用requests打localhost:8000没问题,但想从外部访问就得用 Colab 提供的端口转发机制。验证阶段在 notebook 内部自测完全够用,不用折腾外网暴露。

第二个心得:服务进程要用&放后台,并且把输出重定向到日志文件。Colab 的单元格是同步执行的,如果前台启动 Triton,这个单元格会一直卡住不返回,你就没法执行后续的验证代码了。重定向到文件还有个好处,出问题时能翻完整日志。

第三个心得:模型仓库路径用绝对路径。相对路径在 Colab 里容易因为工作目录变化而失效,尤其是你在不同单元格之间%cd来%cd去的时候。我统一用/content/triton_workspace/...这种绝对路径,从没出过路径问题。

第四个心得:transformers 的 pipeline 首次调用会触发模型下载,如果模型没缓存,第一次推理会卡十几秒甚至更久,容易被误判成服务挂了。我的做法是在initialize里先跑一次 dummy 推理做预热,把模型权重加载和 CUDA 上下文初始化都提前完成,这样正式请求进来就是热的。

5. 性能观察与后续扩展方向

5.1 实测性能的大致量级

在 T4 上跑 distilbert 文本分类,单条短文本的推理延迟大概在十几毫秒量级,批处理到 8 条时单条均摊延迟能降到几毫秒。这个数字不算惊艳,但作为接口验证和功能演示完全够用。真正要压性能,还是得走 ONNX Runtime 或 TensorRT backend,把模型编译优化一遍,延迟能再降一个档次。Python backend 的优势从来不是性能,而是改起来快、换模型零成本。

我做过一个简单对比:同一个模型,Python backend 和 ONNX Runtime backend 在相同 batch 下的吞吐差距大概在两到三倍。所以如果你的目标是验证“服务能不能跑通、接口对不对”,Python backend 是最省事的选择;如果目标是“压测出真实生产性能”,那就得换后端,而换后端的前提是先把模型导出成 ONNX,这又是另一套流程了。

5.2 把这套流程脚本化的建议

Colab 环境是临时的,每次重连都要重来一遍,所以强烈建议把整个流程写成一个可重复执行的脚本或者 notebook 的“一键运行”单元格。我的做法是把下载、解压、写 config、写 model.py、启动、验证这几步拆成独立的单元格,按顺序执行,中间任何一步失败都能单独重跑,不用从头再来。

更进一步,可以把config.pbtxt和model.py的内容用 Python 的字符串写进文件,而不是手动创建,这样整个环境搭建就是纯代码驱动的,复制到任何 Colab notebook 里都能跑。模型仓库的目录创建也用os.makedirs搞定,避免手动操作带来的路径错误。

5.3 可以继续往下做的方向

这套最小环境跑通之后,往下扩展有几个自然的方向。一是多模型仓库,在model_repository下再建几个模型目录,Triton 会同时加载,你可以验证多模型并存的调度行为。二是换 backend 做性能对比,把同一个 HuggingFace 模型导出成 ONNX,用 ONNX Runtime backend 再跑一遍,对比延迟和吞吐。三是加客户端压测,用locust或者自己写个多线程脚本,观察 Triton 的批处理调度在并发下的表现,日志里能看到实际的 batch 组成。

我个人在实际操作中的体会是,这套“不用 Docker 手动拼 Triton”的流程,最大的价值不是省了 Docker 那点事,而是逼着我把 Triton 的目录结构、backend 加载机制、模型仓库规范这些底层细节彻底搞明白了。以前用官方镜像一键启动,很多东西是黑盒;手动拼一遍之后,哪个文件放哪里、为什么这么放,全都清清楚楚。这种理解在排查线上问题时特别有用,因为你知道每个报错背后对应的是哪个环节。

最后再分享一个小技巧:如果你在 Colab 里反复调试 model.py,每次改完都要重启服务很麻烦,可以先用一个极简的 Python 脚本在本地把initialize和execute的核心逻辑单独跑通,确认 transformers 那部分没问题,再放进 Triton 里跑。这样能把“模型逻辑错误”和“Triton 集成错误”两类问题分开排查,效率高很多。

返回列表