Todos os produtos
Search
Central de documentação

Platform For AI:Train and deploy a PyTorch model

Última atualização: Jun 27, 2026

O PAI SDK for Python oferece APIs de alto nível para treinar e implantar modelos no Platform for AI (PAI). Este tutorial orienta você no treinamento de um modelo de classificação de imagens com PyTorch usando o conjunto de dados MNIST e na implantação como serviço de inferência online no Elastic Algorithm Service (EAS).

O fluxo de trabalho consiste em cinco etapas:

  1. Instale e configure o PAI SDK for Python

  2. Carregue os dados de treinamento no Object Storage Service (OSS)

  3. Escreva um script de treinamento adaptado para o PAI

  4. Envie um job de treinamento

  5. Implante um serviço de inferência no EAS

Um Jupyter Notebook para este tutorial está disponível para baixe: pytorch_mnist.ipynb

Pré-requisitos

Antes de começar, verifique se você tem:

Etapa 1: Instalar e configurar o PAI SDK for Python

Instale o SDK:

python -m pip install "alipai>=0.4.0"
Caso receba um erro ModuleNotFoundError , execute pip install --upgrade pip primeiro.

Configure o SDK com seu par de AccessKey, workspace e bucket do OSS:

python -m pai.toolkit.config

Para obter detalhes sobre a configuração, consulte Instale e configure o PAI SDK for Python.

Etapa 2: Carregar dados de treinamento no OSS

Este tutorial utiliza o conjunto de dados de dígitos manuscritos MNIST para treinar um modelo de classificação de imagens. Como os jobs de treinamento do PAI leem os dados de entrada diretamente do OSS, carregue o conjunto de dados antes de enviar o job.

Baixe o conjunto de dados MNIST

Execute o script shell a seguir para baixar o conjunto de dados para um diretório local data:

#!/bin/sh
set -e

url_prefix="https://ossci-datasets.s3.amazonaws.com/mnist/"
# Alternative mirror if the download is slow:
# url_prefix="http://yann.lecun.com/exdb/mnist/"

mkdir -p data/MNIST/raw/

wget -nv ${url_prefix}train-images-idx3-ubyte.gz -P data/MNIST/raw/
wget -nv ${url_prefix}train-labels-idx1-ubyte.gz -P data/MNIST/raw/
wget -nv ${url_prefix}t10k-images-idx3-ubyte.gz  -P data/MNIST/raw/
wget -nv ${url_prefix}t10k-labels-idx1-ubyte.gz  -P data/MNIST/raw/

Carregar no OSS

Use o ossutil ou o PAI SDK for Python:

  • Com o ossutil (consulte ossutil 1.0):

    ossutil cp -rf ./data oss://<YourOssBucket>/mnist/data/
  • Com o PAI SDK for Python:

    from pai.common.oss_utils import upload
    from pai.session import get_default_session
    
    sess = get_default_session()
    data_uri = upload("./data/", oss_path="mnist/data/", bucket=sess.oss_bucket)
    print(data_uri)
Se você fizer o upload com o ossutil , defina data_uri = "oss://<YourOssBucket>/mnist/data/" explicitamente ao enviar o job de treinamento na Etapa 4.

Etapa 3: Escrever um script de treinamento

O PAI monta os dados de entrada e captura a saída do modelo por meio de variáveis de ambiente. O script de treinamento deve ler essas variáveis em vez de usar caminhos fixos no código.

Variáveis de ambiente do PAI para jobs de treinamento

Variável

Descrição

PAI_INPUT_<CHANNEL_NAME>

Caminho de montagem para um canal de dados de entrada. O nome do canal é convertido para maiúsculas. Por exemplo, um canal chamado train_data corresponde a PAI_INPUT_TRAIN_DATA.

PAI_OUTPUT_MODEL

Caminho onde o PAI grava a saída do modelo. Padrão: /ml/output/model. Os arquivos gravados aqui são salvos automaticamente no seu bucket do OSS.

PAI_USER_ARGS

Hiperparâmetros formatados como string de argumentos CLI (por exemplo, --epochs 5 --batch-size 256). Usado no campo command do Estimator.

PAI_CONFIG_DIR

Diretório que contém o arquivo hyperparameters.json com os mesmos valores de hiperparâmetros.

Este tutorial parte do exemplo do MNIST no repositório do PyTorch e aplica duas modificações:

Modificação 1 — Ler dados de entrada da variável de ambiente

- dataset1 = datasets.MNIST("../data", train=True, download=True, transform=transform)
- dataset2 = datasets.MNIST("../data", train=False, transform=transform)

+ # Read the mounted data path from the environment variable.
+ data_path = os.environ.get("PAI_INPUT_TRAIN_DATA", "../data")
+ dataset1 = datasets.MNIST(data_path, train=True, download=True, transform=transform)
+ dataset2 = datasets.MNIST(data_path, train=False, transform=transform)

Modificação 2 — Salve o modelo no formato TorchScript no caminho de saída

O processador integrado do PyTorch exige modelos no formato TorchScript.

- if args.save_model:
-     torch.save(model.state_dict(), "mnist_cnn.pt")

+ # Save the model.
+ save_model(model)

+ def save_model(model):
+     """Convert the model to TorchScript and save it to the output path."""
+     output_model_path = os.environ.get("PAI_OUTPUT_MODEL")
+     os.makedirs(output_model_path, exist_ok=True)
+
+     m = torch.jit.script(model)
+     m.save(os.path.join(output_model_path, "mnist_cnn.pt"))

Script de treinamento completo

Salve o script a seguir em train_src/train.py:

# source: https://github.com/pytorch/examples/blob/main/mnist/main.py
from __future__ import print_function

import argparse
import os

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
from torchvision import datasets, transforms

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.dropout1 = nn.Dropout(0.25)
        self.dropout2 = nn.Dropout(0.5)
        self.fc1 = nn.Linear(9216, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)
        x = self.dropout1(x)
        x = torch.flatten(x, 1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output

def train(args, model, device, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % args.log_interval == 0:
            print(
                "Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}".format(
                    epoch,
                    batch_idx * len(data),
                    len(train_loader.dataset),
                    100.0 * batch_idx / len(train_loader),
                    loss.item(),
                )
            )
            if args.dry_run:
                break

def test(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += F.nll_loss(
                output, target, reduction="sum"
            ).item()  # sum up batch loss
            pred = output.argmax(
                dim=1, keepdim=True
            )  # get the index of the max log-probability
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)

    print(
        "\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n".format(
            test_loss,
            correct,
            len(test_loader.dataset),
            100.0 * correct / len(test_loader.dataset),
        )
    )

def main():
    # Training settings
    parser = argparse.ArgumentParser(description="PyTorch MNIST Example")
    parser.add_argument(
        "--batch-size",
        type=int,
        default=64,
        metavar="N",
        help="input batch size for training (default: 64)",
    )
    parser.add_argument(
        "--test-batch-size",
        type=int,
        default=1000,
        metavar="N",
        help="input batch size for testing (default: 1000)",
    )
    parser.add_argument(
        "--epochs",
        type=int,
        default=14,
        metavar="N",
        help="number of epochs to train (default: 14)",
    )
    parser.add_argument(
        "--lr",
        type=float,
        default=1.0,
        metavar="LR",
        help="learning rate (default: 1.0)",
    )
    parser.add_argument(
        "--gamma",
        type=float,
        default=0.7,
        metavar="M",
        help="Learning rate step gamma (default: 0.7)",
    )
    parser.add_argument(
        "--no-cuda", action="store_true", default=False, help="disables CUDA training"
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        default=False,
        help="quickly check a single pass",
    )
    parser.add_argument(
        "--seed", type=int, default=1, metavar="S", help="random seed (default: 1)"
    )
    parser.add_argument(
        "--log-interval",
        type=int,
        default=10,
        metavar="N",
        help="how many batches to wait before logging training status",
    )
    parser.add_argument(
        "--save-model",
        action="store_true",
        default=False,
        help="For Saving the current Model",
    )
    args = parser.parse_args()
    use_cuda = not args.no_cuda and torch.cuda.is_available()

    torch.manual_seed(args.seed)

    device = torch.device("cuda" if use_cuda else "cpu")

    train_kwargs = {"batch_size": args.batch_size}
    test_kwargs = {"batch_size": args.test_batch_size}
    if use_cuda:
        cuda_kwargs = {"num_workers": 1, "pin_memory": True, "shuffle": True}
        train_kwargs.update(cuda_kwargs)
        test_kwargs.update(cuda_kwargs)

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

    data_path = os.environ.get("PAI_INPUT_TRAIN_DATA", "../data")
    dataset1 = datasets.MNIST(data_path, train=True, download=True, transform=transform)
    dataset2 = datasets.MNIST(data_path, train=False, transform=transform)
    train_loader = torch.utils.data.DataLoader(dataset1, **train_kwargs)
    test_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)

    model = Net().to(device)
    optimizer = optim.Adadelta(model.parameters(), lr=args.lr)

    scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma)
    for epoch in range(1, args.epochs + 1):
        train(args, model, device, train_loader, optimizer, epoch)
        test(model, device, test_loader)
        scheduler.step()

    # Save the model.
    save_model(model)

def save_model(model):
    """Convert the model to TorchScript and save it to the specified path."""
    output_model_path = os.environ.get("PAI_OUTPUT_MODEL")
    os.makedirs(output_model_path, exist_ok=True)

    m = torch.jit.script(model)
    m.save(os.path.join(output_model_path, "mnist_cnn.pt"))

if __name__ == "__main__":
    main()

Estrutura de diretórios esperada para o código de treinamento:

|-- train_src/           # Training script directory (uploaded to OSS before the job starts)
    |-- requirements.txt # Optional: third-party dependencies
    '-- train.py         # Training script

Etapa 4: Envie um job de treinamento

O Estimator executa o script de treinamento local em uma instância gerenciada pelo PAI usando uma imagem de contêiner especificada.

Principais parâmetros do Estimator

Parâmetro

Descrição

source_dir

Diretório local do script a ser carregado. O PAI o monta em /ml/usercode (diretório de trabalho do command).

command

Comando de inicialização. A variável $PAI_USER_ARGS expande os hiperparâmetros como flags CLI. Neste exemplo: --epochs 5 --batch-size 256 --lr 0.5.

image_uri

Imagem do contêiner. Use retrieve() para obter uma imagem PyTorch gerenciada pelo PAI.

instance_type

Instância de computação. Para tipos suportados e preços, consulte Detalhes de preços do grupo de recursos público.

metric_definitions

Padrões Regex para extrair métricas de treinamento dos logs stdout/stderr. O PAI exibe as métricas analisadas na página de detalhes do job.

from pai.estimator import Estimator
from pai.image import retrieve

# Get a PAI-managed PyTorch 1.8 GPU image.
image_uri = retrieve(
    "PyTorch", framework_version="1.8PAI", accelerator_type="GPU"
).image_uri
print(image_uri)

est = Estimator(
    # Startup command; working directory is /ml/usercode.
    command="python train.py $PAI_USER_ARGS",
    # Local training script directory to upload.
    source_dir="./train_src/",
    image_uri=image_uri,
    # Instance type: 4 vCPU, 15 GB RAM, 1x NVIDIA T4.
    instance_type="ecs.gn6i-c4g1.xlarge",
    # Hyperparameters are passed to the script as CLI flags via $PAI_USER_ARGS.
    hyperparameters={
        "epochs": 5,
        "batch-size": 64 * 4,
        "lr": 0.5,
    },
    # Extract the loss metric from training logs.
    metric_definitions=[
        {
            "Name": "loss",
            "Regex": r".*loss=([-+]?[0-9]*.?[0-9]+(?:[eE][-+]?[0-9]+)?).*",
        },
    ],
    base_job_name="pytorch_mnist",
)

Envie o job e aguarde a conclusão:

# If you uploaded with ossutil, set data_uri explicitly:
# data_uri = "oss://<YourOssBucket>/mnist/data/"

est.fit(
    inputs={
        "train_data": data_uri,
    }
)

# OSS path of the trained model.
print("Training job output model path:")
print(est.model_data())

Após o envio, o SDK imprime um link para a página de detalhes do job e transmite os logs de treinamento até a conclusão. Para mais informações, consulte Envie um job de treinamento.

Etapa 5: Implantar um serviço de inferência

Quando o job de treinamento terminar, use est.model_data() para obter o caminho do OSS do modelo treinado e implante-o como serviço de inferência online no EAS.

A implantação de um serviço requer dois componentes:

  • Ativos do modelo: arquivos do modelo treinado armazenados no OSS.

  • Runtime: instruções para carregar o modelo e servir previsões (processador integrado ou imagem de contêiner personalizada).

Escolha o método de implantação conforme suas necessidades:

Método

Quando usar

Processador integrado

Formatos de modelo padrão (como TorchScript). Não requer código de serviço personalizado.

Imagem personalizada

Modelos com dependências de terceiros ou necessidade de lógica personalizada de pré-processamento e pós-processamento.

Implantar com um processador integrado

O PAI fornece um processador PyTorch integrado que carrega e serve modelos TorchScript. Use este método quando o modelo não exigir requisitos personalizados de serviço.

Implantar o serviço

from pai.model import Model, InferenceSpec
from pai.predictor import Predictor
from pai.common.utils import random_str

m = Model(
    model_data=est.model_data(),
    # Use the built-in PyTorch processor.
    inference_spec=InferenceSpec(processor="pytorch_cpu_1.10"),
)

p: Predictor = m.deploy(
    service_name="tutorial_pt_mnist_proc_{}".format(random_str(6)),
    instance_type="ecs.c6.xlarge",
)

print(p.service_name)
print(p.service_status)
A implantação geralmente leva vários minutos. O SDK bloqueia a execução até que o serviço esteja pronto.

O método Model.deploy retorna um objeto Predictor. Use Predictor.predict para enviar solicitações e obter previsões.

Execute inferência

import numpy as np

# Input shape: (batch_size, channels, height, width) as float32.
dummy_input = np.random.rand(2, 1, 28, 28).astype(np.float32)

res = p.predict(dummy_input)
print(res)
print(np.argmax(res, 1))

Exclua o serviço

p.delete_service()

Implantar com uma imagem personalizada

Adote esta abordagem quando o modelo exigir pré-processamento ou pós-processamento personalizado, ou tiver dependências não cobertas pelo processador integrado.

Etapa 1: Escrever o código do serviço de inferência

Crie o arquivo infer_src/run.py com uma aplicação Flask que carrega o modelo e lida com requisições HTTP:

import json
from flask import Flask, request
from PIL import Image
import os
import torch
import torchvision.transforms as transforms
import numpy as np
import io

app = Flask(__name__)
# The model is loaded from this path by default.
MODEL_PATH = "/eas/workspace/model/"

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = torch.jit.load(os.path.join(MODEL_PATH, "mnist_cnn.pt"), map_location=device).to(device)
transform = transforms.Compose(
    [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]
)

@app.route("/", methods=["POST"])
def predict():
    # Preprocess the image.
    im = Image.open(io.BytesIO(request.data))
    input_tensor = transform(im).to(device)
    input_tensor.unsqueeze_(0)
    # Run inference.
    output_tensor = model(input_tensor)
    pred_res = output_tensor.detach().cpu().numpy()[0]
    return json.dumps(pred_res.tolist())

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=int(os.environ.get("LISTENING_PORT", 8000)))

Estrutura de diretórios esperada:

|-- infer_src/           # Inference service code directory (uploaded to OSS before deployment)
    |-- requirements.txt # Optional: third-party dependencies
    '-- run.py           # Inference server script

Etapa 2: Construir o InferenceSpec

A função container_serving_spec agrupa o código local com uma imagem PyTorch gerenciada pelo PAI. O diretório source_dir é carregado no OSS e montado em /ml/usercode dentro do contêiner.

from pai.model import InferenceSpec, container_serving_spec
from pai.image import retrieve, ImageScope

torch_image_uri = retrieve(
    "PyTorch", framework_version="latest", image_scope=ImageScope.INFERENCE
).image_uri

inf_spec = container_serving_spec(
    command="python run.py",
    source_dir="./infer_src/",
    image_uri=torch_image_uri,
    requirements=["flask==2.0.0", "Werkzeug==2.2.2", "pillow", "torchvision"],
)
print(inf_spec.to_dict())

Etapa 3: Implantar o serviço

from pai.model import Model
from pai.common.utils import random_str

m = Model(
    model_data=est.model_data(),
    inference_spec=inf_spec,
)

predictor = m.deploy(
    service_name="torch_mnist_script_container_{}".format(random_str(6)),
    instance_type="ecs.c6.xlarge",
)
A implantação geralmente leva vários minutos. O SDK bloqueia a execução até que o serviço esteja pronto.

Etapa 4: Execute inferência

Prepare uma imagem de teste do MNIST:

import base64
from PIL import Image
import io

# raw_data is an MNIST image of the digit 9.
raw_data = base64.b64decode(b"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAAcABwBAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/APn+rVhpmoarP5GnWNzeTYz5dvE0jfkoJovNMv8ATmK3tjc2zByhE8TIQw6jkdR6VVq9oumPrWuWGlxyLG95cRwK7dFLMFyfzr3aXwp4ltAfB3gWwudI01JNuoa7eZhku5AMHafvFOw2Dn6ZJ4z4yeLk1HUbXwrZSSy2Oh5heeaQu88wG1mLHk4wR9c+1eXUqsVYMpIIOQR2r1D4QazqOs/FnSG1fVLi9ZI5vL+2TNKc+U2ApYnB7/hXml5LLNfXEsxLSvIzOSMEsTk1DRVnT7+60vULe/spmhureQSRSL1Vh0NWNd1mXX9ZuNUuLe2gmuCGkS2QohbABbBJwTjJ9yelZ1f/2Q==")

im = Image.open(io.BytesIO(raw_data))

Envie a imagem para o serviço de inferência. O serviço aceita bytes brutos da imagem no corpo da requisição HTTP:

from pai.predictor import RawResponse
import numpy as np

resp: RawResponse = predictor.raw_predict(data=raw_data)
print(resp.json())
print(np.argmax(resp.json()))

Etapa 5: Exclua o serviço

predictor.delete_service()

Próximos passos