chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,722 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
# ruff: noqa: F401
|
||||
"""Provide abstraction for defining optimizers and a set of common optimizers."""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Union
|
||||
|
||||
import numpy as np # type: ignore
|
||||
import tvm_ffi
|
||||
|
||||
import tvm
|
||||
|
||||
from ..block_builder import BlockBuilder
|
||||
from ..expr import Function, TupleGetItem, Var, const
|
||||
from ..expr import Tuple as RxTuple
|
||||
from ..op import add, divide, multiply, sqrt, subtract
|
||||
from ..type import TensorType, TupleType
|
||||
|
||||
|
||||
# TODO(chaofan, yixin): Migrate key logics to C++
|
||||
class Optimizer:
|
||||
"""Relax training optimizer. This class could generate relax Functions for optimizing specified
|
||||
parameters, and store the states used in the optimization process, such as momentum.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the optimizer function. This parameter is provided by subclasses.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
dtype : str
|
||||
The only dtype of the optimizer. It will be used as the dtype of the optimizer states,
|
||||
and the dtype of necessary constants, such as the learning rate. Will be set in `init()`.
|
||||
|
||||
name : str
|
||||
The name of the optimizer.
|
||||
|
||||
param_list : List[Var]
|
||||
The list of variables to optimize. Will be set in `init()`.
|
||||
|
||||
state : tvm_ffi.Array
|
||||
`state` is an runtime Array representing the state of the optimizer. Will be set in
|
||||
`init()`.
|
||||
|
||||
The states of the optimizer can store necessary information in the optimization process at
|
||||
runtime, such as the number of steps, the momentum in momentum SGD, etc.
|
||||
|
||||
`opt.state` should be used as the last argument of the function that is got through
|
||||
`get_function()`, and its new value is returned as the last return value of that function.
|
||||
|
||||
See examples for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
The usage of optimizers should resemble the following pattern. We will take SGD as an example.
|
||||
For detailed examples, please see the tutorial.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Construct the optimizer
|
||||
opt = relax.optimizer.SGD(0.1)
|
||||
|
||||
# Initialize the parameter list, the dtype and the optimizer state
|
||||
# x is the relax Var we want to optimize
|
||||
opt.init(x)
|
||||
|
||||
# The above two lines is equivalent to one line:
|
||||
opt = relax.optimizer.SGD(0.1).init(x)
|
||||
|
||||
# Get the optimizer function
|
||||
# mod is an IRModule constructed earlier
|
||||
mod["SGD"] = opt.get_function()
|
||||
|
||||
# Legalize and build mod
|
||||
lowered_mod = LegalizeOps()(mod)
|
||||
ex = build(lowered_mod, target="llvm")
|
||||
vm = VirtualMachine(ex, tvm.cpu())
|
||||
|
||||
# Optimization process
|
||||
# param_tuple is a runtime tuple of parameters
|
||||
# param_gradient is a runtime tuple of the gradient of the parameters in param_tuple,
|
||||
# respectively
|
||||
# param_gradient can be gained by the automatic differentiation pass. Please see
|
||||
# `relax.transform.Gradient`
|
||||
param_tuple, opt.state = vm["SGD"](param_tuple, param_gradient, opt.state)
|
||||
"""
|
||||
|
||||
dtype: str
|
||||
name: str
|
||||
param_list: list[Var]
|
||||
state: tvm_ffi.Array
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
self.param_list = None
|
||||
self.state = None
|
||||
self.dtype = None
|
||||
|
||||
def init(self, params: Var | list[Var]) -> "Optimizer":
|
||||
"""Set the parameters, determine the dtype, and construct the initial state for the
|
||||
optimizer.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params : Union[Var, List[Var]]
|
||||
The parameter or the list of parameters to optimize.
|
||||
|
||||
Parameters should all be Vars of floating point Tensors, including float32, float64,
|
||||
float16, etc. Currently, all parameters should have the same dtype, and that dtype
|
||||
will be used as the dtype of the optimizer states.
|
||||
|
||||
Returns
|
||||
-------
|
||||
self : Optimizer
|
||||
The optimizer itself.
|
||||
"""
|
||||
if not isinstance(params, list):
|
||||
params = [params]
|
||||
self._set_params_and_dtype(params)
|
||||
# State should be initialized in any implementation of optimizer.
|
||||
self.state = None
|
||||
return self
|
||||
|
||||
def _set_params_and_dtype(self, params: list[Var]) -> None:
|
||||
"""Check params is legal and set the param_list and dtype of the optimizer."""
|
||||
params_set = set()
|
||||
dtype = None
|
||||
for x in params:
|
||||
if not isinstance(x, Var):
|
||||
raise ValueError(f"Parameter {x} is not a Var")
|
||||
if not isinstance(x.ty, TensorType):
|
||||
raise ValueError(
|
||||
f"Optimizers only support Tensor parameters, but parameter {x.name_hint} has "
|
||||
f"type {x.ty}"
|
||||
)
|
||||
data_type = tvm.DataType(x.ty.dtype.dtype)
|
||||
if data_type.type_code not in (tvm.DataTypeCode.BFLOAT, tvm.DataTypeCode.FLOAT):
|
||||
raise ValueError(
|
||||
f"Optimizers only support Tensor parameters of floating point dtype, but dtype "
|
||||
f"of {x.name_hint} is {x.ty.dtype}"
|
||||
)
|
||||
if dtype is None:
|
||||
dtype = x.ty.dtype
|
||||
else:
|
||||
if dtype != x.ty.dtype:
|
||||
raise ValueError(
|
||||
f"All parameters should have the same dtype, but parameter {x.name_hint} "
|
||||
f"has dtype {x.ty.dtype}, which differs from the previous dtype "
|
||||
f"{dtype}"
|
||||
)
|
||||
if x in params_set:
|
||||
raise ValueError(f"Parameter {x.name_hint} appears more than once")
|
||||
params_set.add(x)
|
||||
self.param_list = params
|
||||
self.dtype = dtype
|
||||
|
||||
def _check_init(self):
|
||||
"""Check that the optimizer is initialized. This method should be called at the start of
|
||||
get_function().
|
||||
"""
|
||||
if self.param_list is None or self.state is None or self.dtype is None:
|
||||
raise RuntimeError("Please call init() for the optimizer before calling get_function()")
|
||||
|
||||
def get_function(self) -> Function:
|
||||
"""Use blockbuilder to construct an optimizer function that executes updates of the
|
||||
parameters and the optimizer state.
|
||||
|
||||
The optimizer function will take in a tuple of parameters, a tuple of gradients of
|
||||
parameters, and a tuple of optimizer states. It will return a tuple of updated parameters,
|
||||
and a tuple of optimizer states.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func : Function
|
||||
The optimizer function.
|
||||
|
||||
Examples
|
||||
--------
|
||||
An example of the returned optimizer function. This function executes the stochastic
|
||||
gradient descent method with lr = 0.1.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@R.function
|
||||
def SGD(
|
||||
params: R.Tuple(R.Tensor((3, 3), "float32"), R.Tensor((3,), "float32")),
|
||||
gradients: R.Tuple(R.Tensor((3, 3), "float32"), R.Tensor((3,), "float32")),
|
||||
optim_states: R.Tuple(R.Tensor((), "int64")),
|
||||
) -> R.Tuple(
|
||||
R.Tuple(R.Tensor((3, 3), "float32"), R.Tensor((3,), "float32")),
|
||||
R.Tuple(R.Tensor((), "int64")),
|
||||
):
|
||||
with R.dataflow():
|
||||
num_steps: R.Tensor((), "int64") = optim_states[0]
|
||||
num_steps_new: R.Tensor((), "int64") = R.add(num_steps, R.const(1, "int64"))
|
||||
x: R.Tensor((3, 3), "float32") = params[0]
|
||||
x_grad: R.Tensor((3, 3), "float32") = gradients[0]
|
||||
lv: R.Tensor((3, 3), "float32") = R.multiply(R.const(0.01, "float32"), x_grad)
|
||||
x_new: R.Tensor((3, 3), "float32") = R.subtract(x, lv)
|
||||
y: R.Tensor((3,), "float32") = params[1]
|
||||
y_grad: R.Tensor((3,), "float32") = gradients[1]
|
||||
lv1: R.Tensor((3,), "float32") = R.multiply(R.const(0.01, "float32"), y_grad)
|
||||
y_new: R.Tensor((3,), "float32") = R.subtract(y, lv1)
|
||||
params_new: R.Tuple(R.Tensor((3, 3), "float32"), R.Tensor((3,), "float32")) = (
|
||||
x_new,
|
||||
y_new,
|
||||
)
|
||||
optim_states_new: R.Tuple(R.Tensor((), "int64")) = (num_steps_new,)
|
||||
R.output(params_new, optim_states_new)
|
||||
return (params_new, optim_states_new)
|
||||
"""
|
||||
self._check_init()
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
# TODO(chaofan, yixin): Support symbolic shapes
|
||||
def _get_shape_as_int_list(var: Var) -> list[int]:
|
||||
return [int(val) for val in var.ty.shape]
|
||||
|
||||
|
||||
# We need to subtract on hyperparameters, but do not want to introduce floating point error.
|
||||
# Floating point error would lead to a few problems, such as making assert_structural_equal not
|
||||
# pass in unit tests
|
||||
def _high_precision_subtract(lhs: float, rhs: float) -> float:
|
||||
return float(Decimal(str(lhs)) - Decimal(str(rhs)))
|
||||
|
||||
|
||||
class SGD(Optimizer):
|
||||
"""Implements stochastic gradient descent.
|
||||
|
||||
The returned function of `get_function()` is equivalent to the following numpy code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def SGD(param_tuple, grad_tuple, state_tuple):
|
||||
num_steps = state_tuple[0]
|
||||
param_tuple_new, state_tuple_new = [], []
|
||||
state_tuple_new.append(num_steps + 1)
|
||||
for i in range(len(param_tuple)):
|
||||
param = param_tuple[i]
|
||||
grad = grad_tuple[i]
|
||||
param_tuple_new.append(param - lr * (grad + weight_decay * param))
|
||||
return param_tuple_new, state_tuple_new
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lr : float
|
||||
learning rate
|
||||
|
||||
weight_decay : float
|
||||
weight decay (L2 penalty) (default: 0)
|
||||
"""
|
||||
|
||||
def __init__(self, lr: float, weight_decay: float = 0) -> None:
|
||||
super().__init__("SGD")
|
||||
self.lr = float(lr)
|
||||
self.weight_decay = float(weight_decay)
|
||||
|
||||
def init(self, params: Var | list[Var]) -> "SGD":
|
||||
"""Set the parameters, determine the dtype, and construct the initial state for the
|
||||
optimizer.
|
||||
|
||||
The state of SGD is `(num_steps,)`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params : Union[Var, List[Var]]
|
||||
The parameter or the list of parameters to optimize.
|
||||
|
||||
Parameters should all be Vars of floating point Tensors, including float32, float64,
|
||||
float16, etc. Currently, all parameters should have the same dtype, and that dtype
|
||||
will be used as the dtype of the optimizer states.
|
||||
|
||||
Returns
|
||||
-------
|
||||
self : SGD
|
||||
The SGD optimizer itself.
|
||||
"""
|
||||
if not isinstance(params, list):
|
||||
params = [params]
|
||||
self._set_params_and_dtype(params)
|
||||
self.state = (
|
||||
# num_steps = 0
|
||||
tvm.runtime.tensor(np.zeros((), "int64")),
|
||||
)
|
||||
return self
|
||||
|
||||
def get_function(self) -> Function:
|
||||
"""Use blockbuilder to construct an optimizer function that executes updates of the
|
||||
parameters and the optimizer state. `init()` should be called before `get_function()`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func : Function
|
||||
The optimizer function.
|
||||
"""
|
||||
self._check_init()
|
||||
|
||||
plist = self.param_list
|
||||
len_param = len(plist)
|
||||
dtype = self.dtype
|
||||
|
||||
# input variables
|
||||
param_var = Var("params", TupleType([p.ty for p in plist]))
|
||||
grad_var = Var("gradients", TupleType([p.ty for p in plist]))
|
||||
state_var = Var("optim_states", TupleType([TensorType((), "int64")]))
|
||||
|
||||
# constants
|
||||
lr = const(self.lr, dtype)
|
||||
weight_decay = const(self.weight_decay, dtype)
|
||||
one = const(1, "int64")
|
||||
|
||||
builder = BlockBuilder()
|
||||
with builder.function(self.name, [param_var, grad_var, state_var]):
|
||||
with builder.dataflow():
|
||||
param_list_new, state_list_new = [], []
|
||||
|
||||
# handle num_steps
|
||||
num_steps = builder.emit(TupleGetItem(state_var, 0), "num_steps")
|
||||
num_steps_new = builder.emit(add(num_steps, one), "num_steps_new")
|
||||
state_list_new.append(num_steps_new)
|
||||
|
||||
# computation logics
|
||||
for i in range(len_param):
|
||||
name = self.param_list[i].name_hint
|
||||
p = builder.emit(TupleGetItem(param_var, i), name)
|
||||
g = builder.emit(TupleGetItem(grad_var, i), name + "_grad")
|
||||
if self.weight_decay:
|
||||
g = builder.emit(add(multiply(weight_decay, p), g), name + "_grad_new")
|
||||
p_new = builder.emit(subtract(p, multiply(lr, g)), name + "_new")
|
||||
param_list_new.append(p_new)
|
||||
|
||||
# handle return values
|
||||
params_new = builder.emit_output(RxTuple(param_list_new), "params_new")
|
||||
optim_states_new = builder.emit_output(RxTuple(state_list_new), "optim_states_new")
|
||||
builder.emit_func_output((params_new, optim_states_new))
|
||||
return builder.get()[self.name]
|
||||
|
||||
|
||||
class MomentumSGD(Optimizer):
|
||||
"""Implements stochastic gradient descent with momentum. Optionally supports Nesterov
|
||||
momentum.
|
||||
|
||||
The returned function of `get_function()` is equivalent to the following numpy code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def MomentumSGD(param_tuple, grad_tuple, state_tuple):
|
||||
num_steps = state_tuple[0]
|
||||
param_tuple_new, state_tuple_new = [], []
|
||||
state_tuple_new.append(num_steps + 1)
|
||||
|
||||
for i in range(len(param_tuple)):
|
||||
param = param_tuple[i]
|
||||
grad = grad_tuple[i]
|
||||
velocity = state_tuple[i + 1]
|
||||
grad = param * weight_decay + grad
|
||||
velocity = momentum * velocity + grad * (1 - dampening)
|
||||
if nesterov:
|
||||
param = param - (grad + momentum * velocity) * lr
|
||||
else:
|
||||
param = param - velocity * lr
|
||||
param_tuple_new.append(param)
|
||||
state_tuple_new.append(velocity)
|
||||
|
||||
return param_tuple_new, state_tuple_new
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lr : float
|
||||
learning rate
|
||||
|
||||
momentum : float
|
||||
momentum factor (default: 0)
|
||||
|
||||
weight_decay : float
|
||||
weight decay (L2 penalty) (default: 0)
|
||||
|
||||
dampening : float
|
||||
dampening for momentum (default: 0)
|
||||
|
||||
nesterov : bool
|
||||
enables Nesterov momentum (default: False)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
lr: float,
|
||||
momentum: float,
|
||||
dampening: float = 0,
|
||||
weight_decay: float = 0,
|
||||
nesterov: bool = False,
|
||||
) -> None:
|
||||
super().__init__("MomentumSGD")
|
||||
self.lr = float(lr)
|
||||
self.momentum = float(momentum)
|
||||
self.weight_decay = float(weight_decay)
|
||||
self.dampening = float(dampening)
|
||||
self.nesterov = nesterov
|
||||
|
||||
def init(self, params: Var | list[Var]) -> "MomentumSGD":
|
||||
"""Set the parameters, determine the dtype, and construct the initial state for the
|
||||
optimizer.
|
||||
|
||||
The state of MomentumSGD is
|
||||
`(num_steps, velocity_of_param_0, ..., velocity_of_param_n-1)`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params : Union[Var, List[Var]]
|
||||
The parameter or the list of parameters to optimize.
|
||||
|
||||
Parameters should all be Vars of floating point Tensors, including float32, float64,
|
||||
float16, etc. Currently, all parameters should have the same dtype, and that dtype
|
||||
will be used as the dtype of the optimizer states.
|
||||
|
||||
Returns
|
||||
-------
|
||||
self : MomentumSGD
|
||||
The MomentumSGD optimizer itself.
|
||||
"""
|
||||
if not isinstance(params, list):
|
||||
params = [params]
|
||||
self._set_params_and_dtype(params)
|
||||
self.state = (
|
||||
# num_steps = 0
|
||||
tvm.runtime.tensor(np.zeros((), "int64")),
|
||||
# v_{param} is initialized to all zeros
|
||||
*(
|
||||
tvm.runtime.tensor(np.zeros(_get_shape_as_int_list(p), p.ty.dtype.dtype))
|
||||
for p in self.param_list
|
||||
),
|
||||
)
|
||||
return self
|
||||
|
||||
def get_function(self) -> Function:
|
||||
"""Use blockbuilder to construct an optimizer function that executes updates of the
|
||||
parameters and the optimizer state. `init()` should be called before `get_function()`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func : Function
|
||||
The optimizer function.
|
||||
"""
|
||||
self._check_init()
|
||||
plist = self.param_list
|
||||
len_param = len(plist)
|
||||
dtype = self.dtype
|
||||
|
||||
# input variables
|
||||
param_var = Var("params", TupleType([p.ty for p in plist]))
|
||||
grad_var = Var("gradients", TupleType([p.ty for p in plist]))
|
||||
state_var = Var(
|
||||
"optim_states",
|
||||
TupleType([TensorType((), "int64"), *(p.ty for p in plist)]),
|
||||
)
|
||||
|
||||
# constants
|
||||
lr = const(self.lr, dtype)
|
||||
momentum = const(self.momentum, dtype)
|
||||
weight_decay = const(self.weight_decay, dtype)
|
||||
dampening_inv = const(_high_precision_subtract(1, self.dampening), dtype)
|
||||
one = const(1, "int64")
|
||||
|
||||
builder = BlockBuilder()
|
||||
with builder.function(self.name, [param_var, grad_var, state_var]):
|
||||
with builder.dataflow():
|
||||
param_list_new, state_list_new = [], []
|
||||
|
||||
# handle num_steps
|
||||
num_steps = builder.emit(TupleGetItem(state_var, 0), "num_steps")
|
||||
num_steps_new = builder.emit(add(num_steps, one), "num_steps_new")
|
||||
state_list_new.append(num_steps_new)
|
||||
|
||||
# computation logics
|
||||
for i in range(len_param):
|
||||
name = self.param_list[i].name_hint
|
||||
p = builder.emit(TupleGetItem(param_var, i), name)
|
||||
g = builder.emit(TupleGetItem(grad_var, i), name + "_grad")
|
||||
v = builder.emit(TupleGetItem(state_var, i + 1), name + "_v")
|
||||
if self.weight_decay:
|
||||
g = builder.emit(add(multiply(weight_decay, p), g), name + "_grad_new")
|
||||
damp_g = multiply(dampening_inv, g) if self.dampening else g
|
||||
v_new = builder.emit(add(multiply(momentum, v), damp_g), name + "_v_new")
|
||||
g_new = (
|
||||
builder.emit(add(g, multiply(momentum, v_new)), name + "_g_nest")
|
||||
if self.nesterov
|
||||
else v_new
|
||||
)
|
||||
p_new = builder.emit(subtract(p, multiply(lr, g_new)), name + "_new")
|
||||
param_list_new.append(p_new)
|
||||
state_list_new.append(v_new)
|
||||
|
||||
# handle return values
|
||||
params_new = builder.emit_output(RxTuple(param_list_new), "params_new")
|
||||
optim_states_new = builder.emit_output(RxTuple(state_list_new), "optim_states_new")
|
||||
builder.emit_func_output((params_new, optim_states_new))
|
||||
return builder.get()[self.name]
|
||||
|
||||
|
||||
class Adam(Optimizer):
|
||||
"""Implements Adam optimization algorithm.
|
||||
|
||||
The returned function of `get_function()` is equivalent to the following numpy code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def Adam(param_tuple, grad_tuple, state_tuple):
|
||||
num_steps = state_tuple[0]
|
||||
num_steps_new = num_steps + 1
|
||||
|
||||
param_tuple_new = []
|
||||
state_tuple_new = [None] * len(state_tuple)
|
||||
state_tuple_new[0] = num_steps_new
|
||||
state_tuple_new[1] = state_tuple[1] * betas[0]
|
||||
state_tuple_new[2] = state_tuple[2] * betas[1]
|
||||
|
||||
for i in range(len(param_tuple)):
|
||||
param = param_tuple[i]
|
||||
grad = grad_tuple[i]
|
||||
m = state_tuple[i + 3]
|
||||
v = state_tuple[i + 3 + len(param_tuple)]
|
||||
grad = grad + weight_decay * param
|
||||
m = betas[0] * m + (1 - betas[0]) * grad
|
||||
v = betas[1] * v + (1 - betas[1]) * grad * grad
|
||||
m_hat = m / (1 - betas[0] ** num_steps_new)
|
||||
v_hat = v / (1 - betas[1] ** num_steps_new)
|
||||
param = param - lr * m_hat / (np.sqrt(v_hat) + eps)
|
||||
param_tuple_new.append(param)
|
||||
state_tuple_new[i + 3] = m
|
||||
state_tuple_new[i + 3 + len(param_tuple)] = v
|
||||
|
||||
return param_tuple_new, state_tuple_new
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lr : float
|
||||
learning rate
|
||||
|
||||
betas : Tuple[float, float]
|
||||
coefficients used for computing running averages of gradient and its square
|
||||
(default: (0.9, 0.999))
|
||||
|
||||
eps : float
|
||||
term added to the denominator to improve numerical stability (default: 1e-8)
|
||||
|
||||
weight_decay : float
|
||||
weight decay (L2 penalty) (default: 0)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
lr: float,
|
||||
betas: tuple[float, float] = (0.9, 0.999),
|
||||
eps: float = 1e-08,
|
||||
weight_decay: float = 0,
|
||||
) -> None:
|
||||
super().__init__("Adam")
|
||||
self.lr = float(lr)
|
||||
self.beta1 = float(betas[0])
|
||||
self.beta2 = float(betas[1])
|
||||
self.eps = float(eps)
|
||||
self.weight_decay = float(weight_decay)
|
||||
|
||||
def init(self, params: Var | list[Var]) -> "Adam":
|
||||
"""Set the parameters, determine the dtype, and construct the initial state for the
|
||||
optimizer.
|
||||
|
||||
The state of Adam is
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
(
|
||||
num_steps,
|
||||
beta_0_prod, # beta0 ** num_steps
|
||||
beta_1_prod, # beta1 ** num_steps
|
||||
first_momentum_of_param_0, ..., first_momentum_of_param_n-1,
|
||||
second_momentum_of_param_0, ..., second_momentum_of_param_n-1
|
||||
)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params : Union[Var, List[Var]]
|
||||
The parameter or the list of parameters to optimize.
|
||||
|
||||
Parameters should all be Vars of floating point Tensors, including float32, float64,
|
||||
float16, etc. Currently, all parameters should have the same dtype, and that dtype
|
||||
will be used as the dtype of the optimizer states.
|
||||
|
||||
Returns
|
||||
-------
|
||||
self : Adam
|
||||
The Adam optimizer itself.
|
||||
"""
|
||||
if not isinstance(params, list):
|
||||
params = [params]
|
||||
self._set_params_and_dtype(params)
|
||||
self.state = (
|
||||
# num_steps, beta_0_prod, beta_1_prod
|
||||
tvm.runtime.tensor(np.zeros((), "int64")),
|
||||
tvm.runtime.tensor(np.ones((), self.dtype)),
|
||||
tvm.runtime.tensor(np.ones((), self.dtype)),
|
||||
# first_momentum
|
||||
*(
|
||||
tvm.runtime.tensor(np.zeros(_get_shape_as_int_list(p), p.ty.dtype.dtype))
|
||||
for p in self.param_list
|
||||
),
|
||||
# second_momentum
|
||||
*(
|
||||
tvm.runtime.tensor(np.zeros(_get_shape_as_int_list(p), p.ty.dtype.dtype))
|
||||
for p in self.param_list
|
||||
),
|
||||
)
|
||||
return self
|
||||
|
||||
def get_function(self) -> Function:
|
||||
"""Use blockbuilder to construct an optimizer function that executes updates of the
|
||||
parameters and the optimizer state. `init()` should be called before `get_function()`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func : Function
|
||||
The optimizer function.
|
||||
"""
|
||||
self._check_init()
|
||||
plist = self.param_list
|
||||
len_param = len(plist)
|
||||
dtype = self.dtype
|
||||
|
||||
# input variables
|
||||
param_var = Var("params", TupleType([p.ty for p in plist]))
|
||||
grad_var = Var("gradients", TupleType([p.ty for p in plist]))
|
||||
state_var = Var(
|
||||
"optim_states",
|
||||
TupleType(
|
||||
[
|
||||
TensorType((), "int64"),
|
||||
TensorType((), dtype),
|
||||
TensorType((), dtype),
|
||||
*(p.ty for p in plist),
|
||||
*(p.ty for p in plist),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
# constants
|
||||
lr = const(self.lr, dtype)
|
||||
beta1 = const(self.beta1, dtype)
|
||||
beta2 = const(self.beta2, dtype)
|
||||
beta1_inv = const(_high_precision_subtract(1, self.beta1), dtype)
|
||||
beta2_inv = const(_high_precision_subtract(1, self.beta2), dtype)
|
||||
eps = const(self.eps, dtype)
|
||||
weight_decay = const(self.weight_decay, dtype)
|
||||
one_int = const(1, "int64")
|
||||
one_float = const(1, dtype)
|
||||
|
||||
builder = BlockBuilder()
|
||||
with builder.function(self.name, [param_var, grad_var, state_var]):
|
||||
with builder.dataflow():
|
||||
param_list_new = []
|
||||
state_list_new = [None] * (len_param * 2 + 3) # type: List[Optional[Var]]
|
||||
|
||||
# handle num_steps
|
||||
num_steps = builder.emit(TupleGetItem(state_var, 0), "num_steps")
|
||||
num_steps_new = builder.emit(add(num_steps, one_int), "num_steps_new")
|
||||
state_list_new[0] = num_steps_new
|
||||
beta1_prod = builder.emit(multiply(TupleGetItem(state_var, 1), beta1), "beta1_prod")
|
||||
beta2_prod = builder.emit(multiply(TupleGetItem(state_var, 2), beta2), "beta2_prod")
|
||||
state_list_new[1] = beta1_prod
|
||||
state_list_new[2] = beta2_prod
|
||||
|
||||
# computation logics
|
||||
for i in range(len_param):
|
||||
name = self.param_list[i].name_hint
|
||||
p = builder.emit(TupleGetItem(param_var, i), name)
|
||||
g = builder.emit(TupleGetItem(grad_var, i), name + "_grad")
|
||||
m = builder.emit(TupleGetItem(state_var, i + 3), name + "_m")
|
||||
v = builder.emit(TupleGetItem(state_var, i + 3 + len_param), name + "_v")
|
||||
if self.weight_decay:
|
||||
g = builder.emit(add(multiply(weight_decay, p), g), name + "_grad_new")
|
||||
m_new = builder.emit(
|
||||
add(multiply(beta1, m), multiply(beta1_inv, g)), name + "_m_new"
|
||||
)
|
||||
v_new = builder.emit(
|
||||
add(multiply(beta2, v), multiply(beta2_inv, multiply(g, g))),
|
||||
name + "_v_new",
|
||||
)
|
||||
m_hat = builder.emit(
|
||||
divide(m_new, subtract(one_float, state_list_new[1])), name + "_m_hat"
|
||||
)
|
||||
v_hat = builder.emit(
|
||||
divide(v_new, subtract(one_float, state_list_new[2])), name + "_v_hat"
|
||||
)
|
||||
p_new = builder.emit(
|
||||
subtract(p, multiply(lr, divide(m_hat, add(sqrt(v_hat), eps)))),
|
||||
name + "_new",
|
||||
)
|
||||
param_list_new.append(p_new)
|
||||
state_list_new[i + 3] = m_new
|
||||
state_list_new[i + 3 + len_param] = v_new
|
||||
|
||||
# handle return values
|
||||
params_new = builder.emit_output(RxTuple(param_list_new), "params_new")
|
||||
optim_states_new = builder.emit_output(RxTuple(state_list_new), "optim_states_new")
|
||||
builder.emit_func_output((params_new, optim_states_new))
|
||||
return builder.get()[self.name]
|
||||
Reference in New Issue
Block a user