Skip to content

Commit 164943c

Browse files
authored
Add a gather_for_metrics capability (#540)
* Add test and full implementation
1 parent 9c1e688 commit 164943c

18 files changed

Lines changed: 257 additions & 48 deletions

docs/source/quicktour.mdx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,13 @@ validation_dataloader = accelerator.prepare(validation_dataloader)
120120

121121
As for your training dataloader, it will mean that (should you run your script on multiple devices) each device will
122122
only see part of the evaluation data. This means you will need to group your predictions together. This is very easy to
123-
do with the [`~Accelerator.gather`] method.
123+
do with the [`~Accelerator.gather_for_metrics`] method.
124124

125125
```python
126126
for inputs, targets in validation_dataloader:
127127
predictions = model(inputs)
128128
# Gather all predictions and targets
129-
all_predictions = accelerator.gather(predictions)
130-
all_targets = accelerator.gather(targets)
129+
all_predictions, all_targets = accelerator.gather_for_metrics((predictions, targets))
131130
# Example of use with a *Datasets.Metric*
132131
metric.add_batch(all_predictions, all_targets)
133132
```
@@ -141,11 +140,17 @@ As for the training dataloader, passing your validation dataloader through
141140
Any instruction using your training dataloader length (for instance if you need the number of total training steps
142141
to create a learning rate scheduler) should go after the call to [`~Accelerator.prepare`].
143142

143+
As some data at the end of the dataset may be duplicated so the batch can divide equally to all workers, metrics should be
144+
calculated through the [`~Accelerator.gather_for_metrics`] method to automatically remove the duplicated data.
145+
146+
If for some reason you don't wish to have this automatically done, [`~Accelerator.gather`] can be used instead to gather
147+
the data across all processes and this can manually be done instead.
148+
144149
</Tip>
145150

146151
<Tip warning={true}>
147152

148-
The [`~Accelerator.gather`] method requires the tensors to be all the same size on each process. If
153+
The [`~Accelerator.gather`] and [`~Accelerator.gather_for_metrics`] methods require the tensors to be all the same size on each process. If
149154
you have tensors of different sizes on each process (for instance when dynamically padding to the maximum length in
150155
a batch), you should use the [`~Accelerator.pad_across_processes`] method to pad you tensor to the
151156
biggest size across processes.

examples/by_feature/checkpointing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def training_function(config, args):
238238
outputs = model(**batch)
239239
predictions = outputs.logits.argmax(dim=-1)
240240
# It is slightly faster to call this once, than multiple times
241-
predictions, references = accelerator.gather((predictions, batch["labels"]))
241+
predictions, references = accelerator.gather_for_metrics((predictions, batch["labels"]), eval_dataloader)
242242
metric.add_batch(
243243
predictions=predictions,
244244
references=references,

examples/by_feature/cross_validation.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,9 @@ def training_function(config, args):
207207
with torch.no_grad():
208208
outputs = model(**batch)
209209
predictions = outputs.logits.argmax(dim=-1)
210-
predictions, references = accelerator.gather((predictions, batch["labels"]))
210+
predictions, references = accelerator.gather_for_metrics(
211+
(predictions, batch["labels"]), eval_dataloader
212+
)
211213
metric.add_batch(
212214
predictions=predictions,
213215
references=references,
@@ -226,7 +228,7 @@ def training_function(config, args):
226228
with torch.no_grad():
227229
outputs = model(**batch)
228230
predictions = outputs.logits
229-
predictions, references = accelerator.gather((predictions, batch["labels"]))
231+
predictions, references = accelerator.gather_for_metrics((predictions, batch["labels"]), test_dataloader)
230232
fold_predictions.append(predictions.cpu())
231233
if i == 0:
232234
# We need all of the test predictions

examples/by_feature/fsdp_with_peak_mem_tracking.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -270,23 +270,16 @@ def collate_fn(examples):
270270
# context manager to track the peak memory usage during the evaluation
271271
with TorchTracemalloc() as tracemalloc:
272272
model.eval()
273-
samples_seen = 0
274273
for step, batch in enumerate(eval_dataloader):
275274
# We could avoid this line since we set the accelerator with `device_placement=True`.
276275
batch.to(accelerator.device)
277276
with torch.no_grad():
278277
outputs = model(**batch)
279278
predictions = outputs.logits.argmax(dim=-1)
280279
# It is slightly faster to call this once, than multiple times
281-
predictions, references = accelerator.gather(
282-
(predictions, batch["labels"])
283-
) # If we are in a multiprocess environment, the last batch has duplicates
284-
if accelerator.use_distributed:
285-
if step == len(eval_dataloader) - 1:
286-
predictions = predictions[: len(eval_dataloader.dataset) - samples_seen]
287-
references = references[: len(eval_dataloader.dataset) - samples_seen]
288-
else:
289-
samples_seen += references.shape[0]
280+
predictions, references = accelerator.gather_for_metrics(
281+
(predictions, batch["labels"]), eval_dataloader
282+
)
290283
metric.add_batch(
291284
predictions=predictions,
292285
references=references,

examples/by_feature/gradient_accumulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def training_function(config, args):
171171
with torch.no_grad():
172172
outputs = model(**batch)
173173
predictions = outputs.logits.argmax(dim=-1)
174-
predictions, references = accelerator.gather((predictions, batch["labels"]))
174+
predictions, references = accelerator.gather_for_metrics((predictions, batch["labels"]), eval_dataloader)
175175
metric.add_batch(
176176
predictions=predictions,
177177
references=references,

examples/by_feature/memory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,9 @@ def inner_training_loop(batch_size):
183183
with torch.no_grad():
184184
outputs = model(**batch)
185185
predictions = outputs.logits.argmax(dim=-1)
186-
predictions, references = accelerator.gather((predictions, batch["labels"]))
186+
predictions, references = accelerator.gather_for_metrics(
187+
(predictions, batch["labels"]), eval_dataloader
188+
)
187189
metric.add_batch(
188190
predictions=predictions,
189191
references=references,

examples/by_feature/multi_process_metrics.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ def training_function(config, args):
187187
else:
188188
# Otherwise we add the number of samples seen
189189
samples_seen += references.shape[0]
190+
# All of this can be avoided if you use `Accelerator.gather_for_metrics` instead of `Accelerator.gather`:
191+
# accelerator.gather_for_metrics((predictions, references), eval_dataloader)
190192
metric.add_batch(
191193
predictions=predictions,
192194
references=references,

examples/by_feature/tracking.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def training_function(config, args):
197197
outputs = model(**batch)
198198
predictions = outputs.logits.argmax(dim=-1)
199199
# It is slightly faster to call this once, than multiple times
200-
predictions, references = accelerator.gather((predictions, batch["labels"]))
200+
predictions, references = accelerator.gather_for_metrics((predictions, batch["labels"]), eval_dataloader)
201201
metric.add_batch(
202202
predictions=predictions,
203203
references=references,

examples/complete_cv_example.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -233,27 +233,18 @@ def training_function(config, args):
233233
accelerator.save_state(output_dir)
234234
model.eval()
235235
accurate = 0
236-
samples_seen = 0
237236
for step, batch in enumerate(eval_dataloader):
238237
# We could avoid this line since we set the accelerator with `device_placement=True`.
239238
batch = {k: v.to(accelerator.device) for k, v in batch.items()}
240239
inputs = (batch["image"] - mean) / std
241240
with torch.no_grad():
242241
outputs = model(inputs)
243242
predictions = outputs.argmax(dim=-1)
244-
predictions, references = accelerator.gather((predictions, batch["label"]))
245-
if accelerator.use_distributed:
246-
if step == len(eval_dataloader) - 1:
247-
predictions = predictions[: len(eval_dataloader) - samples_seen]
248-
references = references[: len(eval_dataloader) - samples_seen]
249-
else:
250-
samples_seen += references.shape[0]
251-
else:
252-
samples_seen += references.shape[0]
253-
accurate_preds = predictions == references
243+
predictions, labels = accelerator.gather_for_metrics((predictions, batch["label"]), eval_dataloader)
244+
accurate_preds = predictions == labels
254245
accurate += accurate_preds.long().sum()
255246

256-
eval_metric = accurate.item() / samples_seen
247+
eval_metric = accurate.item() / accelerator.gradient_state.samples_seen
257248
# Use accelerator.print to print only on the main process.
258249
accelerator.print(f"epoch {epoch}: {100 * eval_metric:.2f}")
259250
if args.with_tracking:

examples/complete_nlp_example.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,23 +211,14 @@ def collate_fn(examples):
211211
accelerator.save_state(output_dir)
212212

213213
model.eval()
214-
samples_seen = 0
215214
for step, batch in enumerate(eval_dataloader):
216215
# We could avoid this line since we set the accelerator with `device_placement=True`.
217216
batch.to(accelerator.device)
218217
with torch.no_grad():
219218
outputs = model(**batch)
220219
predictions = outputs.logits.argmax(dim=-1)
221220
# It is slightly faster to call this once, than multiple times
222-
predictions, references = accelerator.gather(
223-
(predictions, batch["labels"])
224-
) # If we are in a multiprocess environment, the last batch has duplicates
225-
if accelerator.use_distributed:
226-
if step == len(eval_dataloader) - 1:
227-
predictions = predictions[: len(eval_dataloader.dataset) - samples_seen]
228-
references = references[: len(eval_dataloader.dataset) - samples_seen]
229-
else:
230-
samples_seen += references.shape[0]
221+
predictions, references = accelerator.gather_for_metrics((predictions, batch["labels"]), eval_dataloader)
231222
metric.add_batch(
232223
predictions=predictions,
233224
references=references,

0 commit comments

Comments
 (0)