Files
paddlepaddle--paddle/python/paddle/incubate/optimizer/recompute.py
T
2026-07-13 12:40:42 +08:00

812 lines
34 KiB
Python

# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import paddle
from paddle.base import core, framework, unique_name
from paddle.base.backward import append_backward
from paddle.base.framework import Variable, in_dygraph_mode, program_guard
from paddle.optimizer import Optimizer
class RecomputeOptimizer(Optimizer):
"""
:api_attr: Static Graph
Recompute Optimizer Wrapper
Normally, a training step contains three sub-steps: first, run forward
Operators to calculate the loss; second, run backward Operators to
calculate gradient of the parameters; third, apply optimization method
to update the value of the parameters.
In the forward computation process, all variables that are needed by
backward computation process will be kept in memory, which occupy a great
amount of memory when the network becomes very deep.
Recompute split the network to k segments. In each segment, It will
recompute the forward Operators, before running backward operators. It is
very helpful for saving memory.
The Variables that separate a network to segments are called as checkpoints,
and users should set it manually. The usage is very simple:
Args:
optimizer (Optimizer): The optimizer that is applied to parameters.
Examples:
.. code-block:: pycon
>>> import paddle
>>> import numpy as np
>>> paddle.enable_static()
>>> def gen_data():
... return {
... "x": np.random.random(size=(32, 32)).astype('float32'),
... "y": np.random.randint(2, size=(32, 1)).astype('int64'),
... }
>>> def mlp(input_x, input_y, hid_dim=128, label_dim=2):
... print(input_x)
... fc_1 = paddle.static.nn.fc(x=input_x, size=hid_dim)
... prediction = paddle.static.nn.fc(x=[fc_1], size=label_dim, activation='softmax')
... cost = paddle.nn.functional.cross_entropy(
... input=prediction,
... label=input_y,
... reduction='none',
... use_softmax=False,
... )
... sum_cost = paddle.mean(cost)
... return sum_cost, fc_1, prediction
>>> input_x = paddle.static.data(name="x", shape=[-1, 32], dtype='float32')
>>> input_y = paddle.static.data(name="y", shape=[-1, 1], dtype='int64')
>>> cost, fc_1, pred = mlp(input_x, input_y)
>>> sgd = paddle.optimizer.Adam(learning_rate=0.01)
>>> sgd = paddle.incubate.optimizer.RecomputeOptimizer(sgd)
>>> sgd._set_checkpoints([fc_1, pred])
>>> sgd.minimize(cost)
>>> print("Finished optimize")
Finished optimize
>>> place = paddle.CPUPlace()
>>> exe = paddle.static.Executor(place)
>>> exe.run(paddle.static.default_startup_program())
>>> step = 10
>>> for i in range(step):
... cost_val = exe.run(
... feed=gen_data(),
... program=paddle.static.default_main_program(),
... fetch_list=[cost.name],
... )
... print("step=%d cost=%f" % (i, cost_val[0]))
var x : DENSE_TENSOR.shape(-1, 32).dtype(float32).stop_gradient(True)
Finished optimize
step=0 cost=0.737203
step=1 cost=1.308077
step=2 cost=0.768422
step=3 cost=1.239475
step=4 cost=0.882643
step=5 cost=0.738027
step=6 cost=0.819374
step=7 cost=0.818534
step=8 cost=0.753692
step=9 cost=0.787448
"""
def __init__(self, optimizer):
if in_dygraph_mode():
raise Exception("In dygraph, don't support RecomputeOptimizer.")
self._optimizer = optimizer
self._checkpoints = None
self._learning_rate = self._optimizer._learning_rate
self._learning_rate_map = self._optimizer._learning_rate_map
self.enable_offload = False
def _set_checkpoints(self, checkpoints):
"""
Args:
checkpoints (list): List of Variable or string
"""
assert isinstance(checkpoints, list), (
"_checkpoints should be a list of Variable or a list of String"
)
for ckpt in checkpoints:
assert isinstance(ckpt, (Variable, str)), (
"_checkpoints should be a list of Variable or a list of String"
)
self._checkpoints = checkpoints
# should enable offload before calling backward
def _enable_offload(self):
self.enable_offload = True
@framework.deprecate_stat_dict
def load(self, state_dict):
"""
:api_attr: Static Graph
load function is not supported by Recompute Optimizer for now.
:return: None
Args:
state_dict: the dict load by load_persistable method
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> def mlp(input_x, input_y, hid_dim=128, label_dim=2):
... fc_1 = paddle.static.nn.fc(x=input_x, size=hid_dim)
... prediction = paddle.static.nn.fc(x=[fc_1], size=label_dim, activation='softmax')
... cost = paddle.nn.functional.cross_entropy(
... input=prediction,
... label=input_y,
... reduction='none',
... use_softmax=False,
... )
... sum_cost = paddle.mean(cost)
... return sum_cost, fc_1, prediction
>>> input_x = paddle.static.data(name="x", shape=[-1, 32], dtype='float32')
>>> input_y = paddle.static.data(name="y", shape=[-1, 1], dtype='int64')
>>> cost, fc_1, pred = mlp(input_x, input_y)
>>> print("Finished FF")
Finished FF
>>> sgd = paddle.optimizer.Adam(learning_rate=0.01)
>>> sgd = paddle.incubate.optimizer.RecomputeOptimizer(sgd)
>>> sgd._set_checkpoints([fc_1, pred])
>>> try:
... state_dict = {}
... sgd.load(state_dict)
>>> except NotImplementedError as e:
... print(e)
load function is not supported by Recompute Optimizer for now
"""
raise NotImplementedError(
"load function is not supported by Recompute Optimizer for now"
)
def apply_gradients(self, params_grads):
"""
call apply_gradients function of self._optimizer.
Args:
params_grads (list): list of (param, grad) pair to do optimization.
Returns:
list: A list of operators appended to the current program.
Examples:
.. code-block:: pycon
>>> import paddle
>>> import paddle.base.framework as framework
>>> paddle.enable_static()
>>> def mlp(input_x, input_y, hid_dim=128, label_dim=2):
... fc_1 = paddle.static.nn.fc(x=input_x, size=hid_dim)
... prediction = paddle.static.nn.fc(x=[fc_1], size=label_dim, activation='softmax')
... cost = paddle.nn.functional.cross_entropy(
... input=prediction,
... label=input_y,
... reduction='none',
... use_softmax=False,
... )
... sum_cost = paddle.mean(cost)
... return sum_cost, fc_1, prediction
>>> input_x = paddle.static.data(name="x", shape=[-1, 32], dtype='float32')
>>> input_y = paddle.static.data(name="y", shape=[-1, 1], dtype='int64')
>>> cost, fc_1, pred = mlp(input_x, input_y)
>>> print("Finished FF")
Finished FF
>>> sgd = paddle.optimizer.Adam(learning_rate=0.01)
>>> sgd = paddle.incubate.optimizer.RecomputeOptimizer(sgd)
>>> sgd._set_checkpoints([fc_1, pred])
>>> params_grads = sgd.backward(
... cost,
... startup_program=None,
... parameter_list=None,
... no_grad_set=None,
... )
>>> program = cost.block.program
>>> with framework.program_guard(program, None):
... optimize_ops = sgd.apply_gradients(params_grads)
>>> print("Finished apply gradients")
Finished apply gradients
"""
return self._optimizer.apply_gradients(params_grads=params_grads)
def _create_vars(self, varname):
pinned_var_name = unique_name.generate(varname + "@Pinned")
fetched_var_name = unique_name.generate(varname + "@Fetch")
pinned_var = self._main_program.global_block().create_var(
name=pinned_var_name,
shape=self.checkpoint_shape,
dtype=self._main_program.global_block().var(varname).dtype,
persistable=False,
stop_gradient=True,
)
fetch_var = self._main_program.global_block().create_var(
name=fetched_var_name,
shape=self.checkpoint_shape,
dtype=self._main_program.global_block().var(varname).dtype,
persistable=False,
stop_gradient=False,
)
return pinned_var_name, fetched_var_name
def _append_fill_constant_ops(self, startup_program):
"""
add fill_constant_ops to the end of the prog
we should fill the pinned vars before running the main_prog
to instantiate their tensor hold_, which could tell us whether
the host memory could hold all the checkpoints from all the
GPU devices in this node.
"""
op_role = 0
block = startup_program.global_block()
fill_constant_vars = self.checkpoint_name2pinned_name.values()
OP_ROLE_KEY = core.op_proto_and_checker_maker.kOpRoleAttrName()
for varname in fill_constant_vars:
var = self._main_program.global_block().var(varname)
# NOTE (JZ-LIANG) to pre-allocate the CUDAPinned MEM
pinned_var = block.create_var(
name=varname,
shape=self.checkpoint_shape,
dtype=self._main_program.global_block().var(var.name).dtype,
persistable=False,
stop_gradient=True,
)
block.append_op(
type='fill_constant',
outputs={'Out': varname},
attrs={
"shape": var.shape,
"dtype": var.dtype,
"value": 0.0,
"place_type": 2,
OP_ROLE_KEY: op_role,
},
)
def _insert_async_memcpy_op(
self, insert_idx, src_varname, dst_varname, op_role, dst_place_type
):
OP_ROLE_KEY = core.op_proto_and_checker_maker.kOpRoleAttrName()
self.block._insert_op_without_sync(
insert_idx,
type='memcpy',
inputs={'X': [self._main_program.global_block().var(src_varname)]},
outputs={
'Out': [self._main_program.global_block().var(dst_varname)]
},
attrs={"dst_place_type": int(dst_place_type), OP_ROLE_KEY: op_role},
)
def _insert_fetch_op(self, idx, varname):
assert varname in self.checkpoint_name2pinned_name, (
f"Try to fetch {varname} from Pinned Memory, but it is NOT a checkpoint"
)
pinned_varname = self.checkpoint_name2pinned_name[varname]
fetch_varname = self.checkpoint_name2fetch_name[varname]
self._insert_async_memcpy_op(idx, pinned_varname, fetch_varname, 1, 1)
def _insert_offload_op(self, idx, varname):
assert varname in self.checkpoint_name2pinned_name, (
f"Try to offload {varname} to Pinned Memory, but it is NOT a checkpoint"
)
pinned_varname = self.checkpoint_name2pinned_name[varname]
self._insert_async_memcpy_op(idx, varname, pinned_varname, 0, 2)
def _insert_sync_op(self, op_idx, checkpoint_name):
# single stream offload no need sync
pass
def _record_fetch_op(self, idx):
assert len(self.un_fetch_checkpoint_names) > 0, (
"Could NOT found checkpoint to fetch"
)
checkpoint_name = self.un_fetch_checkpoint_names.pop(-1)
logging.debug(f"Record fetch [{checkpoint_name}]")
self.idx2insertions[idx] = ("fetch", checkpoint_name)
return checkpoint_name
def _record_offload_op(self, idx, checkpoint_name):
expected_checkpoint_name = self.un_offload_checkpoint_names.pop(0)
assert checkpoint_name == expected_checkpoint_name, (
f"expected to offload [{expected_checkpoint_name}] but got [{checkpoint_name}]"
)
logging.debug(f"Record offload [{checkpoint_name}]")
self.idx2insertions[idx] = ("offload", checkpoint_name)
def _record_sync_op(self, idx, checkpoint_name):
assert checkpoint_name not in self.synced_checkpoints, (
f"Try to sync the checkpoint [{checkpoint_name}] twice"
)
self.synced_checkpoints.add(checkpoint_name)
logging.debug(f"Record offload sync [{checkpoint_name}]")
self.idx2insertions[idx] = ("sync", checkpoint_name)
def _parse_backward(self):
self.idx2insertions = {}
# don't offload the last checkpoints, to favor throughput
self.un_fetch_checkpoint_names = self.sorted_checkpoint_names[:]
self.un_fetch_checkpoint_names.pop(-1)
need_fetch_checkpoint_names = self.un_fetch_checkpoint_names[:]
self.checkpoint_usage_count = {}
for checkpoint_name in self.un_fetch_checkpoint_names:
self.checkpoint_usage_count[checkpoint_name] = 0
self.bw_start_op_idx = len(self.block.ops)
for idx, op in enumerate(self.block.ops):
if int(op.desc.attr("op_role")) == 1:
self.bw_start_op_idx = idx
break
assert self.bw_start_op_idx < len(self.block.ops), (
"Could NOT found backward op in prog"
)
# fetch second to last checkpoint at the beginning of BW
fetched_checkpoint_varname = self._record_fetch_op(self.bw_start_op_idx)
last_last_fetch_checkpoint = None
for i, op in enumerate(self.block.ops[self.bw_start_op_idx :]):
idx = self.bw_start_op_idx + i
input_vars = op.desc.input_arg_names()
for input_var in input_vars:
if input_var in need_fetch_checkpoint_names:
if input_var not in self.un_fetch_checkpoint_names:
# fetch the offload checkpoint when the first usage of its previous one
if self.checkpoint_usage_count[input_var] == 0:
# TODO (JZ-LIANG) sync memcpy_stream if extra stream for memcpy
second_to_last_fetch_checkpoint = (
fetched_checkpoint_varname
)
# there is NO fetch ahead the first checkpoint
if input_var != self.sorted_checkpoint_names[0]:
fetched_checkpoint_varname = (
self._record_fetch_op(idx)
)
# should check the current used checkpoint is the last fetch one
assert second_to_last_fetch_checkpoint == input_var, (
f"Current recompute segment should use [{second_to_last_fetch_checkpoint}] BUT got [{input_var}]"
)
# rename
self.block.ops[idx]._rename_input(
input_var,
self.checkpoint_name2fetch_name[input_var],
)
self.checkpoint_usage_count[input_var] += 1
else:
raise ValueError(
f"use checkpoint [{input_var}] before fetch in BW"
)
assert len(self.un_fetch_checkpoint_names) == 0, (
f"{self.un_fetch_checkpoint_names} checkpoints have NOT been Recorded"
)
def _update_backward(self):
if len(self.idx2insertions) == 0:
return
total_op = len(self.block.ops)
for op_idx in reversed(range(self.bw_start_op_idx, total_op)):
if op_idx in self.idx2insertions:
operation, checkpoint_name = self.idx2insertions[op_idx]
if operation == "fetch":
self._insert_fetch_op(op_idx, checkpoint_name)
logging.debug(f"Insert [{checkpoint_name}] fetch op.")
del self.idx2insertions[op_idx]
elif operation == "sync":
self._insert_sync_op(op_idx, checkpoint_name)
logging.debug(f"Sync [{checkpoint_name}] fetch op.")
self.block._sync_with_cpp()
assert len(self.idx2insertions) == 0, (
f"{[ele[1] for ele in self.idx2insertions.values()]} checkpoints left un-Fetched"
)
def _parse_forward(self):
self.idx2insertions = {}
# don't offload the last checkpoints, faster, less memory saving
self.un_offload_checkpoint_names = self.sorted_checkpoint_names[:]
last_checkpoint = self.un_offload_checkpoint_names.pop(-1)
need_offload_checkpoint_names = self.un_offload_checkpoint_names[:]
self.checkpoint_usage_count_and_idx = {}
for checkpoint_name in self.un_offload_checkpoint_names:
self.checkpoint_usage_count_and_idx[checkpoint_name] = {
'count': 0,
'idx': -1,
}
self.synced_checkpoints = set()
self.fw_start_op_idx = len(self.block.ops)
for idx, op in enumerate(self.block.ops):
if int(op.desc.attr("op_role")) == 0:
self.fw_start_op_idx = idx
break
assert self.fw_start_op_idx < len(self.block.ops), (
"Could NOT found Forward op in prog"
)
last_offload_checkpoint = None
for i, op in enumerate(
self.block.ops[self.fw_start_op_idx : self.bw_start_op_idx]
):
idx = self.fw_start_op_idx + i
output_vars = op.desc.output_arg_names()
input_vars = op.desc.input_arg_names()
for output_var in output_vars:
if output_var in need_offload_checkpoint_names:
assert len(output_vars) == 1, (
f"checkpoint should be the only Output of a certain op, but [{output_var}] is from [{op}]"
)
if output_var in self.un_offload_checkpoint_names:
# insert sync op if last checkpoint has not been sync
if last_offload_checkpoint is not None:
if (
self.checkpoint_usage_count_and_idx[
last_offload_checkpoint
]['count']
== 0
):
self._record_sync_op(
idx, last_offload_checkpoint
)
else:
last_usage_idx = (
self.checkpoint_usage_count_and_idx[
last_offload_checkpoint
]['idx']
)
assert last_usage_idx > 0, (
f"last_usage_idx of checkpoint [{last_offload_checkpoint}] should large than 0"
)
self._record_sync_op(
last_usage_idx + 1, last_offload_checkpoint
)
# insert offload op after the checkpoint's generation op
self._record_offload_op(idx + 1, output_var)
last_offload_checkpoint = output_var
else:
raise ValueError(
f"There should be just ONE op that output checkpoint [{output_var}]"
)
# need to sync the last need to offload checkpoint before the last checkpoint as output op
if output_var == last_checkpoint:
assert len(output_vars) == 1, (
f"checkpoint should be the only Output of a certain op, but [{output_var}] is from [{op}]"
)
assert (
last_offload_checkpoint
== self.sorted_checkpoint_names[-2]
), (
f"the last offload checkpoint before [{last_checkpoint}] is suppose to be [{self.sorted_checkpoint_names[-2]}], but got [{last_offload_checkpoint}]"
)
# sync if last checkpoint has not been sync
if (
self.checkpoint_usage_count_and_idx[
last_offload_checkpoint
]['idx']
== 0
):
self._record_sync_op(idx, last_offload_checkpoint)
else:
last_usage_idx = self.checkpoint_usage_count_and_idx[
last_offload_checkpoint
]['idx']
assert last_usage_idx > 0, (
f"last_usage_idx of checkpoint [{last_offload_checkpoint}] should large than 0"
)
self._record_sync_op(
last_usage_idx + 1, last_offload_checkpoint
)
# record checkpoint usage
for input_var in input_vars:
if input_var in need_offload_checkpoint_names:
assert input_var not in self.synced_checkpoints, (
f"checkpoint [{input_var}] used after sync"
)
self.checkpoint_usage_count_and_idx[input_var]['count'] += 1
self.checkpoint_usage_count_and_idx[input_var]['idx'] = idx
assert len(self.un_offload_checkpoint_names) == 0, (
f"{self.un_fetch_checkpoint_names} checkpoints have NOT been Recorded"
)
assert len(self.synced_checkpoints) == len(
need_offload_checkpoint_names
), (
f"{set(need_offload_checkpoint_names) - set(self.synced_checkpoints)} checkpoints have NOT been Recorded"
)
def _update_forward(self):
if len(self.idx2insertions) == 0:
return
for op_idx in reversed(
range(self.fw_start_op_idx, self.bw_start_op_idx)
):
if op_idx in self.idx2insertions:
operation, checkpoint_name = self.idx2insertions[op_idx]
if operation == "offload":
self._insert_offload_op(op_idx, checkpoint_name)
logging.debug(f"Insert [{checkpoint_name}] offload op.")
del self.idx2insertions[op_idx]
elif operation == "sync":
self._insert_sync_op(op_idx, checkpoint_name)
logging.debug(
f"Insert [{checkpoint_name}] offload_sync op."
)
del self.idx2insertions[op_idx]
self.block._sync_with_cpp()
assert len(self.idx2insertions) == 0, (
f"{[ele[1] for ele in self.idx2insertions.values()]} checkpoints left un-Offloaded"
)
def _check_offload_fetch(self):
# TODO(JZ-LIANG) the single stream offload need no sync
pass
def _offload(self, loss, startup_program=None):
"""
core steps for recompute offload
1. create pinned vars and temp vars
2. parse & update Forward pass: offload, sync
3. parse & update Backward pass: rename, fetch, sync
4. verify the correctness
"""
self._main_program = loss.block.program
self.block = loss.block
if startup_program is None:
startup_program = paddle.static.default_startup_program()
with program_guard(self._main_program, startup_program):
assert len(self.checkpoint_shape) > 0, (
f"checkpoints shape {self.checkpoint_shape} should be an non empty list like: [12, 512, 1024]"
)
assert all(ele > 0 for ele in self.checkpoint_shape), (
f"all ele in checkpoints shape {self.checkpoint_shape} should be a determined integer larger than 0"
)
self.checkpoint_name2pinned_name = {}
self.checkpoint_name2fetch_name = {}
for checkpoint_varname in self.sorted_checkpoint_names:
pinned_var_name, fetch_var_name = self._create_vars(
checkpoint_varname
)
self.checkpoint_name2pinned_name[checkpoint_varname] = (
pinned_var_name
)
self.checkpoint_name2fetch_name[checkpoint_varname] = (
fetch_var_name
)
self._append_fill_constant_ops(startup_program)
# TODO (JZ-LIANG) to provide two offload strategy in future
# step 2. parse & update FW: rename, offload, sync
self._parse_backward()
self._update_backward()
# step 3. parse & update BW: rename, offload, sync
self._parse_forward()
self._update_forward()
# step 4. verify the correctness
self._check_offload_fetch()
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
"""
call append_backward with checkpoints.
Args:
loss (Variable): loss variable to run optimizations.
startup_program (Program): startup_program for initializing parameters
in `parameter_list`.
parameter_list (list): list of Variables or Variable.names to update.
no_grad_set (set|None): set of Variables or Variables.names should be ignored.
callbacks (list|None): list of callables to run when appending backward
operator for one parameter.
checkpoints (list): list of Variables as checkpoints
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> def mlp(input_x, input_y, hid_dim=128, label_dim=2):
... fc_1 = paddle.static.nn.fc(x=input_x, size=hid_dim)
... prediction = paddle.static.nn.fc(x=[fc_1], size=label_dim, activation='softmax')
... cost = paddle.nn.functional.cross_entropy(
... input=prediction,
... label=input_y,
... reduction='none',
... use_softmax=False,
... )
... sum_cost = paddle.mean(cost)
... return sum_cost, fc_1, prediction
>>> input_x = paddle.static.data(name="x", shape=[-1, 32], dtype='float32')
>>> input_y = paddle.static.data(name="y", shape=[-1, 1], dtype='int64')
>>> cost, fc_1, pred = mlp(input_x, input_y)
>>> print("Finished FF")
Finished FF
>>> sgd = paddle.optimizer.Adam(learning_rate=0.01)
>>> sgd = paddle.incubate.optimizer.RecomputeOptimizer(sgd)
>>> sgd._set_checkpoints([fc_1, pred])
>>> params_grads = sgd.backward(
... cost,
... startup_program=None,
... parameter_list=None,
... no_grad_set=None,
... )
>>> print("Finished backward")
Finished backward
"""
assert self._checkpoints is not None, (
"You should call _set_checkpoints first"
)
if in_dygraph_mode():
raise NotImplementedError(
"DyGraph current does not support recompute"
)
self._dtype = loss.dtype
program = loss.block.program
with program_guard(program, startup_program):
checkpoint_vars = []
for ckpt in self._checkpoints:
if isinstance(ckpt, Variable):
checkpoint_vars.append(ckpt)
else:
checkpoint_vars.append(loss.block.var(ckpt))
# allow return to non-recompute when checkpoints is empty
if len(checkpoint_vars) > 0:
params_grads, sorted_checkpoint_names = append_backward(
loss,
parameter_list,
no_grad_set,
checkpoints=checkpoint_vars,
)
else:
params_grads = append_backward(
loss,
parameter_list,
no_grad_set,
checkpoints=checkpoint_vars,
)
if self.enable_offload:
self.sorted_checkpoint_names = sorted_checkpoint_names
self._offload(loss, startup_program=startup_program)
return params_grads
def apply_optimize(self, loss, startup_program, params_grads):
"""
call the apply_optimize function of self._optimizer
Args:
loss (Variable): loss variable to run optimizations.
startup_program (Program): startup_program for initializing parameters
in `parameter_list`.
params_grads (list): list of (param, grad) pair to do optimization.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> def mlp(input_x, input_y, hid_dim=128, label_dim=2):
... fc_1 = paddle.static.nn.fc(x=input_x, size=hid_dim)
... prediction = paddle.static.nn.fc(x=[fc_1], size=label_dim, activation='softmax')
... cost = paddle.nn.functional.cross_entropy(
... input=prediction,
... label=input_y,
... reduction='none',
... use_softmax=False,
... )
... sum_cost = paddle.mean(cost)
... return sum_cost, fc_1, prediction
>>> input_x = paddle.static.data(name="x", shape=[-1, 32], dtype='float32')
>>> input_y = paddle.static.data(name="y", shape=[-1, 1], dtype='int64')
>>> cost, fc_1, pred = mlp(input_x, input_y)
>>> print("Finished FF")
Finished FF
>>> sgd = paddle.optimizer.Adam(learning_rate=0.01)
>>> sgd = paddle.incubate.optimizer.RecomputeOptimizer(sgd)
>>> sgd._set_checkpoints([fc_1, pred])
>>> params_grads = sgd.backward(
... cost,
... startup_program=None,
... parameter_list=None,
... no_grad_set=None,
... )
>>> optimize_ops = sgd.apply_optimize(
... cost,
... startup_program=None,
... params_grads=params_grads,
... )
>>> print("Finished apply_optimize")
Finished apply_optimize
"""
func = (
self._optimizer.apply_optimize
if hasattr(self._optimizer, 'apply_optimize')
else self._optimizer._apply_optimize
)
return func(
loss, startup_program=startup_program, params_grads=params_grads
)
def minimize(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
assert isinstance(loss, Variable), "The loss should be an Variable."
assert self._checkpoints is not None, (
"You should call _set_checkpoints first"
)
if in_dygraph_mode():
raise NotImplementedError(
"DyGraph current does not support recompute"
)
params_grads = self.backward(
loss,
startup_program=startup_program,
parameter_list=parameter_list,
no_grad_set=no_grad_set,
)
optimize_ops = self.apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
return optimize_ops, params_grads