| | from collections import defaultdict |
| | import torch |
| | import intel_extension_for_pytorch as ipex |
| | import intel_extension_for_pytorch._C as core |
| |
|
| | |
| |
|
| | OptState = ipex.cpu.autocast._grad_scaler.OptState |
| | _MultiDeviceReplicator = ipex.cpu.autocast._grad_scaler._MultiDeviceReplicator |
| | _refresh_per_optimizer_state = ipex.cpu.autocast._grad_scaler._refresh_per_optimizer_state |
| |
|
| | def _unscale_grads_(self, optimizer, inv_scale, found_inf, allow_fp16): |
| | per_device_inv_scale = _MultiDeviceReplicator(inv_scale) |
| | per_device_found_inf = _MultiDeviceReplicator(found_inf) |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | per_device_and_dtype_grads = defaultdict(lambda: defaultdict(list)) |
| | |
| | if hasattr(optimizer, "sync_grad"): |
| | optimizer.sync_grad() |
| | with torch.no_grad(): |
| | for group in optimizer.param_groups: |
| | for param in group["params"]: |
| | if param.grad is None: |
| | continue |
| | if (not allow_fp16) and param.grad.dtype == torch.float16: |
| | raise ValueError("Attempting to unscale FP16 gradients.") |
| | if param.grad.is_sparse: |
| | |
| | |
| | |
| | |
| | if param.grad.dtype is torch.float16: |
| | param.grad = param.grad.coalesce() |
| | to_unscale = param.grad._values() |
| | else: |
| | to_unscale = param.grad |
| |
|
| | |
| | to_unscale = to_unscale.to("cpu") |
| | per_device_and_dtype_grads[to_unscale.device][ |
| | to_unscale.dtype |
| | ].append(to_unscale) |
| |
|
| | for _, per_dtype_grads in per_device_and_dtype_grads.items(): |
| | for grads in per_dtype_grads.values(): |
| | core._amp_foreach_non_finite_check_and_unscale_( |
| | grads, |
| | per_device_found_inf.get("cpu"), |
| | per_device_inv_scale.get("cpu"), |
| | ) |
| |
|
| | return per_device_found_inf._per_device_tensors |
| |
|
| | def unscale_(self, optimizer): |
| | """ |
| | Divides ("unscales") the optimizer's gradient tensors by the scale factor. |
| | :meth:`unscale_` is optional, serving cases where you need to |
| | :ref:`modify or inspect gradients<working-with-unscaled-gradients>` |
| | between the backward pass(es) and :meth:`step`. |
| | If :meth:`unscale_` is not called explicitly, gradients will be unscaled automatically during :meth:`step`. |
| | Simple example, using :meth:`unscale_` to enable clipping of unscaled gradients:: |
| | ... |
| | scaler.scale(loss).backward() |
| | scaler.unscale_(optimizer) |
| | torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm) |
| | scaler.step(optimizer) |
| | scaler.update() |
| | Args: |
| | optimizer (torch.optim.Optimizer): Optimizer that owns the gradients to be unscaled. |
| | .. warning:: |
| | :meth:`unscale_` should only be called once per optimizer per :meth:`step` call, |
| | and only after all gradients for that optimizer's assigned parameters have been accumulated. |
| | Calling :meth:`unscale_` twice for a given optimizer between each :meth:`step` triggers a RuntimeError. |
| | .. warning:: |
| | :meth:`unscale_` may unscale sparse gradients out of place, replacing the ``.grad`` attribute. |
| | """ |
| | if not self._enabled: |
| | return |
| |
|
| | self._check_scale_growth_tracker("unscale_") |
| |
|
| | optimizer_state = self._per_optimizer_states[id(optimizer)] |
| |
|
| | if optimizer_state["stage"] is OptState.UNSCALED: |
| | raise RuntimeError( |
| | "unscale_() has already been called on this optimizer since the last update()." |
| | ) |
| | elif optimizer_state["stage"] is OptState.STEPPED: |
| | raise RuntimeError("unscale_() is being called after step().") |
| |
|
| | |
| | assert self._scale is not None |
| | inv_scale = self._scale.to("cpu").double().reciprocal().float().to(self._scale.device) |
| | found_inf = torch.full( |
| | (1,), 0.0, dtype=torch.float32, device=self._scale.device |
| | ) |
| |
|
| | optimizer_state["found_inf_per_device"] = self._unscale_grads_( |
| | optimizer, inv_scale, found_inf, False |
| | ) |
| | optimizer_state["stage"] = OptState.UNSCALED |
| |
|
| | def update(self, new_scale=None): |
| | """ |
| | Updates the scale factor. |
| | If any optimizer steps were skipped the scale is multiplied by ``backoff_factor`` |
| | to reduce it. If ``growth_interval`` unskipped iterations occurred consecutively, |
| | the scale is multiplied by ``growth_factor`` to increase it. |
| | Passing ``new_scale`` sets the new scale value manually. (``new_scale`` is not |
| | used directly, it's used to fill GradScaler's internal scale tensor. So if |
| | ``new_scale`` was a tensor, later in-place changes to that tensor will not further |
| | affect the scale GradScaler uses internally.) |
| | Args: |
| | new_scale (float or :class:`torch.FloatTensor`, optional, default=None): New scale factor. |
| | .. warning:: |
| | :meth:`update` should only be called at the end of the iteration, after ``scaler.step(optimizer)`` has |
| | been invoked for all optimizers used this iteration. |
| | """ |
| | if not self._enabled: |
| | return |
| |
|
| | _scale, _growth_tracker = self._check_scale_growth_tracker("update") |
| |
|
| | if new_scale is not None: |
| | |
| | if isinstance(new_scale, float): |
| | self._scale.fill_(new_scale) |
| | else: |
| | reason = "new_scale should be a float or a 1-element torch.FloatTensor with requires_grad=False." |
| | assert isinstance(new_scale, torch.FloatTensor), reason |
| | assert new_scale.numel() == 1, reason |
| | assert new_scale.requires_grad is False, reason |
| | self._scale.copy_(new_scale) |
| | else: |
| | |
| | |
| | found_infs = [ |
| | found_inf.to(device="cpu", non_blocking=True) |
| | for state in self._per_optimizer_states.values() |
| | for found_inf in state["found_inf_per_device"].values() |
| | ] |
| |
|
| | assert len(found_infs) > 0, "No inf checks were recorded prior to update." |
| |
|
| | found_inf_combined = found_infs[0] |
| | if len(found_infs) > 1: |
| | for i in range(1, len(found_infs)): |
| | found_inf_combined += found_infs[i] |
| |
|
| | to_device = _scale.device |
| | _scale = _scale.to("cpu") |
| | _growth_tracker = _growth_tracker.to("cpu") |
| |
|
| | core._amp_update_scale_( |
| | _scale, |
| | _growth_tracker, |
| | found_inf_combined, |
| | self._growth_factor, |
| | self._backoff_factor, |
| | self._growth_interval, |
| | ) |
| |
|
| | _scale = _scale.to(to_device) |
| | _growth_tracker = _growth_tracker.to(to_device) |
| | |
| | self._per_optimizer_states = defaultdict(_refresh_per_optimizer_state) |
| |
|
| | def gradscaler_init(): |
| | torch.xpu.amp.GradScaler = ipex.cpu.autocast._grad_scaler.GradScaler |
| | torch.xpu.amp.GradScaler._unscale_grads_ = _unscale_grads_ |
| | torch.xpu.amp.GradScaler.unscale_ = unscale_ |
| | torch.xpu.amp.GradScaler.update = update |
| | return torch.xpu.amp.GradScaler |
| |
|