Files
paddlepaddle--paddle/paddle/fluid/framework/ir/ipu/optimizer_extract_pass.cc
T
2026-07-13 12:40:42 +08:00

350 lines
14 KiB
C++

// Copyright (c) 2021 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.
#include "paddle/fluid/framework/ir/ipu/optimizer_extract_pass.h"
#include "paddle/fluid/framework/ir/graph_helper.h"
#include "paddle/fluid/framework/ir/pass_tester_helper.h"
namespace paddle {
namespace framework {
namespace ir {
std::set<std::string> ignored_ops = {
"sign",
"sum",
"clip",
"clip_by_norm",
"reduce_sum",
"sqrt",
"elementwise_max",
"elementwise_div",
"elementwise_mul",
"scale", // adamax
"assign", // adamw
"squared_l2_norm", // gradient_clip_norm
"cast", // mix-precision support
};
const bool startswith(const std::string& str, const std::string& pre) {
if (str.rfind(pre, 0) == 0) {
return true;
} else {
return false;
}
}
const bool is_grad_clip_op(const std::string& op_namescope) {
return startswith(op_namescope, "/gradient_clip");
}
const bool is_optimizer_op(const std::string& op_namescope) {
return startswith(op_namescope, "/optimizer");
}
const bool is_regularization_op(const std::string& op_namescope) {
return startswith(op_namescope, "/regularization");
}
void IpuOptimizerExtractPass::ApplyImpl(ir::Graph* graph) const {
// optimizer values will be extracted when lowering optimizer in ipu_backend
OpDesc new_op("popart_optimizer", {}, {}, {});
new_op.SetAttr("op_role", 0);
new_op.SetAttr("with_lr_sched", false);
std::set<std::string> set_ops{};
// save the weight decay tensor_name and weight_decay_value for Lamb
std::vector<std::string> weight_decay_vars{};
std::vector<float> weight_decay_values{};
// use map store <op_type, op_ptr> ?
for (auto* node : TopologySortOperations(*graph)) {
if (!node->IsOp()) {
continue;
}
auto op = node->Op();
auto op_type = op->Type();
int op_role_ = PADDLE_GET_CONST(
int, op->GetAttr(OpProtoAndCheckerMaker::OpRoleAttrName()));
auto op_role = static_cast<OpRole>(op_role_);
if (op_role == OpRole::kOptimize) {
// save weight decay value from every lamb optimizer op
if (op_type == "lamb" && op->HasAttr("weight_decay")) {
auto weight_decay_value =
PADDLE_GET_CONST(float, op->GetAttr("weight_decay"));
auto params = op->Output("ParamOut");
weight_decay_vars.push_back(params[0]);
weight_decay_values.push_back(weight_decay_value);
}
if (set_ops.count(op_type)) {
continue;
}
auto op_namescope =
PADDLE_GET_CONST(std::string, op->GetAttr("op_namescope"));
bool is_grad_clip = is_grad_clip_op(op_namescope);
// bool is_optimizer = is_optimizer_op(op_namescope);
bool is_regularization = is_regularization_op(op_namescope);
VLOG(10) << "found optimizer related op: " << op_type;
// initial learning_rate will be set in ipu_backend
set_ops.insert(op_type);
if (op_type == "sgd") {
auto type = std::string{"sgd"};
auto lr_var = op->Input("LearningRate").front();
new_op.SetAttr("type", type);
new_op.SetAttr("lr_var", lr_var);
new_op.SetAttr("weight_decay", 0.0f);
new_op.SetAttr("momentum", 0.0f);
new_op.SetAttr("raw_type", op_type);
} else if (op_type == "momentum") {
auto type = std::string{"sgd"};
// auto LearningRate = op->Input("LearningRate");
auto use_nesterov = PADDLE_GET_CONST(bool, op->GetAttr("use_nesterov"));
PADDLE_ENFORCE_EQ(use_nesterov,
false,
common::errors::Unimplemented(
"ipu does not support nesterov mode."));
auto regularization_method =
PADDLE_GET_CONST(std::string, op->GetAttr("regularization_method"));
PADDLE_ENFORCE_NE(regularization_method,
"l1_decay",
common::errors::Unimplemented(
"ipu does not support l1_decay mode."));
auto multi_precision =
PADDLE_GET_CONST(bool, op->GetAttr("multi_precision"));
PADDLE_ENFORCE_EQ(multi_precision,
false,
common::errors::Unimplemented(
"ipu does not support multi_precision mode."));
auto rescale_grad =
PADDLE_GET_CONST(float, op->GetAttr("rescale_grad"));
PADDLE_ENFORCE_EQ(rescale_grad,
1.0,
common::errors::Unimplemented(
"ipu does not support rescale_grad mode."));
auto regularization_coeff =
PADDLE_GET_CONST(float, op->GetAttr("regularization_coeff"));
auto lr_var = op->Input("LearningRate").front();
auto momentum = PADDLE_GET_CONST(float, op->GetAttr("mu"));
new_op.SetAttr("type", type);
new_op.SetAttr("lr_var", lr_var);
new_op.SetAttr("momentum", momentum);
new_op.SetAttr("weight_decay", regularization_coeff);
new_op.SetAttr("raw_type", op_type);
} else if (op_type == "adam" || op_type == "adamw") {
auto type = std::string{"adam"};
auto lr_var = op->Input("LearningRate").front();
auto beta1 = PADDLE_GET_CONST(float, op->GetAttr("beta1"));
auto beta2 = PADDLE_GET_CONST(float, op->GetAttr("beta2"));
auto epsilon = PADDLE_GET_CONST(float, op->GetAttr("epsilon"));
auto lazy_mode = PADDLE_GET_CONST(bool, op->GetAttr("lazy_mode"));
auto multi_precision =
PADDLE_GET_CONST(bool, op->GetAttr("multi_precision"));
PADDLE_ENFORCE_EQ(lazy_mode,
false,
common::errors::Unimplemented(
"ipu does not support lazy_mode mode."));
PADDLE_ENFORCE_EQ(multi_precision,
false,
common::errors::Unimplemented(
"ipu does not support multi_precision mode."));
new_op.SetAttr("type", type);
new_op.SetAttr("lr_var", lr_var);
new_op.SetAttr("weight_decay", 0.0f);
new_op.SetAttr("beta1", beta1);
new_op.SetAttr("beta2", beta2);
new_op.SetAttr("eps", epsilon);
new_op.SetAttr("adam_mode", std::string{"adam"});
// adam or adamw
if (op_type == "adam") {
new_op.SetAttr("weight_decay_mode", std::string{"l2_regularization"});
new_op.SetAttr("raw_type", std::string{"adam"});
} else {
new_op.SetAttr("weight_decay_mode", std::string{"decay"});
new_op.SetAttr("raw_type", std::string{"adamw"});
}
} else if (op_type == "adamax") {
auto type = std::string{"adam"};
auto lr_var = op->Input("LearningRate").front();
auto beta1 = PADDLE_GET_CONST(float, op->GetAttr("beta1"));
auto beta2 = PADDLE_GET_CONST(float, op->GetAttr("beta2"));
auto epsilon = PADDLE_GET_CONST(float, op->GetAttr("epsilon"));
new_op.SetAttr("type", type);
new_op.SetAttr("lr_var", lr_var);
new_op.SetAttr("weight_decay", 0.0f);
new_op.SetAttr("beta1", beta1);
new_op.SetAttr("beta2", beta2);
new_op.SetAttr("eps", epsilon);
new_op.SetAttr("adam_mode", std::string{"adamax"});
new_op.SetAttr("weight_decay_mode", std::string{"l2_regularization"});
new_op.SetAttr("raw_type", op_type);
} else if (op_type == "lamb") {
// use decay mode
auto type = std::string{"adam"};
auto lr_var = op->Input("LearningRate").front();
auto weight_decay =
PADDLE_GET_CONST(float, op->GetAttr("weight_decay"));
auto beta1 = PADDLE_GET_CONST(float, op->GetAttr("beta1"));
auto beta2 = PADDLE_GET_CONST(float, op->GetAttr("beta2"));
auto epsilon = PADDLE_GET_CONST(float, op->GetAttr("epsilon"));
new_op.SetAttr("type", type);
new_op.SetAttr("lr_var", lr_var);
new_op.SetAttr("weight_decay", weight_decay);
new_op.SetAttr("beta1", beta1);
new_op.SetAttr("beta2", beta2);
new_op.SetAttr("eps", epsilon);
new_op.SetAttr("adam_mode", std::string{"lamb"});
new_op.SetAttr("weight_decay_mode", std::string{"decay"});
new_op.SetAttr("raw_type", op_type);
} else if (op_type == "adadelta") {
// NO LearningRate
auto type = std::string{"adaptive"};
auto rho = PADDLE_GET_CONST(float, op->GetAttr("rho"));
auto epsilon = PADDLE_GET_CONST(float, op->GetAttr("epsilon"));
new_op.SetAttr("type", type);
new_op.SetAttr("weight_decay", 0.0f);
new_op.SetAttr("alpha", rho);
new_op.SetAttr("eps", epsilon);
new_op.SetAttr("momentum", 0.0f);
new_op.SetAttr("adaptive_mode", std::string{"adadelta"});
new_op.SetAttr("weight_decay_mode", std::string{"l2_regularization"});
new_op.SetAttr("raw_type", op_type);
} else if (op_type == "adagrad") {
auto type = std::string{"adaptive"};
auto lr_var = op->Input("LearningRate").front();
auto epsilon = PADDLE_GET_CONST(float, op->GetAttr("epsilon"));
new_op.SetAttr("type", type);
new_op.SetAttr("lr_var", lr_var);
new_op.SetAttr("weight_decay", 0.0f);
// `alpha` use default
new_op.SetAttr("alpha", 0.99f);
new_op.SetAttr("eps", epsilon);
new_op.SetAttr("momentum", 0.0f);
new_op.SetAttr("adaptive_mode", std::string{"adagrad"});
new_op.SetAttr("weight_decay_mode", std::string{"l2_regularization"});
new_op.SetAttr("raw_type", op_type);
} else if (op_type == "rmsprop") {
auto type = std::string{"adaptive"};
auto lr_var = op->Input("LearningRate").front();
auto epsilon = PADDLE_GET_CONST(float, op->GetAttr("epsilon"));
auto decay = PADDLE_GET_CONST(float, op->GetAttr("decay"));
auto momentum = PADDLE_GET_CONST(float, op->GetAttr("momentum"));
auto centered = PADDLE_GET_CONST(bool, op->GetAttr("centered"));
new_op.SetAttr("type", type);
new_op.SetAttr("weight_decay", 0.0f);
new_op.SetAttr("alpha", decay);
new_op.SetAttr("eps", epsilon);
new_op.SetAttr("momentum", momentum);
new_op.SetAttr("weight_decay_mode", std::string{"l2_regularization"});
if (centered) {
new_op.SetAttr("adaptive_mode", std::string{"centered_rmsprop"});
new_op.SetAttr("raw_type", op_type);
} else {
new_op.SetAttr("adaptive_mode", std::string{"rmsprop"});
new_op.SetAttr("raw_type", op_type);
}
} else if (is_regularization && op_type == "scale") {
// set weight_decay for L2Decay
auto scale = PADDLE_GET_CONST(float, op->GetAttr("scale"));
new_op.SetAttr("weight_decay", scale);
} else if (is_grad_clip && op_type == "fill_constant") {
// set clip_norm for ClipGradByGlobalNorm
auto value = PADDLE_GET_CONST(float, op->GetAttr("value"));
new_op.SetAttr("clip_norm", value);
} else if (ignored_ops.count(op_type)) {
VLOG(10) << "Ignore optimizer related op: " << op_type;
} else {
PADDLE_THROW(common::errors::InvalidArgument(
"Unknown optimizer related op_type: %s", op_type));
}
} else if (op_role == OpRole::kLoss) {
VLOG(10) << "found loss op type: " << op->Type();
auto outputs = op->Outputs();
PADDLE_ENFORCE_EQ(
outputs.size(),
1,
common::errors::InvalidArgument("Can only support one loss key"));
auto losses = outputs.begin()->second;
PADDLE_ENFORCE_EQ(
losses.size(),
1,
common::errors::InvalidArgument("Can only support one loss name"));
auto loss_var = losses.front();
new_op.SetAttr("loss_var", loss_var);
} else if (op_role == OpRole::kLRSched) {
// op_role == OpRole::kLRSched | OpRole::kOptimize
new_op.SetAttr("with_lr_sched", true);
}
}
// seems with_lr_sched is always true
new_op.SetAttr("with_lr_sched", true);
// setup weight decay for Lamb
new_op.SetAttr("weight_decay_vars", weight_decay_vars);
new_op.SetAttr("weight_decay_values", weight_decay_values);
// weight_decay/coeff is "scale" attr of scale_op
if (set_ops.count("scale") && set_ops.count("sum")) {
if (set_ops.count("sign")) {
// L1Decay
// sign + scale + sum
PADDLE_THROW(
common::errors::Unimplemented("Unsupported L1Decay regularizer"));
} else {
// L2Decay
// scale + sum
new_op.SetAttr("weight_decay_mode", std::string{"l2_regularization"});
}
} else {
VLOG(10) << "No weight decay setting found";
}
// setup grad clip
if (set_ops.count("clip")) {
// ClipGradByValue
PADDLE_THROW(common::errors::Unimplemented("Unsupported ClipGradByValue"));
} else if (set_ops.count("clip_by_norm")) {
// ClipGradByNorm
PADDLE_THROW(common::errors::Unimplemented("Unsupported ClipGradByNorm"));
}
// ClipGradByGlobalNorm
// use graph pattern match ClipGradByGlobalNorm
// square + reduce_sum + sum + sqrt + fill_constant
// + elementwise_max + elementwise_div + elementwise_mul
// clip_norm from fill_constant`s attr `value` dtype float
if (new_op.HasAttr("type")) {
auto new_node = graph->CreateOpNode(&new_op);
VLOG(10) << "New Optimizer Node:";
VLOG(10) << DebugString(new_node);
} else {
PADDLE_THROW(common::errors::NotFound(
"No optimizer found, optimizer must be one of these types: sgd, "
"momentum, adam, adamw, adamax, lamb, adadelta, adagrad or rmsprop"));
}
}
} // namespace ir
} // namespace framework
} // namespace paddle
REGISTER_PASS(optimizer_extract_pass,
paddle::framework::ir::IpuOptimizerExtractPass);