507 lines
19 KiB
Plaintext
507 lines
19 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"# Using PyTorch Lightning with Tune\n",
|
|
"\n",
|
|
"<a id=\"try-anyscale-quickstart-tune-pytorch-lightning\" href=\"https://console.anyscale.com/register/ha?render_flow=ray&utm_source=ray_docs&utm_medium=docs&utm_campaign=tune-pytorch-lightning\">\n",
|
|
" <img src=\"../../_static/img/run-on-anyscale.svg\" alt=\"try-anyscale-quickstart\">\n",
|
|
"</a>\n",
|
|
"<br></br>\n",
|
|
"\n",
|
|
"(tune-pytorch-lightning-ref)=\n",
|
|
"\n",
|
|
"PyTorch Lightning is a framework which brings structure into training PyTorch models. It aims to avoid boilerplate code, so you don't have to write the same training loops all over again when building a new model.\n",
|
|
"\n",
|
|
"```{image} /images/pytorch_lightning_full.png\n",
|
|
":align: center\n",
|
|
"```\n",
|
|
"\n",
|
|
"The main abstraction of PyTorch Lightning is the `LightningModule` class, which should be extended by your application. There is [a great post on how to transfer your models from vanilla PyTorch to Lightning](https://towardsdatascience.com/from-pytorch-to-pytorch-lightning-a-gentle-introduction-b371b7caaf09).\n",
|
|
"\n",
|
|
"The class structure of PyTorch Lightning makes it very easy to define and tune model parameters. This tutorial will show you how to use Tune with PyTorch Lightning. Notably, the `LightningModule` does not have to be altered at all for this - so you can use it plug and play for your existing models, assuming their parameters are configurable!\n",
|
|
"\n",
|
|
":::{note}\n",
|
|
"To run this example, you will need to install the following:\n",
|
|
"\n",
|
|
"```bash\n",
|
|
"$ pip install -q \"ray[tune]\" torch torchvision lightning\n",
|
|
"```\n",
|
|
":::\n",
|
|
"\n",
|
|
"```{contents}\n",
|
|
":backlinks: none\n",
|
|
":local: true\n",
|
|
"```\n",
|
|
"\n",
|
|
"## PyTorch Lightning classifier for MNIST\n",
|
|
"\n",
|
|
"Let's first start with the basic PyTorch Lightning implementation of an MNIST classifier. This classifier does not include any tuning code at this point.\n",
|
|
"\n",
|
|
"First, we run some imports:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/home/ray/anaconda3/lib/python3.11/site-packages/lightning_utilities/core/imports.py:14: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.\n",
|
|
" import pkg_resources\n",
|
|
"/home/ray/anaconda3/lib/python3.11/site-packages/transformers/utils/generic.py:441: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
|
" _torch_pytree._register_pytree_node(\n",
|
|
"/home/ray/anaconda3/lib/python3.11/site-packages/transformers/utils/generic.py:309: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
|
" _torch_pytree._register_pytree_node(\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import os\n",
|
|
"import torch\n",
|
|
"import tempfile\n",
|
|
"\n",
|
|
"try:\n",
|
|
" import lightning.pytorch as pl\n",
|
|
"except ModuleNotFoundError:\n",
|
|
" import pytorch_lightning as pl\n",
|
|
"\n",
|
|
"import torch.nn.functional as F\n",
|
|
"from filelock import FileLock\n",
|
|
"from torchmetrics import Accuracy\n",
|
|
"from torch.utils.data import DataLoader, random_split\n",
|
|
"from torchvision.datasets import MNIST\n",
|
|
"from torchvision import transforms"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {
|
|
"tags": [
|
|
"remove-cell"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# If you want to run full test, please set SMOKE_TEST to False\n",
|
|
"SMOKE_TEST = True"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Our example builds on the MNIST example from the [blog post](https://towardsdatascience.com/from-pytorch-to-pytorch-lightning-a-gentle-introduction-b371b7caaf09) we mentioned before. We adapted the original model and dataset definitions into `MNISTClassifier` and `MNISTDataModule`. "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class MNISTClassifier(pl.LightningModule):\n",
|
|
" def __init__(self, config):\n",
|
|
" super(MNISTClassifier, self).__init__()\n",
|
|
" self.accuracy = Accuracy(task=\"multiclass\", num_classes=10, top_k=1)\n",
|
|
" self.layer_1_size = config[\"layer_1_size\"]\n",
|
|
" self.layer_2_size = config[\"layer_2_size\"]\n",
|
|
" self.lr = config[\"lr\"]\n",
|
|
"\n",
|
|
" # mnist images are (1, 28, 28) (channels, width, height)\n",
|
|
" self.layer_1 = torch.nn.Linear(28 * 28, self.layer_1_size)\n",
|
|
" self.layer_2 = torch.nn.Linear(self.layer_1_size, self.layer_2_size)\n",
|
|
" self.layer_3 = torch.nn.Linear(self.layer_2_size, 10)\n",
|
|
" self.eval_loss = []\n",
|
|
" self.eval_accuracy = []\n",
|
|
"\n",
|
|
" def cross_entropy_loss(self, logits, labels):\n",
|
|
" return F.nll_loss(logits, labels)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" batch_size, channels, width, height = x.size()\n",
|
|
" x = x.view(batch_size, -1)\n",
|
|
"\n",
|
|
" x = self.layer_1(x)\n",
|
|
" x = torch.relu(x)\n",
|
|
"\n",
|
|
" x = self.layer_2(x)\n",
|
|
" x = torch.relu(x)\n",
|
|
"\n",
|
|
" x = self.layer_3(x)\n",
|
|
" x = torch.log_softmax(x, dim=1)\n",
|
|
"\n",
|
|
" return x\n",
|
|
"\n",
|
|
" def training_step(self, train_batch, batch_idx):\n",
|
|
" x, y = train_batch\n",
|
|
" logits = self.forward(x)\n",
|
|
" loss = self.cross_entropy_loss(logits, y)\n",
|
|
" accuracy = self.accuracy(logits, y)\n",
|
|
"\n",
|
|
" self.log(\"ptl/train_loss\", loss)\n",
|
|
" self.log(\"ptl/train_accuracy\", accuracy)\n",
|
|
" return loss\n",
|
|
"\n",
|
|
" def validation_step(self, val_batch, batch_idx):\n",
|
|
" x, y = val_batch\n",
|
|
" logits = self.forward(x)\n",
|
|
" loss = self.cross_entropy_loss(logits, y)\n",
|
|
" accuracy = self.accuracy(logits, y)\n",
|
|
" self.eval_loss.append(loss)\n",
|
|
" self.eval_accuracy.append(accuracy)\n",
|
|
" return {\"val_loss\": loss, \"val_accuracy\": accuracy}\n",
|
|
"\n",
|
|
" def on_validation_epoch_end(self):\n",
|
|
" avg_loss = torch.stack(self.eval_loss).mean()\n",
|
|
" avg_acc = torch.stack(self.eval_accuracy).mean()\n",
|
|
" self.log(\"ptl/val_loss\", avg_loss, sync_dist=True)\n",
|
|
" self.log(\"ptl/val_accuracy\", avg_acc, sync_dist=True)\n",
|
|
" self.eval_loss.clear()\n",
|
|
" self.eval_accuracy.clear()\n",
|
|
"\n",
|
|
" def configure_optimizers(self):\n",
|
|
" optimizer = torch.optim.Adam(self.parameters(), lr=self.lr)\n",
|
|
" return optimizer\n",
|
|
"\n",
|
|
"\n",
|
|
"class MNISTDataModule(pl.LightningDataModule):\n",
|
|
" def __init__(self, batch_size=128):\n",
|
|
" super().__init__()\n",
|
|
" self.data_dir = tempfile.mkdtemp()\n",
|
|
" self.batch_size = batch_size\n",
|
|
" self.transform = transforms.Compose(\n",
|
|
" [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]\n",
|
|
" )\n",
|
|
"\n",
|
|
" def setup(self, stage=None):\n",
|
|
" with FileLock(f\"{self.data_dir}.lock\"):\n",
|
|
" mnist = MNIST(\n",
|
|
" self.data_dir, train=True, download=True, transform=self.transform\n",
|
|
" )\n",
|
|
" self.mnist_train, self.mnist_val = random_split(mnist, [55000, 5000])\n",
|
|
"\n",
|
|
" self.mnist_test = MNIST(\n",
|
|
" self.data_dir, train=False, download=True, transform=self.transform\n",
|
|
" )\n",
|
|
"\n",
|
|
" def train_dataloader(self):\n",
|
|
" return DataLoader(self.mnist_train, batch_size=self.batch_size, num_workers=4)\n",
|
|
"\n",
|
|
" def val_dataloader(self):\n",
|
|
" return DataLoader(self.mnist_val, batch_size=self.batch_size, num_workers=4)\n",
|
|
"\n",
|
|
" def test_dataloader(self):\n",
|
|
" return DataLoader(self.mnist_test, batch_size=self.batch_size, num_workers=4)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"default_config = {\n",
|
|
" \"layer_1_size\": 128,\n",
|
|
" \"layer_2_size\": 256,\n",
|
|
" \"lr\": 1e-3,\n",
|
|
"}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Define a training function that creates model, `DataModule`, and the PyTorch Lightning `Trainer`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from ray.tune.integration.pytorch_lightning import TuneReportCheckpointCallback\n",
|
|
"\n",
|
|
"def train_func(config):\n",
|
|
" dm = MNISTDataModule(batch_size=config[\"batch_size\"])\n",
|
|
" model = MNISTClassifier(config)\n",
|
|
"\n",
|
|
" trainer = pl.Trainer(\n",
|
|
" devices=\"auto\",\n",
|
|
" accelerator=\"auto\",\n",
|
|
" callbacks=[TuneReportCheckpointCallback()],\n",
|
|
" enable_progress_bar=False,\n",
|
|
" )\n",
|
|
" trainer.fit(model, datamodule=dm)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Tuning the model parameters\n",
|
|
"\n",
|
|
"The parameters above should give you a good accuracy of over 90% already. However, we might improve on this simply by changing some of the hyperparameters. For instance, maybe we get an even higher accuracy if we used a smaller learning rate and larger middle layer size.\n",
|
|
"\n",
|
|
"Instead of manually loop through all the parameter combinitions, let's use Tune to systematically try out parameter combinations and find the best performing set.\n",
|
|
"\n",
|
|
"First, we need some additional imports:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from ray import tune\n",
|
|
"from ray.tune.schedulers import ASHAScheduler"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Configuring the search space\n",
|
|
"\n",
|
|
"Now we configure the parameter search space. We would like to choose between different layer dimensions, learning rate, and batch sizes. The learning rate should be sampled uniformly between `0.0001` and `0.1`. The `tune.loguniform()` function is syntactic sugar to make sampling between these different orders of magnitude easier, specifically we are able to also sample small values. Similarly for `tune.choice()`, which samples from all the provided options."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"search_space = {\n",
|
|
" \"layer_1_size\": tune.choice([32, 64, 128]),\n",
|
|
" \"layer_2_size\": tune.choice([64, 128, 256]),\n",
|
|
" \"lr\": tune.loguniform(1e-4, 1e-1),\n",
|
|
" \"batch_size\": tune.choice([32, 64]),\n",
|
|
"}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Selecting a scheduler\n",
|
|
"\n",
|
|
"In this example, we use an [Asynchronous Hyperband](https://blog.ml.cmu.edu/2018/12/12/massively-parallel-hyperparameter-optimization/)\n",
|
|
"scheduler. This scheduler decides at each iteration which trials are likely to perform\n",
|
|
"badly, and stops these trials. This way we don't waste any resources on bad hyperparameter\n",
|
|
"configurations."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# The maximum training epochs\n",
|
|
"num_epochs = 5\n",
|
|
"\n",
|
|
"# Number of samples from parameter space\n",
|
|
"num_samples = 10"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"If you have more resources available, you can modify the above parameters accordingly. e.g. more epochs, more parameter samples."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {
|
|
"tags": [
|
|
"remove-cell"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"if SMOKE_TEST:\n",
|
|
" num_epochs = 1\n",
|
|
" num_samples = 3"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"scheduler = ASHAScheduler(max_t=num_epochs, grace_period=1, reduction_factor=2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Training with GPUs\n",
|
|
"\n",
|
|
"We can specify the number of resources, including GPUs, that Tune should request for each trial."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"train_fn_with_resources = tune.with_resources(train_func, resources={\"CPU\": 1, \"GPU\": 1})"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {
|
|
"tags": [
|
|
"remove-cell"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"if SMOKE_TEST:\n",
|
|
" train_fn_with_resources = tune.with_resources(train_func, resources={\"CPU\": 1})\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Putting it together\n",
|
|
"\n",
|
|
"Lastly, we need to create a `Tuner()` object and start Ray Tune with `tuner.fit()`. The full code looks like this:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"tags": [
|
|
"hide-output"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def tune_mnist_asha(num_samples=10):\n",
|
|
" scheduler = ASHAScheduler(max_t=num_epochs, grace_period=1, reduction_factor=2)\n",
|
|
"\n",
|
|
" tuner = tune.Tuner(\n",
|
|
" train_fn_with_resources,\n",
|
|
" param_space=search_space,\n",
|
|
" tune_config=tune.TuneConfig(\n",
|
|
" metric=\"ptl/val_accuracy\",\n",
|
|
" mode=\"max\",\n",
|
|
" num_samples=num_samples,\n",
|
|
" scheduler=scheduler,\n",
|
|
" ),\n",
|
|
" run_config=tune.RunConfig(\n",
|
|
" checkpoint_config=tune.CheckpointConfig(\n",
|
|
" num_to_keep=2,\n",
|
|
" checkpoint_score_attribute=\"ptl/val_accuracy\",\n",
|
|
" checkpoint_score_order=\"max\",\n",
|
|
" ),\n",
|
|
" ),\n",
|
|
" )\n",
|
|
" return tuner.fit()\n",
|
|
"\n",
|
|
"\n",
|
|
"results = tune_mnist_asha(num_samples=num_samples)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"Result(\n",
|
|
" metrics={'ptl/train_loss': 0.001267582061700523, 'ptl/train_accuracy': 1.0, 'ptl/val_loss': 0.1036270260810852, 'ptl/val_accuracy': 0.9721123576164246},\n",
|
|
" path='/home/ray/ray_results/train_func_2025-09-23_13-37-55/train_func_2f534_00006_6_batch_size=64,layer_1_size=64,layer_2_size=64,lr=0.0020_2025-09-23_13-37-55',\n",
|
|
" filesystem='local',\n",
|
|
" checkpoint=Checkpoint(filesystem=local, path=/home/ray/ray_results/train_func_2025-09-23_13-37-55/train_func_2f534_00006_6_batch_size=64,layer_1_size=64,layer_2_size=64,lr=0.0020_2025-09-23_13-37-55/checkpoint_000004)\n",
|
|
")"
|
|
]
|
|
},
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"results.get_best_result(metric=\"ptl/val_accuracy\", mode=\"max\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"In the example above, Tune runs 10 trials with different hyperparameter configurations.\n",
|
|
"\n",
|
|
"As you can see in the `training_iteration` column, trials with a high loss (and low accuracy) have been terminated early. The best performing trial used\n",
|
|
"`batch_size=64`, `layer_1_size=128`, `layer_2_size=256`, and `lr=0.0037`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## More PyTorch Lightning Examples\n",
|
|
"\n",
|
|
"- For running distributed PyTorch Lightning training with Ray Train, see the {ref}`quickstart <train-pytorch-lightning>`.\n",
|
|
"- {doc}`[Basic] Train a PyTorch Lightning Image Classifier with Ray Train <../../train/examples/lightning/lightning_mnist_example>`.\n",
|
|
"- {doc}`[Intermediate] Fine-tune a BERT Text Classifier with PyTorch Lightning and Ray Train <../../train/examples/lightning/lightning_cola_advanced>`\n",
|
|
"- {doc}`[Advanced] Fine-tune dolly-v2-7b with PyTorch Lightning and FSDP <../../train/examples/lightning/dolly_lightning_fsdp_finetuning>`\n",
|
|
"- {doc}`/tune/examples/includes/mlflow_ptl_example`: Example for using [MLflow](https://github.com/mlflow/mlflow/)\n",
|
|
" and [Pytorch Lightning](https://github.com/PyTorchLightning/pytorch-lightning) with Ray Tune.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.11.11"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|