src/modules/optim/adam.cc
Namespaces
Source code
// Copyright 2026 The Tenzr Project Authors
//
// SPDX-License-Identifier: Apache-2.0
//
// 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
//
// https://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.
#include "tenzr/modules/optim/adam.h"
#include <algorithm>
#include <cmath>
#include <vector>
#include "tenzr/common/error.h"
namespace tenzr::optim {
Adam::Adam(double lr, double beta1, double beta2, double eps, double weight_decay, bool amsgrad, bool decoupled_weight_decay, ScheduleFn schedule)
: lr_(lr),
beta1_(beta1),
beta2_(beta2),
eps_(eps),
weight_decay_(weight_decay),
amsgrad_(amsgrad),
decoupled_weight_decay_(decoupled_weight_decay),
schedule_(std::move(schedule)) {
TENZR_CHECK(lr >= 0.0, "Learning rate must be non-negative");
TENZR_CHECK(beta1 >= 0.0 && beta1 < 1.0, "Beta1 must be in [0, 1)");
TENZR_CHECK(beta2 >= 0.0 && beta2 < 1.0, "Beta2 must be in [0, 1)");
TENZR_CHECK(eps > 0.0, "Eps must be positive");
}
OptState Adam::Init(const std::unordered_map<std::string, Tensor>& params) {
OptState state;
state.step = 0;
for (const auto& [name, p] : params) {
state.param_states[name]["m"] = Tensor::Zeros(p.shape(), p.Dtype());
state.param_states[name]["v"] = Tensor::Zeros(p.shape(), p.Dtype());
if (amsgrad_) {
state.param_states[name]["v_hat_max"] = Tensor::Zeros(p.shape(), p.Dtype());
}
}
return state;
}
std::unordered_map<std::string, Tensor> Adam::Update(const std::unordered_map<std::string, Tensor>& grads, OptState& state,
const std::unordered_map<std::string, Tensor>& params) {
uint64_t step = state.step + 1;
double current_lr = schedule_ ? schedule_(state.step) : lr_;
double bc1 = 1.0 - std::pow(beta1_, static_cast<double>(step));
double bc2 = 1.0 - std::pow(beta2_, static_cast<double>(step));
double lr_d = current_lr;
double b1_d = beta1_;
double b2_d = beta2_;
double eps_d = eps_;
double wd_d = weight_decay_;
double bc1_d = bc1;
double bc2_sqrt_d = std::sqrt(bc2);
std::unordered_map<std::string, Tensor> updates;
for (const auto& [name, g] : grads) {
auto p_it = params.find(name);
TENZR_CHECK(p_it != params.end(), "Gradient key not found in parameters: " + name);
const auto& p = p_it->second;
// A lazy gradient is consumed here (to_vector materializes it).
if (!p.HasData()) {
updates[name] = g;
continue;
}
auto g_raw = g.ToVectorAs<double>();
auto p_raw = p.ToVectorAs<double>();
size_t n = g_raw.size();
auto& m_tensor = state.param_states[name]["m"];
auto& v_tensor = state.param_states[name]["v"];
auto m_raw = m_tensor.ToVectorAs<double>();
auto v_raw = v_tensor.ToVectorAs<double>();
std::vector<double> v_hat_max_raw;
if (amsgrad_) {
v_hat_max_raw = state.param_states[name]["v_hat_max"].ToVectorAs<double>();
}
std::vector<double> u_raw(n);
for (size_t i = 0; i < n; ++i) {
double grad_val = g_raw[i];
if (!decoupled_weight_decay_ && wd_d > 0.0) {
grad_val += wd_d * p_raw[i];
}
// Update moments
m_raw[i] = b1_d * m_raw[i] + (1.0 - b1_d) * grad_val;
v_raw[i] = b2_d * v_raw[i] + (1.0 - b2_d) * (grad_val * grad_val);
double v_denom;
if (amsgrad_) {
v_hat_max_raw[i] = std::max(v_hat_max_raw[i], v_raw[i]);
v_denom = (std::sqrt(v_hat_max_raw[i]) / bc2_sqrt_d) + eps_d;
} else {
v_denom = (std::sqrt(v_raw[i]) / bc2_sqrt_d) + eps_d;
}
double m_hat = m_raw[i] / bc1_d;
double step_val = -lr_d * (m_hat / v_denom);
if (decoupled_weight_decay_ && wd_d > 0.0) {
step_val -= lr_d * wd_d * p_raw[i];
}
u_raw[i] = step_val;
}
m_tensor.BindData(Tensor::FromVector<double>(m_raw, m_tensor.shape()).Astype(m_tensor.Dtype()).Data());
v_tensor.BindData(Tensor::FromVector<double>(v_raw, v_tensor.shape()).Astype(v_tensor.Dtype()).Data());
if (amsgrad_) {
auto& vmax = state.param_states[name]["v_hat_max"];
vmax.BindData(Tensor::FromVector<double>(v_hat_max_raw, vmax.shape()).Astype(vmax.Dtype()).Data());
}
updates[name] = Tensor::FromVector<double>(u_raw, p.shape()).Astype(p.Dtype());
}
state.step = step;
return updates;
}
} // namespace tenzr::optim