Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions recipes/full_finetune_distributed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,14 @@ def train(self) -> None:

# Loss is normalized by default so we multiply by the number of tokens
# This way we can normalize by the total number of tokens if we're accumulating gradients
# NOTE: we can't skip the loss multiply / scale_grads when
# gradient_accumulation_steps == 1 here. current_num_tokens is
# rank-local (count of unmasked labels on this rank), while
# num_tokens below is all-reduced across ranks. Skipping both
# would normalize each rank by its own local token count,
# up-weighting ranks with shorter batches. The single-device
# recipes can skip because world_size == 1 makes the two
# quantities identical.
current_loss = self._loss_step(batch) * current_num_tokens
running_loss += current_loss
# For optimizer in backward, we need to normalize before calling backward
Expand Down
31 changes: 23 additions & 8 deletions recipes/full_finetune_single_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,19 @@ def train(self) -> None:
).sum()
num_tokens += current_num_tokens

# Mean loss for in-bwd optimizers, else multiply by token count
loss_factor = current_num_tokens if not self.optimizer_in_bwd else 1.0
# Mean loss for in-bwd optimizers, else multiply by token count.
# When gradient_accumulation_steps == 1 and not using optimizer_in_bwd,
# the loss is already normalized per-token so we skip the loss_factor
# multiplication and the subsequent scale_grads call, saving ~5% per step.
skip_loss_scale = (
not self.optimizer_in_bwd
and self._gradient_accumulation_steps == 1
)
loss_factor = (
1.0
if skip_loss_scale or self.optimizer_in_bwd
else current_num_tokens
)
current_loss = self._loss_step(batch) * loss_factor

running_loss += current_loss.detach()
Expand All @@ -621,14 +632,14 @@ def train(self) -> None:
# Take a normal optimizer step
if (batch_count + 1) % self._gradient_accumulation_steps == 0:
grad_norm = None
if not self.optimizer_in_bwd:
if not self.optimizer_in_bwd and not skip_loss_scale:
training.scale_grads_(
self._model.parameters(), 1.0 / num_tokens
)
if self._clip_grad_norm:
grad_norm = torch.nn.utils.clip_grad_norm_(
self._model.parameters(), float(self._clip_grad_norm)
)
if not self.optimizer_in_bwd and self._clip_grad_norm:
grad_norm = torch.nn.utils.clip_grad_norm_(
self._model.parameters(), float(self._clip_grad_norm)
)

# This will be a no-op for optim in bwd, but prevents a warning w/ LR Scheduler
self.optimizer.step()
Expand All @@ -641,7 +652,11 @@ def train(self) -> None:
inner_step_count += 1
loss_value = (
running_loss
/ (num_tokens if not self.optimizer_in_bwd else 1.0)
/ (
num_tokens
if not self.optimizer_in_bwd and not skip_loss_scale
else 1.0
)
).item()
pbar.update(1)
pbar.set_description(
Expand Down
8 changes: 8 additions & 0 deletions recipes/lora_finetune_distributed.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,14 @@ def train(self) -> None:

# Loss is normalized by default so we multiply by the number of tokens
# This way we can normalize by the total number of tokens if we're accumulating gradients
# NOTE: we can't skip the loss multiply / scale_grads when
# gradient_accumulation_steps == 1 here. current_num_tokens is
# rank-local (count of unmasked labels on this rank), while
# num_tokens below is all-reduced across ranks. Skipping both
# would normalize each rank by its own local token count,
# up-weighting ranks with shorter batches. The single-device
# recipes can skip because world_size == 1 makes the two
# quantities identical.
current_loss = self._loss_step(batch) * current_num_tokens
running_loss += current_loss
current_loss.backward()
Expand Down
14 changes: 11 additions & 3 deletions recipes/lora_finetune_single_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,13 +637,18 @@ def train(self) -> None:

# Loss is normalized by default so we multiply by the number of tokens
# This way we can normalize by the total number of tokens if we're accumulating gradients
current_loss = self._loss_step(batch) * current_num_tokens
skip_loss_scale = self._gradient_accumulation_steps == 1
current_loss = self._loss_step(batch) * (
1.0 if skip_loss_scale else current_num_tokens
)
running_loss += current_loss
current_loss.backward()

# Step with optimizer
if (idx + 1) % self._gradient_accumulation_steps == 0:
training.scale_grads(self._model, 1 / num_tokens)
grad_norm = None
if not skip_loss_scale:
training.scale_grads(self._model, 1 / num_tokens)
if self._clip_grad_norm is not None:
grad_norm = torch.nn.utils.clip_grad_norm_(
self._model.parameters(),
Expand All @@ -655,7 +660,10 @@ def train(self) -> None:
# Update the number of steps when the weights are updated
self.global_step += 1

loss_to_log = running_loss.detach().item() / num_tokens
loss_to_log = (
running_loss.detach().item()
/ (1.0 if skip_loss_scale else num_tokens)
)
pbar.update(1)
pbar.set_description(
f"{curr_epoch + 1}|{self.global_step}|Loss: {loss_to_log}"
Expand Down
17 changes: 14 additions & 3 deletions recipes/qat_single_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,13 @@ def train(self) -> None:

# Loss is normalized by default so we multiply by the number of tokens
# This way we can normalize by the total number of tokens if we're accumulating gradients
current_loss = current_loss * current_num_tokens
skip_loss_scale = (
not self._optimizer_in_bwd
and self._gradient_accumulation_steps == 1
)
current_loss = current_loss * (
1.0 if skip_loss_scale else current_num_tokens
)

# free outputs otherwise it peaks backward memory
del outputs
Expand All @@ -590,8 +596,10 @@ def train(self) -> None:
current_loss.backward()

if (idx + 1) % self._gradient_accumulation_steps == 0:
if not self._optimizer_in_bwd:
grad_norm = None
if not self._optimizer_in_bwd and not skip_loss_scale:
training.scale_grads(self._model, 1 / num_tokens)
if not self._optimizer_in_bwd:
if self._clip_grad_norm is not None:
grad_norm = torch.nn.utils.clip_grad_norm_(
self._model.parameters(),
Expand All @@ -603,7 +611,10 @@ def train(self) -> None:
# Update the number of steps when the weights are updated
self.global_step += 1

loss_to_log = running_loss.detach().item() / num_tokens
loss_to_log = (
running_loss.detach().item()
/ (1.0 if skip_loss_scale else num_tokens)
)
pbar.update(1)
pbar.set_description(
f"{curr_epoch + 1}|{self.global_step}|Loss: {loss_to_log}"
Expand Down
2 changes: 1 addition & 1 deletion tests/recipes/test_full_finetune_single_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _fetch_expected_loss_values(self, model_ckpt):
@pytest.mark.parametrize("compile", [True, False])
@pytest.mark.parametrize(
"micro_batch_size, gradient_accumulation_steps, optimizer_in_bwd",
[(8, 1, True), (2, 4, False)],
[(8, 1, True), (2, 4, False), (8, 1, False)],
)
@pytest.mark.parametrize(
"config, model_ckpt",
Expand Down