Skip to content

Commit cd53b5f

Browse files
committed
reorg with new pytorch pages
1 parent 8b4958a commit cd53b5f

9 files changed

Lines changed: 193 additions & 3 deletions

File tree

docs/genai/01_getting_started/01_intro.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ If you're looking to harness Generative AI for administrative or classroom use,
66

77
Welcome to Pythia, the generative AI platform for research workflows. As part of the Pythia platform, the following capabilities are offered:
88
- [Access to externally hosted LLMs](../01_getting_started/02_llm_gateway.mdx)
9-
- [HPC resources for fine tuning LLMs](../../hpc/08_ml_ai_hpc/05_llm_fine_tuning.md)
9+
- [HPC resources for fine tuning LLMs](../../hpc/08_ml_ai_hpc/08_llm_fine_tuning.md)
1010

1111
:::tip[Personal use]
1212
If you want to access NYU provided LLMs for personal use, proceed to https://gemini.google.com/app with your NYU credentials.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Fine tuning
22

3-
Please look into harnessing RAG before attempting to fine-tune a model. For open-weight models, you can use the HPC cluster to perform LoRA fine-tuning as [described here](../../hpc/08_ml_ai_hpc/05_llm_fine_tuning.md).
3+
Please look into harnessing RAG before attempting to fine-tune a model. For open-weight models, you can use the HPC cluster to perform LoRA fine-tuning as [described here](../../hpc/08_ml_ai_hpc/08_llm_fine_tuning.md).
Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,191 @@
1-
# Machine Learning on HPC
1+
# Single-GPU Training with PyTorch
2+
3+
It is important to optimize your script for the single-GPU case before moving to multi-GPU training. This is because as you request more resources, your queue time increases. We also want to avoid wasting resources by running code that is not optimized.
4+
5+
Here we train a CNN on the MNIST dataset using a single GPU as an example. We profile the code and make performance improvements.
6+
7+
This tutorial uses PyTorch but the steps are similar for TensorFlow. See our [TensorFlow](./06_tensorflow.md) page for details.
8+
9+
## Step 1: Activate the Environment
10+
11+
For simplicity we will use a pre-installed Conda environmnet. Run these commands to activate the environment:
12+
13+
```bash
14+
$ ssh <YourNetID>@adroit.princeton.edu
15+
$ module load anaconda3/2023.9
16+
$ conda activate /home/jdh4/.conda/envs/torch-env
17+
```
18+
19+
Watch a [video](https://www.youtube.com/watch?v=wqTgM-Wq4YY&t=296s) that covers everything on this page for single-GPU training with [profiling Python](https://researchcomputing.princeton.edu/python-profiling) using `line_profiler`.
20+
21+
## Step 2: Run and Profile the Script
22+
23+
```
24+
fix link
25+
First, inspect the script ([see script](mnist_classify.py)) by running these commands:
26+
```
27+
28+
```bash
29+
(torch-env) $ cd multi_gpu_training/01_single_gpu
30+
(torch-env) $ cat mnist_classify.py
31+
```
32+
33+
We will profile the `train` function using `line_profiler` (see line 39) by adding the following decorator:
34+
35+
```python
36+
@profile
37+
def train(args, model, device, train_loader, optimizer, epoch):
38+
```
39+
40+
Next, download the data while on the login node since the compute nodes do not have internet access:
41+
42+
```
43+
(torch-env) $ python download_mnist.py
44+
```
45+
46+
Below is the Slurm script:
47+
48+
```bash
49+
#!/bin/bash
50+
#SBATCH --job-name=mnist # create a short name for your job
51+
#SBATCH --nodes=1 # node count
52+
#SBATCH --ntasks=1 # total number of tasks across all nodes
53+
#SBATCH --cpus-per-task=1 # cpu-cores per task (>1 if multi-threaded tasks)
54+
#SBATCH --mem=8G # total memory per node (4 GB per cpu-core is default)
55+
#SBATCH --gres=gpu:1 # number of gpus per node
56+
#SBATCH --time=00:05:00 # total run time limit (HH:MM:SS)
57+
#SBATCH --mail-type=begin # send email when job begins
58+
#SBATCH --mail-type=end # send email when job ends
59+
60+
# which gpu node was used
61+
echo "Running on host" $(hostname)
62+
63+
# print the slurm environment variables sorted by name
64+
printenv | grep -i slurm | sort
65+
66+
module purge
67+
module load anaconda3/2023.9
68+
conda activate /home/jdh4/.conda/envs/torch-env
69+
70+
kernprof -o ${SLURM_JOBID}.lprof -l mnist_classify.py --epochs=3
71+
```
72+
73+
`kernprof` is a profiler that wraps Python. Adroit has two different A100 nodes. Learn how to choose [specific nodes](https://researchcomputing.princeton.edu/systems/adroit#gpus).
74+
75+
Finally, submit the job while specifying the reservation:
76+
77+
```bash
78+
(torch-env) $ sbatch --reservation=multigpu job.slurm
79+
```
80+
81+
You should find that the code runs in about 20-40 seconds with 1 CPU-core depending on which A100 GPU node was used:
82+
83+
```
84+
$ seff 1937315
85+
Job ID: 1937315
86+
Cluster: adroit
87+
User/Group: aturing/cses
88+
State: COMPLETED (exit code 0)
89+
Cores: 1
90+
CPU Utilized: 00:00:36
91+
CPU Efficiency: 94.74% of 00:00:38 core-walltime
92+
Job Wall-clock time: 00:00:38
93+
Memory Utilized: 593.32 MB
94+
Memory Efficiency: 7.24% of 8.00 GB
95+
```
96+
97+
For jobs that run for longer than 1 minute, one should use the `jobstats` command instead of `seff`. Use `shistory -n` to see which node was used or look in the `slurm-#######.out` file.
98+
99+
Some variation in the run time is expected when multiple users are running on the same node. Also, the two A100 GPU nodes are not equal:
100+
101+
| hostname | CPU | GPU |
102+
| ----------- | ----------- | ----------- |
103+
| adroit-h11g1 | Intel Xeon Gold 6442Y @ 2.6GHz | NVIDIA A100 80GB PCIe |
104+
| adroit-h11g2 | Intel Xeon Gold 6342 @ 2.8GHz | NVIDIA A100-PCIE-40GB |
105+
106+
## Step 3: Analyze the Profiling Data
107+
108+
We installed [line_profiler](https://researchcomputing.princeton.edu/python-profiling) into the Conda environment and profiled the code. To analyze the profiling data:
109+
110+
```
111+
(torch-env) $ python -m line_profiler -rmt *.lprof
112+
Timer unit: 1e-06 s
113+
114+
Total time: 30.8937 s
115+
File: mnist_classify.py
116+
Function: train at line 39
117+
118+
Line # Hits Time Per Hit % Time Line Contents
119+
==============================================================
120+
39 @profile
121+
40 def train(args, model, device, train_loader, optimizer, epoch):
122+
41 3 213.1 71.0 0.0 model.train()
123+
42 2817 26106124.7 9267.3 84.5 for batch_idx, (data, target) in enumerate(train_loader):
124+
43 2814 286242.0 101.7 0.9 data, target = data.to(device), target.to(device)
125+
44 2814 296440.2 105.3 1.0 optimizer.zero_grad()
126+
45 2814 1189206.1 422.6 3.8 output = model(data)
127+
46 2814 81578.6 29.0 0.3 loss = F.nll_loss(output, target)
128+
47 2814 1979990.2 703.6 6.4 loss.backward()
129+
48 2814 841861.9 299.2 2.7 optimizer.step()
130+
49 2814 2095.3 0.7 0.0 if batch_idx % args.log_interval == 0:
131+
50 564 1852.9 3.3 0.0 print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
132+
51 282 2218.6 7.9 0.0 epoch, batch_idx * len(data), len(train_loader.dataset),
133+
52 282 105753.3 375.0 0.3 100. * batch_idx / len(train_loader), loss.item()))
134+
53 282 119.2 0.4 0.0 if args.dry_run:
135+
54 break
136+
137+
30.89 seconds - mnist_classify.py:39 - train
138+
```
139+
140+
The slowest line is number 42 which consumes 84.5% of the time in the training function. That line involves `train_loader` which is the data loader for the training set. Are you surprised that the data loader is the slowest step and not the forward pass or calculation of the gradients? Can we improve on this?
141+
142+
### Examine Your GPU Utilization
143+
144+
Use tools like [jobstats](https://researchcomputing.princeton.edu/support/knowledge-base/job-stats#jobstats), [gpudash](https://researchcomputing.princeton.edu/support/knowledge-base/gpu-computing#gpudash) and [stats.rc](https://researchcomputing.princeton.edu/support/knowledge-base/job-stats#stats.rc) to measure your GPU utilization. You can also do this on a [compute node in real time](https://researchcomputing.princeton.edu/support/knowledge-base/gpu-computing#gpu-utilization).
145+
146+
Note that GPU utilization as measured using nvidia-smi is only a measure of the fraction of the time that a GPU kernel is running on the GPU. It says nothing about how many CUDA cores are being used or how efficiently the GPU kernels have been written. However, for codes used by large communities, one can generally associate GPU utilization with overall GPU efficiency. For a more accurate measure of GPU utilization, use [Nsight Systems or Nsight Compute](https://researchcomputing.princeton.edu/support/knowledge-base/gpu-computing#profiling) to measure the occupancy.
147+
148+
## Step 4: Work through the Performance Tuning Guide
149+
150+
Make sure you optimize the single GPU case before going to multiple GPUs by working through the [Performance Tuning Guide](https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html).
151+
152+
## Step 5: Optimize Your Script
153+
154+
One technique that was discussed in the [Performance Tuning Guide](https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html) was using multiple CPU-cores to speed-up [ETL](https://en.wikipedia.org/wiki/Extract,_transform,_load). Let's put this into practice.
155+
156+
![multiple_workers](https://www.telesens.co/wp-content/uploads/2019/04/img_5ca4eff975d80.png)
157+
158+
*Credit for image above is [here](https://www.telesens.co/2019/04/04/distributed-data-parallel-training-using-pytorch-on-aws/).*
159+
160+
In `mnist_classify.py`, change `num_workers` from 1 to 8. And then in `job.slurm` change `--cpus-per-task` from 1 to 8. Then run the script again and note the speed-up:
161+
162+
```
163+
(torch-env) $ sbatch --reservation=multigpu job.slurm
164+
```
165+
166+
How did the profiling data change? Watch the [video](https://www.youtube.com/watch?v=wqTgM-Wq4YY&t=296s) for the solution. For consistency between the Slurm script and PyTorch script, one can use:
167+
168+
```python
169+
import os
170+
...
171+
cuda_kwargs = {'num_workers': int(os.environ["SLURM_CPUS_PER_TASK"]),
172+
...
173+
```
174+
175+
Several environment variables are set in the Slurm script. These can be referenced by the PyTorch script as demonstrated above. To see all of the available environment variables that are set in the Slurm script, add this line to `job.slurm`:
176+
177+
```
178+
printenv | sort
179+
```
180+
181+
Consider these external data loading libraries: [ffcv](https://github.com/libffcv/ffcv) and [NVIDIA DALI](https://developer.nvidia.com/dali).
182+
183+
## Summary
184+
185+
It is essential to optimize your code before going to multi-GPU training since the inefficiencies will only be magnified otherwise. The more GPUs you request in a Slurm job, the longer you will wait for the job to run. If you can get your work done using an optimized script running on a single GPU then proceed that way. Do not use multiple GPUs if your GPU efficiency is low. The average GPU efficiency on Della is around 50%.
186+
187+
Next, we focus on scaling the code to multiple GPUs (go to [next section](./03_pytorch_dpp.md)).
188+
189+
## How was the Conda environment made?
190+
191+
Your `/home` directory on Adroit probably has a capacity of 9.3 GB. To store Conda environments in another location see [this page](https://researchcomputing.princeton.edu/support/knowledge-base/checkquota). See the Research Computing knowledge base on [PyTorch](https://researchcomputing.princeton.edu/support/knowledge-base/pytorch) for installation directions.
File renamed without changes.

docs/hpc/08_ml_ai_hpc/04_pytorch_lightning.md

Whitespace-only changes.

docs/hpc/08_ml_ai_hpc/05_pytorch_fsdp.md

Whitespace-only changes.

docs/hpc/08_ml_ai_hpc/06_tensorflow.md

Whitespace-only changes.
File renamed without changes.

0 commit comments

Comments
 (0)