Monitoring Training Progress Across Multiple Processes
The rank identifies which process is executing the code.
One Script, Three Hardware Situations
Training a PyTorch model on several GPUs raises a practical question before any distributed logic enters the picture: which GPUs will the program actually touch? A machine might have one GPU, two, or four, and the same script may need to run on all of them. This article follows a single script, named ch02-DDP-script.py, through these situations. At each point it asks where the observed behavior comes from and where the output can go wrong.
The script is designed to run on both single-GPU and multi-GPU machines, so its code does not change between those cases. The differences you will observe come from two places: the environment the script runs in, and the way the distributed training machinery spreads work across the available GPUs. Keeping those two sources separate is the central habit of this article. When you look at a run, the goal is to say which line of reasoning explains each thing you see.
What do you think happens?
Suppose the script runs on a machine with two GPUs, and its testing loop contains a print statement that nothing guards. How many times does that line appear in the output?
Reveal answer
Twice
Each DDP process runs the full script independently, so each process reaches the print statement inside the testing loop and executes it. With two processes, the same message appears two times, once tagged by each GPU.
Reading a Single-GPU Run as a Baseline
Before looking at multiple GPUs, it helps to know what a single-GPU run should look like. When the script runs on one GPU, the first lines report the PyTorch version, whether CUDA is available, and how many GPUs were found. Then each epoch appears with its batch size and its training or validation loss. The tag at the start of each line, GPU0, identifies which process printed that line.
The source treats this output as a sanity check. A sanity check is a quick comparison that tells you the basic setup behaves as expected before you trust the detailed results. For a single GPU, the header should report one GPU, and the per-epoch structure should match what you would anticipate for one process. Once that baseline is clear, any difference in a multi-GPU run stands out.
Limiting GPUs with CUDA_VISIBLE_DEVICES
The simplest way to restrict how many GPUs a training run uses is the CUDA_VISIBLE_DEVICES environment variable. You set it on the same line you use to start the script, so the restriction applies only to that one run. Instead of launching the script plainly, you prefix the command with the variable assignment. The variable takes GPU indices, and the script sees only the GPUs you list.
- CUDA_VISIBLE_DEVICES=0 sets the variable for this single command only
- Only GPU index 0 is made available to the script
- The script file itself is unchanged
The variable also accepts a comma-separated list of indices. The source describes a machine with four GPUs where you want only the first and the third. You list those indices together, separated by commas, on the same command line. The same script then runs with exactly those GPUs visible.
The diagram above shows the remapping that matters when you read output. The script does not refer to the physical numbering of the machine. It refers to the GPUs it was given, starting again from zero. Listing indices 2 and 3 therefore produces devices named cuda:0 and cuda:1 inside the script, even though the hardware keeps its original labels.
The reason this approach is convenient is that GPU allocation happens without modifying your PyTorch scripts. The same file can be launched with different environment settings to use different hardware. That keeps the code independent of the machine it happens to run on, which is useful when one script must work on a laptop with one GPU and a server with four.
Reading the Interleaved Two-GPU Log
Now run the same command and the same script on a machine with two GPUs. The header now reports two GPUs available. The epoch lines carry tags for both GPU0 and GPU1, and they appear in an interleaved order rather than in a single tidy sequence. This interleaving is what you would expect when two processes run at the same time, and it is the first thing to notice when you read a multi-GPU log.
Trace the last four lines of this log with a specific question in mind: how many times does the test accuracy appear, and from which processes? You will find one line from GPU1 and one from GPU0. Those two lines are not a bug in the arithmetic. They are two independent processes each reporting the same kind of result, and that is the place where the duplicated output comes from.
The GPU tag on each line is your main tool for reading a multi-process log. It tells you which process wrote the line. When two lines have identical message text, the tag is what separates them. A repeated message with two different tags is a record of two processes, not a mistake in the result.
Why Every Process Repeats the Print
The source explains the duplication with a direct chain of reasoning. Distributed Data Parallel, usually called DDP, replicates the model onto each GPU, and each process runs independently. In this setup a process and a GPU correspond to one another: each process drives its own GPU with its own copy of the model. Because each copy works on part of the data, the batches of training examples are divided between the processes.
The chain continues into the output. If your code contains a print statement inside the testing loop, then every process reaches that statement and executes it. Each execution produces a line, so the same message appears once per process. Nothing in this chain requires the processes to coordinate their printing. Each one simply does what the code tells it to do.
Limiting Output with the Process Rank
If the duplication bothers you, the source suggests using the rank of each process to control print statements. The rank identifies which process is executing the code. A condition that checks for rank zero lets only the first process perform the print, and the other processes skip it. The guard is an ordinary if statement wrapped around the print call.
- rank is the identifier of the process running this line
- Only the process whose rank is zero enters the if block
- Every other process skips the print and continues running
print(f"Test accuracy: {accuracy:.2f}")Every process runs the print, so each accuracy line appears once per GPU
if rank == 0:
print(f"Test accuracy: {accuracy:.2f}")Only the rank zero process runs the print, so the line appears once
Notice what this change does and does not do. It changes which process prints the line, so the message appears once. It does not change the computation: every process still runs the testing loop and computes accuracy. The guard controls only the output step. This separation is worth keeping in mind whenever you add a guard, because a guard placed around the computation itself would change what the training run does.
Mistakes Beginners Make with Multi-GPU Output
Common mistake Treating a repeated line as a sign that the metric is wrong
What it looks likeA test accuracy line appears twice in a two-GPU log, and the reader concludes the evaluation ran incorrectly.
Why it failsEach process runs the print statement independently, so repeated lines record how many processes executed it, not a problem with the arithmetic.
Do this insteadCheck the GPU tag on each line to see which process printed it, and ask how many processes reach that statement.
Common mistake Wrapping the computation in a rank check instead of the print
What it looks likePlacing if rank == 0 around the whole testing loop so that only one process evaluates the model.
Why it failsThe guard then changes what the run does, because the other processes skip the computation entirely.
Do this insteadKeep the computation in every process and wrap only the print call in the rank check.
Common mistake Assuming the GPU restriction persists after the command ends
What it looks likeSetting CUDA_VISIBLE_DEVICES on one run, then launching the script plainly later and expecting the same GPU limit.
Why it failsThe variable is attached to the single command line that sets it, so the restriction applies only to that one run.
Do this insteadPut the variable assignment on every command where you want the restriction to apply.
Common mistake Reading the listed indices as the names the script uses
What it looks likeSetting CUDA_VISIBLE_DEVICES=2,3 and expecting the script to refer to the device as cuda:2 and cuda:3.
Why it failsThe script sees the listed GPUs starting again from zero, so they appear as cuda:0 and cuda:1.
Do this insteadMap the script's cuda indices back to the listed physical indices in order when you read the output.
Alternative Route: Add-On APIs Such as Fabric
DDP is not the only route to multi-GPU training in PyTorch. The source mentions add-on APIs for readers who prefer a more straightforward way to use several GPUs. One example it names is the open-source Fabric library, which it describes as an add-on API for PyTorch. The source points to a separate write-up on Fabric for further detail, so this article does not teach Fabric's own interface.
What you can take from this section is the idea of an add-on API: a library that sits alongside a PyTorch training loop and helps manage multi-GPU use. The output issues in this article, including the repeated lines and the rank-based guard, are the kinds of concerns you will still need to reason about when reading the logs of any multi-GPU setup.
Practice: Plan a Four-GPU Run
A machine has four GPUs, indexed 0 through 3. You want to run the script on only the first and third GPUs. Write the command prefix that restricts the run, then predict how many times an unguarded accuracy print in the testing loop will appear, and which GPU tags will appear on those lines.
Hints
- The source's pattern for several GPUs is a comma-separated list of indices placed in the variable assignment.
- Count how many processes will be running the script, since each one executes the unguarded print.
- Inside the script, the two GPUs will be named cuda:0 and cuda:1, but the tags reflect the processes that ran.
Summary Notes for Multi-Process Monitoring
- 1CUDA_VISIBLE_DEVICES limits a single launch to the GPU indices you list, and it does this without editing the script.
- 2In DDP, each process runs the full script independently with its own model copy on its own GPU, so each process reports on its own.
- 3Repeated output lines come from every process executing the same print statement, and the GPU tag shows which process printed each line.
- 4A rank == 0 guard around a print limits output to one process while leaving the computation unchanged in every process.
- 5Fabric is an add-on API for multi-GPU PyTorch training, and this article points to a separate write-up for its details.
At a glance
- CUDA_VISIBLE_DEVICES restricts which GPUs one command can use by listing indices on that command line, without modifying the script.
- DDP replicates the model onto each GPU and runs each process independently, so every print statement executes once per process.
- The GPU tag on each log line identifies the process that wrote it, which is how repeated lines are told apart.
- Guarding a print with rank == 0 changes only which process prints, not the computation that every process performs.
- Fabric is an add-on API for multi-GPU PyTorch training, covered in a separate write-up that this article does not teach.