在把PyTorch模型交付到线上推理服务时,TorchServe提供了一套完整的模型服务器方案。它最核心的两个动作是模型归档与请求处理解耦:前者解决环境一致性与分发问题,后者让推理逻辑可以灵活适配业务。理解这两块,才能把实验模型平稳推到生产。

模型归档:从权重到可部署的MAR包
TorchServe并不直接加载pth或pt文件,而是要求先把模型及相关资源打成Model Archive(MAR)文件。这个包本质上是一个zip,里面包含模型权重、manifest、可选的handler脚本以及requirements.txt。使用torch-model-archiver命令可以生成,例如指定--model-name、--handler和--export-path。归档之后,再用torchserve --start加载该mar即可对外提供REST或gRPC接口。
为什么要归档而不是裸跑权重?因为在多团队、多节点场景下,模型依赖的torch版本、自定义层、词表文件经常不一致。MAR把这些都固化下来,服务启动时无需再拉取外部资源。下面是一段典型的归档命令,其中--extra-files可以把词典或配置一并打进去:
torch-model-archiver --model-name sentiment_bert --version 1.0 --model-file model.py --serialized-file bert_weights.pth --handler handler.py --extra-files vocab.txt,config.json --requirements-file requirements.txt --export-path model_store --force
归档时容易忽略的是--handler的指向。如果不写,TorchServe会用内置的image_classifier等默认处理器,这往往不符合文本或多模态模型的需要。建议始终自带handler.py,哪怕只是简单继承。另外,requirements.txt里的包会在服务容器里安装,若包含编译型依赖,最好提前构建基础镜像,否则首次启动会很慢甚至失败。
自定义Handler的结构与生命周期
Handler是TorchServe处理单次请求的程序入口,它是一个Python类,通常继承torchserve.handler.BaseHandler。核心方法包括initialize、preprocess、inference和postprocess,也可以直接重写handle把四步合并。服务起来时,每个worker会调用一次initialize加载模型,之后请求只走预处理和推理,避免重复开销。
在initialize中,我们通过self.manifest拿到mar内的文件列表,用get_model_path定位权重,再用torch加载。预处理负责把HTTP传来的json或二进制转成tensor,比如把文本分词、padding。推理就是调model.forward,后处理则把输出logits转成业务可读的标签与概率。以下示例展示一个最小可用的自定义Handler骨架:
from ts.torch_handler.base_handler import BaseHandler
import torch
class MyHandler(BaseHandler):
def initialize(self, context):
self.model = torch.jit.load(self.manifest['model']['serializedFile'])
self.model.eval()
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.model.to(self.device)
def preprocess(self, data):
# data是请求列表,每个元素含body
texts = [item['body']['text'] for item in data]
# 简化:实际应调用tokenizer
return torch.zeros((len(texts), 10), device=self.device)
def inference(self, inputs):
with torch.no_grad():
return self.model(inputs)
def postprocess(self, outputs):
return outputs.cpu().tolist()
如果业务逻辑复杂,比如要鉴权、打日志或合并多模型结果,直接重写handle方法更直观。但要注意线程安全:TorchServe默认每个worker单线程处理队列,不要在handler里维护跨请求的可变全局状态。此外,异常要显式抛出,否则客户端只会收到空响应,排错困难。
生产部署中的参数调优与避坑
归档和Handler写完后,真正上线还要调TorchServe的启动参数。最常用的有--models指定mar、--workers设置每个模型的推理进程数,以及batch_size和max_batch_delay来开启批处理。批处理能显著提升GPU利用率,但延迟敏感场景要把delay调低,比如5到10毫秒,防止排队过久。
另一个坑是自定义Handler里的第三方库。MAR虽然带了requirements,但TorchServe启动期安装可能没网络或超时。更稳的做法是把handler依赖预装进基础镜像,MAR只放轻量脚本。同时,模型输入形状要在预处理阶段严格校验,因为线上请求常有脏数据,直接喂给tensor会造成服务崩溃而非返回错误码。下面是一段带校验的预处理片段:
def preprocess(self, data):
cleaned = []
for item in data:
body = item.get('body', {})
text = body.get('text')
if not isinstance(text, str) or len(text) == 0:
raise ValueError('invalid text field')
cleaned.append(text)
# 假设tokenizer已加载
return self.tokenizer(cleaned, return_tensors='pt', padding=True)
最后,模型更新时不要直接覆盖mar,而用版本号区分并调用管理API热加载。TorchServe的register接口允许新版本逐步接流,老版本保留以便回滚。配合自定义Handler中打印的request_id,可以做到按请求追踪,对生产排障非常关键。把这些细节理顺,TorchServe就能稳定承载日常推理流量。
TorchServemodel_archivingcustom_handler修改时间:2026-08-17 03:58:26