
MLNLP
(
機器學習演算法與自然語言處理
)社群是國內外知名自然語言處理社群,受眾覆蓋國內外NLP碩博生、高校老師以及企業研究人員。
社群的願景 是促進國內外自然語言處理,機器學習學術界、產業界和廣大愛好者之間的交流,特別是初學者同學們的進步。
機器學習演算法與自然語言處理
-
混合精度訓練 -
大 batch 訓練或者稱為梯度累加 -
gradient checkpointing 梯度檢查點

-
維護一個 FP32 數值精度模型的副本 -
在每個 iteration
-
複製並且轉換成 FP16 模型 -
前向傳播(FP16 的模型引數),此時 weights, activations 都是 FP16 -
loss 乘 scale factor s -
反向傳播(FP16 的模型引數和引數梯度), 此時 gradients 也是 FP16 -
引數梯度乘 1/s -
利用 FP16 的梯度更新 FP32 的模型引數
-
在 OpenMMLab 上游庫例如 MMDetection 中使用 MMCV 的 AMP -
使用者只想簡單呼叫 MMCV 中的 AMP,而不依賴上游庫
(1) OpenMMLab 上游庫如何使用 MMCV 的 AMP
fp16 = dict(loss_scale=
512.
)
# 表示靜態 scale
# 表示動態 scale
fp16 = dict(loss_scale=
'dynamic'
)
# 透過字典形式靈活開啟動態 scale
fp16 = dict(loss_scale=dict(init_scale=
512.
,mode=
'dynamic'
))
loss_scale='dynamic'
(2) 呼叫 MMCV 中的 AMP
-
將 auto_fp16 裝飾器應用到 model 的 forward 函式上 -
設定模型的 fp16_enabled 為 True 表示開啟 AMP 訓練,否則不生效 -
如果開啟了 AMP,需要同時配置對應的 FP16 最佳化器配置 Fp16OptimizerHook -
在訓練的不同時刻,呼叫 Fp16OptimizerHook,如果你同時使用了 MMCV 中的 Runner 模組,那麼直接將第 3 步的引數輸入到 Runner 中即可 -
(可選) 如果對應某些 OP 希望強制執行在 FP32 上,則可以在對應位置引入 force_fp32 裝飾器
# 1 作用到 forward 函式中
classExampleModule(nn.Module):
@auto_fp16()
defforward(self, x, y):
return
x, y
# 2 如果開啟 AMP,則需要加入開啟標誌
model.fp16_enabled =
True
# 3 配置 Fp16OptimizerHook
optimizer_config = Fp16OptimizerHook(
**cfg.optimizer_config, **fp16_cfg, distributed=distributed)
# 4 傳遞給 runner
runner.register_training_hooks(cfg.lr_config, optimizer_config,
cfg.checkpoint_config, cfg.log_config,
cfg.get(
'momentum_config'
,
None
))
# 5 可選
classExampleModule(nn.Module):
@auto_fp16()
defforward(self, x, y):
features=self._forward(x, y)
loss=self._loss(features,labels)
return
loss
def_forward(self, x, y):
pass
@force_fp32(apply_to=('features',))
def_loss(features,labels) :
pass
- 注意 force_fp32 要生效,依然需要 fp16_enabled 為 True 才生效。
y_pred = model(xx)
loss = loss_fn(y_pred, y)
loss.backward()
optimizer.step()
optimizer.zero_grad()
y_pred = model(xx)
loss = loss_fn(y_pred, y)
loss = loss / cumulative_iters
loss.backward()
if
current_iter % cumulative_iters==
0
optimizer.step()
optimizer.zero_grad()
@HOOKS.register_module()
classGradientCumulativeOptimizerHook(OptimizerHook):
def__init__(self, cumulative_iters=1, **kwargs):
self.cumulative_iters = cumulative_iters
self.divisible_iters =
0# 剩餘的可以被 cumulative_iters 整除的訓練迭代次數
self.remainder_iters =
0# 剩餘累加次數
self.initialized =
False
defafter_train_iter(self, runner):
# 只需要執行一次即可
ifnot
self.initialized:
self._init(runner)
if
runner.iter < self.divisible_iters:
loss_factor = self.cumulative_iters
else
:
loss_factor = self.remainder_iters
loss = runner.outputs[
'loss'
]
loss = loss / loss_factor
loss.backward()
if
(self.every_n_iters(runner, self.cumulative_iters)
or
self.is_last_iter(runner)):
runner.optimizer.step()
runner.optimizer.zero_grad()
def_init(self, runner):
residual_iters = runner.max_iters - runner.iter
self.divisible_iters = (
residual_iters // self.cumulative_iters * self.cumulative_iters)
self.remainder_iters = residual_iters - self.divisible_iters
self.initialized =
True
(1) 從頭訓練
(2) resume 訓練
_torch.no_grad_
模式執行,並且僅僅儲存輸入引數和 forward 函式,在反向階段重新計算其 forward 輸出值。defforward(self, x):
def_inner_forward(x):
identity = x
out = self.conv1(x)
out = self.norm1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.norm2(out)
if
self.downsample
isnotNone
:
identity = self.downsample(x)
out += identity
return
out
# x.requires_grad 這個判斷很有必要
if
self.with_cp
and
x.requires_grad:
out = cp.checkpoint(_inner_forward, x)
else
:
out = _inner_forward(x)
out = self.relu(out)
return
out
-
模型的第一層不能用 checkpoint 或者說 forward 輸入中不能所有輸入的 requires_grad 屬性都是 False,因為其內部實現是依靠輸入的 requires_grad 屬性來判斷輸出返回是否需要梯度,而通常模型第一層輸入是 image tensor,其 requires_grad 通常是 False。一旦你第一層用了 checkpoint,那麼意味著這個 forward 函式不會有任何梯度,也就是說不會進行任何引數更新,沒有任何使用的必要,具體見 https://discuss.pytorch.org/t/use-of-torch-utils-checkpoint-checkpoint-causes-simple-model-to-diverge/116271。如果第一層用了 checkpoint, PyTorch 會列印 None of the inputs have requires_grad=True. Gradients will be Non
警告 -
對於 dropout 這種 forward 存在隨機性的層,需要保證 preserve_rng_state 為 True (預設就是 True,所以不用擔心),一旦標誌位設定為 True,在 forward 會儲存 RNG 狀態,然後在反向傳播的時候讀取該 RNG,保證兩次 forward 輸出一致。如果你確定不需要儲存 RNG,則可以設定 preserve_rng_state 為 False,省掉一些不必要的執行邏輯 -
其他注意事項,可以參考官方文件 https://pytorch.org/docs/stable/checkpoint.html#
classCheckpointFunction(torch.autograd.Function):
@staticmethod
defforward(ctx, run_function, preserve_rng_state, *args):
# 檢查輸入引數是否需要梯度
check_backward_validity(args)
# 儲存必要的狀態
ctx.run_function = run_function
ctx.save_for_backward(*args)
with
torch.no_grad():
# 以 no_grad 模型執行一遍
outputs = run_function(*args)
return
outputs
@staticmethod
defbackward(ctx, *args):
# 讀取輸入引數
inputs = ctx.saved_tensors
# Stash the surrounding rng state, and mimic the state that was
# present at this time during forward. Restore the surrounding state
# when we're done.
rng_devices = []
with
torch.random.fork_rng(devices=rng_devices, enabled=ctx.preserve_rng_state):
# detach 掉當前不需要考慮的節點
detached_inputs = detach_variable(inputs)
# 重新執行一遍
with
torch.enable_grad():
outputs = ctx.run_function(*detached_inputs)
if
isinstance(outputs, torch.Tensor):
outputs = (outputs,)
# 計算該子圖梯度
torch.autograd.backward(outputs, args)
grads = tuple(inp.grad
if
isinstance(inp, torch.Tensor)
else
inp
for
inp
in
detached_inputs)
return
(
None
,
None
) + grads
顯示卡: GeForce GTX 1660
PyTorch: 1.7.1
CUDA Runtime 10.1
MMCV: 1.3.16
MMDetection: 2.17.0
(1) base
-
資料集:pascal voc -
演算法是 retinanet,對應配置檔案為 retinanet_r50_fpn_1x_voc0712.py -
為了防止 lr 過大導致訓練出現 nan,需要將 lr 設定為 0.01/8=0.00125 -
bs 設定為 2
(2) 混合精度 AMP
fp16 = dict(loss_scale=512.)
(3) 梯度累加
# 累加2次
optimizer_config = dict(
type
=
'GradientCumulativeOptimizerHook'
, cumulative_iters=2)
(4) 梯度檢查點
model = dict(backbone=dict(with_cp=True),
bbox_head=dict(num_classes=20))
配置 | 視訊記憶體佔用(MB) | 訓練時長 |
---|---|---|
base | 2900 | 7 分 45 秒 |
混合精度 AMP | 2243 | 36 分 |
梯度累加 | 3177 | 7 分 32 秒 |
梯度檢查點 | 2590 | 8 分 37 秒 |
-
對比 base 和 AMP 可以發現,由於實驗顯示卡是不支援 AMP 的,故只能節省視訊記憶體,速度會特別慢,如果本身顯示卡支援 AMP 則可以實現在節省視訊記憶體的同時提升訓練速度 -
對比 base 和梯度累加可以發現,在相同 bs 情況下,梯度累加 2 次相當於 bs 擴大一倍,但是視訊記憶體增加不多。如果將 bs 縮小一倍,則可以實現在相同 bs 情況下節省大概一倍視訊記憶體 -
對比 base 和梯度檢查點可以發現,可以節省一定的視訊記憶體,但是訓練時長會增加一些

關於我們
