TorchServe 推理服务

把 PyTorch 模型(.pt / .mar)部署成在线推理服务,需要了解打包、配置、启动命令与调用接口时读这篇

平台使用 / 服务化与推理
TorchServetorch-servertorchservePyTorchpt模型martorch-model-archiverhandlerconfig.properties推理服务inferencemodel serving模型部署

TorchServe 推理服务

本文介绍如何把 PyTorch 模型通过 TorchServe 部署为在线推理服务。在推理服务列表中新建服务时,service_type 选择 torch-server,平台会自动完成模型打包、配置文件生成和启动命令编排。下文标注"(系统完成)"的步骤通常由平台自动处理,用户只需准备好模型文件。

事实核对依据:服务类型、镜像、配置、端口、命令等来自 myapp/views/view_inferenceserving.pymyapp/config.pyINFERENCE_IMAGES 段)。

镜像

平台内置可选的 TorchServe 镜像(myapp/config.py:1327 torch-server):

ccr.ccs.tencentyun.com/cube-studio/torchserve:0.9.0-gpu
ccr.ccs.tencentyun.com/cube-studio/torchserve:0.9.0-cpu
ccr.ccs.tencentyun.com/cube-studio/torchserve:0.8.2-gpu
ccr.ccs.tencentyun.com/cube-studio/torchserve:0.8.2-cpu
ccr.ccs.tencentyun.com/cube-studio/torchserve:0.7.1-gpu
ccr.ccs.tencentyun.com/cube-studio/torchserve:0.7.1-cpu

CPU 镜像用于无 GPU 场景,GPU 镜像需配合资源里的 resource_gpu。各版本对 Torch / CUDA 的兼容关系以 TorchServe 官方发布说明为准:https://github.com/pytorch/serve/releases

模型导出

一定要导出完整的模型,包括模型结构和模型参数。 只保存 state_dict(权重)而不保存结构,TorchServe 无法直接加载。

# 仅保存/加载权重(不推荐单独用于部署)
# torch.save(model.state_dict(), PATH)
# model.load_state_dict(torch.load(PATH))

# 保存完整模型
torch.save(model, PATH)
model = torch.load(PATH)

model.eval()                                   # 转为推理模式
example_input = torch.rand(1, 3, 224, 224)
traced_model = torch.jit.trace(model, example_input)   # TorchScript 序列化优化
traced_model.save('./model.pt')                # 保存完整模型

pt 模型打包(系统完成)

平台在部署时会自动调用 torch-model-archiver.pt 等模型文件打包成 .marview_inferenceserving.py:695):

pip3 install torch-model-archiver

torch-model-archiver \
  --model-name   $model_name \
  --version      $model_version \
  --handler      $handler \
  --serialized-file $model_path \
  --export-path  /models -f

其中 --handler 在平台中取自服务的 transformer 字段,未填写时回退到 model_typeview_inferenceserving.py:695item.transformer or item.model_type)。TorchServe 内置 handler 包括 image_classifierimage_segmenterobject_detectortext_classifier 等;其他类型见 https://pytorch.org/serve/default_handlers.html 。自定义 handler 见文末「自定义 handler」。

如果模型文件本身已经是 .mar,平台不再重复打包,直接拷贝到 /models/ 后启动。在「模型地址」(model_path)中填写的规则:torch-model-archiver 编译后的 .mar 文件,或 TorchScript 保存的模型文件(view_inferenceserving.py:228)。

配置文件 config.properties(系统完成)

平台自动生成 config.propertiesview_inferenceserving.py:543 torch_config()),内容如下:

inference_address=http://0.0.0.0:8080
management_address=http://0.0.0.0:8081
metrics_address=http://0.0.0.0:8082
cors_allowed_origin=*
cors_allowed_methods=GET, POST, PUT, OPTIONS
cors_allowed_headers=X-Custom-Header
number_of_netty_threads=32
enable_metrics_api=true
job_queue_size=1000
enable_envvars_config=true
async_logging=true
default_response_timeout=120
max_request_size=6553500

说明:

  • inference_address / management_address / metrics_address 分别对应推理、管理、监控三个端口(8080 / 8081 / 8082)。
  • 这些属性也可通过环境变量 TS_<PROPERTY_NAME> 注入(view_inferenceserving.py:542 注释)。
  • 与早期版本相比,当前配置启用了 enable_envvars_config=trueasync_logging=true,并新增 default_response_timeout=120max_request_size=6553500

注意:早期文档中的 log4j.properties 日志配置文件与启动命令里的 ---log-config 参数,在当前平台自动生成的配置与启动命令中已不存在(view_inferenceserving.py:704 的启动命令只挂载 --ts-config=/config/config.properties,未含 --log-config)。如需自定义日志,请参考 TorchServe 原生 logging 配置自行加入。

部署服务(系统完成)

平台自动编排的启动命令大致为(view_inferenceserving.py:704):

# 工作目录:/models(系统自动设置)
cp /config/* /models/ \
  && <torch-model-archiver 打包,或 cp 已有 .mar> \
  && torchserve --start --model-store /models \
       --models $model_name=$model_name.mar \
       --foreground \
       --ts-config=/config/config.properties

无需手动填写启动命令;如需自定义,可在服务的「启动命令」字段覆盖默认值。

API 访问

端口(view_inferenceserving.py:106-127):

  • 推理:8080
  • 管理:8081(暴露端口为 8080,8081
  • 监控:8082(指标 8082:/metrics
  • 健康检查:8080:/ping

推理接口(8080 端口):

POST /v1/models/{model_name}:predict      推荐接口,兼容 KServe(kfserving)
POST /predictions/{model_name}            TorchServe 原生接口
POST /predictions/{model_name}/{version}

管理接口(8081):GET /models(平台的访问域名 host 默认指向 :8081/modelsview_inferenceserving.py:69)。 监控接口(8082):/metrics

平台在服务列表的「推理测试」会打开 Notebook 示例 pipeline/example/inference/torch-client.ipynbview_inferenceserving.py:2202)。

image_classifier 调用示例:

# pytorch
SERVER_URL = 'http://<推理服务域名>/predictions/resnet50'
IMAGE_PATH = 'smallcat.jpg'
files = {'data': open(IMAGE_PATH, 'rb')}
response = requests.post(SERVER_URL, files=files)
print(response.json())
print(response.status_code)
response.raise_for_status()

# curl http://<推理服务域名>/predictions/resnet50 -T smallcat.jpg
# curl http://<推理服务域名>/predictions/resnet50 -F "data=@smallcat.jpg"

其他类型接口参考 https://pytorch.org/serve/default_handlers.html

自定义 handler

示例 MNISTDigitClassifier:根据用户输入的图片地址先下载图片再推理,并回传请求中的 image_id

生成 handler.py

from torchvision import transforms
from ts.torch_handler.image_classifier import ImageClassifier
import base64, time, json
import torch
from PIL import Image
import io
import requests
import pysnooper


class MNISTDigitClassifier(ImageClassifier):

    image_processing = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
    ])

    # @pysnooper.snoop()
    def handle(self, data, context):
        # 输入输出为 list
        start_time = time.time()

        self.context = context
        metrics = self.context.metrics
        data_preprocess = self.preprocess(data)

        if not self._is_explain():
            output = self.inference(data_preprocess)
            output = self.postprocess(output)
        else:
            output = self.explain_handle(data_preprocess, data)

        stop_time = time.time()
        metrics.add_time('HandlerTime', round(
            (stop_time - start_time) * 1000, 2), None, 'ms')
        back = []
        for index in range(len(output)):
            back.append({
                "predict": output[index],
                "image_id": data[index]['body']['image_id']
            })
        return back

    # @pysnooper.snoop()
    def preprocess(self, data):
        des_images = []
        for row in data:
            image_url = row.get("body")['image_url']
            image = requests.get(image_url).content
            if isinstance(image, (bytearray, bytes)):
                image = Image.open(io.BytesIO(image))
                image = self.image_processing(image)
            else:
                # 如果传入的是 list 形式的图片数据
                image = torch.FloatTensor(image)

            des_images.append(image)

        return torch.stack(des_images).to(self.device)

    # @pysnooper.snoop()
    def postprocess(self, data):
        return data.argmax(1).tolist()

handler.py 一起打入 .mar(系统可自动完成),再把生成的 .mar 文件路径填入界面:

torch-model-archiver --model-name xx --version 1.0 --handler handler.py --serialized-file xx.pt --export-path ./ -f

client.py 调用:

data = {
    "image_url": "http://xx.xx.xx/xx",
    "image_id": "11111"
}
url = 'http://127.0.0.1:8080/v1/models/mnist:predict'
res = requests.post(url, json=data)
print(res.status_code)
result = res.content.decode()
print(result)
最后更新 2026-06-30完整文档以官方仓库为准:GitHub Wiki