635 lines
19 KiB
C++
635 lines
19 KiB
C++
// Copyright (c) 2023 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/common/flags.h"
|
|
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace paddle::flags {
|
|
|
|
std::stringstream& ErrorStream() {
|
|
static std::stringstream err_ss;
|
|
return err_ss;
|
|
}
|
|
|
|
inline void exit_with_errors() {
|
|
std::cerr << ErrorStream().str();
|
|
exit(-1);
|
|
}
|
|
|
|
#define LOG_FLAG_ERROR(message) \
|
|
ErrorStream() << "paddle flags error: " << message << " (at " << __FILE__ \
|
|
<< ":" << __LINE__ << ")" << std::endl
|
|
|
|
#define LOG_FLAG_FATAL_ERROR(message) \
|
|
LOG_FLAG_ERROR(message); \
|
|
exit_with_errors()
|
|
|
|
enum class FlagType : uint8_t {
|
|
BOOL = 0,
|
|
INT32 = 1,
|
|
UINT32 = 2,
|
|
INT64 = 3,
|
|
UINT64 = 4,
|
|
DOUBLE = 5,
|
|
STRING = 6,
|
|
UNDEFINED = 7,
|
|
};
|
|
|
|
class Flag {
|
|
public:
|
|
Flag(std::string name,
|
|
std::string description,
|
|
std::string file,
|
|
FlagType type,
|
|
const void* default_value,
|
|
void* value)
|
|
: name_(name),
|
|
description_(description),
|
|
file_(file),
|
|
type_(type),
|
|
default_value_(default_value),
|
|
value_(value) {}
|
|
~Flag() = default;
|
|
|
|
// Summary: --name_: type_, description_ (default: default_value_)
|
|
std::string Summary() const;
|
|
|
|
void SetValueFromString(const std::string& value);
|
|
|
|
private:
|
|
friend class FlagRegistry;
|
|
|
|
const std::string name_;
|
|
const std::string description_;
|
|
const std::string file_;
|
|
const FlagType type_;
|
|
const void* default_value_;
|
|
void* value_;
|
|
};
|
|
|
|
class FlagRegistry {
|
|
public:
|
|
static FlagRegistry* Instance() {
|
|
static FlagRegistry* global_registry_ = new FlagRegistry();
|
|
return global_registry_;
|
|
}
|
|
|
|
void RegisterFlag(Flag* flag);
|
|
|
|
bool SetFlagValue(const std::string& name, const std::string& value);
|
|
bool UpdateLinkedFlags(const std::string& name, const std::string& value);
|
|
void InitLinkedFlags();
|
|
bool HasFlag(const std::string& name) const;
|
|
|
|
void PrintAllFlagHelp(std::ostream& os) const;
|
|
|
|
private:
|
|
FlagRegistry() { InitLinkedFlags(); }
|
|
|
|
std::map<std::string, Flag*> flags_;
|
|
std::map<std::string, std::map<std::string, std::string>> linked_flags_;
|
|
|
|
struct FlagCompare {
|
|
bool operator()(const Flag* flag1, const Flag* flag2) const {
|
|
return flag1->name_ < flag2->name_;
|
|
}
|
|
};
|
|
|
|
std::map<std::string, std::set<Flag*, FlagCompare>> flags_by_file_;
|
|
|
|
std::mutex mutex_;
|
|
};
|
|
|
|
template <typename T>
|
|
struct FlagTypeTraits {
|
|
static constexpr FlagType Type = FlagType::UNDEFINED;
|
|
};
|
|
|
|
#define DEFINE_FLAG_TYPE_TRAITS(type, flag_type) \
|
|
template <> \
|
|
struct FlagTypeTraits<type> { \
|
|
static constexpr FlagType Type = flag_type; \
|
|
}
|
|
|
|
DEFINE_FLAG_TYPE_TRAITS(bool, FlagType::BOOL);
|
|
DEFINE_FLAG_TYPE_TRAITS(int32_t, FlagType::INT32);
|
|
DEFINE_FLAG_TYPE_TRAITS(uint32_t, FlagType::UINT32);
|
|
DEFINE_FLAG_TYPE_TRAITS(int64_t, FlagType::INT64);
|
|
DEFINE_FLAG_TYPE_TRAITS(uint64_t, FlagType::UINT64);
|
|
DEFINE_FLAG_TYPE_TRAITS(double, FlagType::DOUBLE);
|
|
DEFINE_FLAG_TYPE_TRAITS(std::string, FlagType::STRING);
|
|
|
|
#undef DEFINE_FLAG_TYPE_TRAITS
|
|
|
|
template <typename T>
|
|
FlagRegisterer::FlagRegisterer(std::string name,
|
|
std::string help,
|
|
std::string file,
|
|
const T* default_value,
|
|
T* value) {
|
|
FlagType type = FlagTypeTraits<T>::Type;
|
|
Flag* flag = new Flag(name, help, file, type, default_value, value);
|
|
FlagRegistry::Instance()->RegisterFlag(flag);
|
|
}
|
|
|
|
// Instantiate FlagRegisterer for supported types.
|
|
#define INSTANTIATE_FLAG_REGISTERER(type) \
|
|
template TEST_API FlagRegisterer::FlagRegisterer(std::string name, \
|
|
std::string help, \
|
|
std::string file, \
|
|
const type* default_value, \
|
|
type* value)
|
|
|
|
INSTANTIATE_FLAG_REGISTERER(bool);
|
|
INSTANTIATE_FLAG_REGISTERER(int32_t);
|
|
INSTANTIATE_FLAG_REGISTERER(uint32_t);
|
|
INSTANTIATE_FLAG_REGISTERER(int64_t);
|
|
INSTANTIATE_FLAG_REGISTERER(uint64_t);
|
|
INSTANTIATE_FLAG_REGISTERER(double);
|
|
INSTANTIATE_FLAG_REGISTERER(std::string);
|
|
|
|
#undef INSTANTIATE_FLAG_REGISTERER
|
|
|
|
std::string FlagType2String(FlagType type) {
|
|
switch (type) {
|
|
case FlagType::BOOL:
|
|
return "bool";
|
|
case FlagType::INT32:
|
|
return "int32";
|
|
case FlagType::UINT32:
|
|
return "uint32";
|
|
case FlagType::INT64:
|
|
return "int64";
|
|
case FlagType::UINT64:
|
|
return "uint64";
|
|
case FlagType::DOUBLE:
|
|
return "double";
|
|
case FlagType::STRING:
|
|
return "string";
|
|
default:
|
|
return "undefined";
|
|
}
|
|
}
|
|
|
|
std::string Value2String(const void* value, FlagType type) {
|
|
switch (type) {
|
|
case FlagType::BOOL: {
|
|
const bool* val = static_cast<const bool*>(value);
|
|
return *val ? "true" : "false";
|
|
}
|
|
case FlagType::INT32: {
|
|
const int32_t* val = static_cast<const int32_t*>(value);
|
|
return std::to_string(*val);
|
|
}
|
|
case FlagType::UINT32: {
|
|
const uint32_t* val = static_cast<const uint32_t*>(value);
|
|
return std::to_string(*val);
|
|
}
|
|
case FlagType::INT64: {
|
|
const int64_t* val = static_cast<const int64_t*>(value);
|
|
return std::to_string(*val);
|
|
}
|
|
case FlagType::UINT64: {
|
|
const uint64_t* val = static_cast<const uint64_t*>(value);
|
|
return std::to_string(*val);
|
|
}
|
|
case FlagType::DOUBLE: {
|
|
const double* val = static_cast<const double*>(value);
|
|
return std::to_string(*val);
|
|
}
|
|
case FlagType::STRING: {
|
|
const std::string* val = static_cast<const std::string*>(value);
|
|
return *val;
|
|
}
|
|
default:
|
|
LOG_FLAG_FATAL_ERROR("flag type is undefined.");
|
|
return "";
|
|
}
|
|
}
|
|
|
|
std::string Flag::Summary() const {
|
|
return "--" + name_ + ": " + FlagType2String(type_) + ", " + description_ +
|
|
" (default: " + Value2String(default_value_, type_) + ")";
|
|
}
|
|
|
|
void Flag::SetValueFromString(const std::string& value) {
|
|
try {
|
|
switch (type_) {
|
|
case FlagType::BOOL: {
|
|
bool* val = static_cast<bool*>(value_);
|
|
if (value == "true" || value == "True" || value == "TRUE" ||
|
|
value == "1") {
|
|
*val = true;
|
|
} else if (value == "false" || value == "False" || value == "FALSE" ||
|
|
value == "0") {
|
|
*val = false;
|
|
} else {
|
|
throw std::invalid_argument(
|
|
", please use [true, True, TRUE, 1] or [false, False, FALSE, "
|
|
"0].");
|
|
}
|
|
break;
|
|
}
|
|
case FlagType::INT32: {
|
|
int32_t* val = static_cast<int32_t*>(value_);
|
|
*val = std::stoi(value);
|
|
break;
|
|
}
|
|
case FlagType::UINT32: {
|
|
uint32_t* val = static_cast<uint32_t*>(value_);
|
|
*val = std::stoul(value);
|
|
break;
|
|
}
|
|
case FlagType::INT64: {
|
|
int64_t* val = static_cast<int64_t*>(value_);
|
|
*val = std::stoll(value);
|
|
break;
|
|
}
|
|
case FlagType::UINT64: {
|
|
uint64_t* val = static_cast<uint64_t*>(value_);
|
|
*val = std::stoull(value);
|
|
break;
|
|
}
|
|
case FlagType::DOUBLE: {
|
|
double* val = static_cast<double*>(value_);
|
|
*val = std::stod(value);
|
|
break;
|
|
}
|
|
case FlagType::STRING: {
|
|
std::string* val = static_cast<std::string*>(value_);
|
|
*val = value;
|
|
break;
|
|
}
|
|
default: {
|
|
LOG_FLAG_FATAL_ERROR("flag type is undefined.");
|
|
}
|
|
}
|
|
} catch (const std::exception& e) {
|
|
std::string error_msg = "value: \"" + value + "\" is invalid for " +
|
|
FlagType2String(type_) + " flag \"" + name_ + "\"";
|
|
if (type_ == FlagType::BOOL) {
|
|
error_msg += e.what();
|
|
} else {
|
|
error_msg += ".";
|
|
}
|
|
LOG_FLAG_ERROR(error_msg);
|
|
}
|
|
}
|
|
|
|
void FlagRegistry::RegisterFlag(Flag* flag) {
|
|
auto iter = flags_.find(flag->name_);
|
|
if (iter != flags_.end()) {
|
|
std::string error_msg = "flag multiple definition, flag \"";
|
|
error_msg += flag->name_;
|
|
error_msg += "\" was defined both in ";
|
|
error_msg += iter->second->file_;
|
|
error_msg += " and ";
|
|
error_msg += flag->file_;
|
|
LOG_FLAG_FATAL_ERROR(error_msg);
|
|
} else {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
flags_[flag->name_] = flag;
|
|
flags_by_file_[flag->file_].insert(flag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Initializes linked_flags_
|
|
*
|
|
* The keys of linked_flags_ represent Flags, and the corresponding values
|
|
* indicate which other flags should be linked when the key Flag is set to a
|
|
* non-default value.
|
|
*/
|
|
void FlagRegistry::InitLinkedFlags() {
|
|
// Set the related flags map
|
|
linked_flags_["dump_api_python_stack_path"] = {{"enable_unique_name", "true"},
|
|
{"call_stack_level", "3"}};
|
|
linked_flags_["tensor_md5_checksum_output_path"] = {
|
|
{"enable_unique_name", "true"}};
|
|
linked_flags_["dump_grad_node_forward_stack_path"] = {
|
|
{"enable_unique_name", "true"}, {"call_stack_level", "3"}};
|
|
linked_flags_["dump_api_and_gradnode_python_stack_dir"] = {
|
|
{"enable_unique_name", "true"}, {"call_stack_level", "3"}};
|
|
}
|
|
|
|
bool FlagRegistry::SetFlagValue(const std::string& name,
|
|
const std::string& value) {
|
|
if (HasFlag(name)) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
flags_[name]->SetValueFromString(value);
|
|
|
|
UpdateLinkedFlags(name, value);
|
|
return true;
|
|
} else {
|
|
LOG_FLAG_ERROR("illegal SetFlagValue, flag \"" + name +
|
|
"\" is not defined.");
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Sets the value of linked variables
|
|
*
|
|
* @param name Name of the variable currently being modified
|
|
* @param value New value of the variable being modified
|
|
*
|
|
* This function updates the values of variables linked to the current modified
|
|
* variable.
|
|
*/
|
|
bool FlagRegistry::UpdateLinkedFlags(const std::string& name,
|
|
const std::string& value) {
|
|
if (HasFlag(name)) {
|
|
// Flags_name does not has linked flags
|
|
if (!linked_flags_.count(name)) {
|
|
return true;
|
|
}
|
|
Flag* f = flags_[name];
|
|
std::string default_value = Value2String(f->default_value_, f->type_);
|
|
// If the value equals to default_value we will not set the linked flags
|
|
if (value == default_value) {
|
|
return true;
|
|
}
|
|
// Update the related flags
|
|
for (auto iter : linked_flags_[name]) {
|
|
std::string linked_flag_name = iter.first;
|
|
std::string linked_flag_value = iter.second;
|
|
if (HasFlag(linked_flag_name)) {
|
|
flags_[linked_flag_name]->SetValueFromString(linked_flag_value);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
// Custom rule for Flags
|
|
if (name == "dump_api_and_gradnode_python_stack_dir") {
|
|
std::string fwd_path;
|
|
std::string bwd_path;
|
|
if (value.back() == '/') {
|
|
fwd_path = value + "api_call_stack";
|
|
bwd_path = value + "gradnode_call_stack";
|
|
} else {
|
|
fwd_path = value + "/api_call_stack";
|
|
bwd_path = value + "/gradnode_call_stack";
|
|
}
|
|
flags_["dump_api_python_stack_path"]->SetValueFromString(fwd_path);
|
|
flags_["dump_grad_node_forward_stack_path"]->SetValueFromString(bwd_path);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FlagRegistry::HasFlag(const std::string& name) const {
|
|
return flags_.find(name) != flags_.end();
|
|
}
|
|
|
|
void FlagRegistry::PrintAllFlagHelp(std::ostream& os) const {
|
|
for (const auto& iter : flags_by_file_) {
|
|
os << std::endl << "Flags defined in " << iter.first << ":" << std::endl;
|
|
for (const auto& flag : iter.second) {
|
|
os << " " << flag->Summary() << std::endl;
|
|
}
|
|
}
|
|
os << std::endl;
|
|
}
|
|
|
|
void PrintAllFlagHelp(bool to_file, const std::string& file_path) {
|
|
if (to_file) {
|
|
std::ofstream fout(file_path);
|
|
FlagRegistry::Instance()->PrintAllFlagHelp(fout);
|
|
} else {
|
|
FlagRegistry::Instance()->PrintAllFlagHelp(std::cout);
|
|
}
|
|
}
|
|
|
|
bool SetFlagValue(const std::string& name, const std::string& value) {
|
|
return FlagRegistry::Instance()->SetFlagValue(name, value);
|
|
}
|
|
PADDLE_API bool UpdateLinkedFlags(const std::string& name,
|
|
const std::string& value) {
|
|
return FlagRegistry::Instance()->UpdateLinkedFlags(name, value);
|
|
}
|
|
|
|
bool FindFlag(const std::string& name) {
|
|
return FlagRegistry::Instance()->HasFlag(name);
|
|
}
|
|
|
|
bool GetValueFromEnv(const std::string& name, std::string* value) {
|
|
const char* env_var = std::getenv(name.c_str());
|
|
if (env_var == nullptr) {
|
|
return false;
|
|
}
|
|
*value = std::string(env_var);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Set flags from environment variables.
|
|
*
|
|
* It receives a list of flags name, and will find the corresponding environment
|
|
* variables named "FLAGS_name", if found, it will set the environment variable
|
|
* values to the flags. If error_fatal is true, the program will exit when the
|
|
* environment variable is not set or the flag is not defined, that is the same
|
|
* effect as using commandline argument "--fromenv=var_name1,var_name2,...".
|
|
* Otherwise, the errors above will be ignored, that is the same effect as using
|
|
* commandline argument "--tryfromenv=var_name1,var_name2,...".
|
|
*/
|
|
|
|
void SetFlagsFromEnv(const std::vector<std::string>& flags, bool error_fatal) {
|
|
bool success = true;
|
|
for (const std::string& flag_name : flags) {
|
|
std::string env_var_name = std::string("FLAGS_") + flag_name;
|
|
std::string env_var_value;
|
|
if (GetValueFromEnv(env_var_name, &env_var_value)) {
|
|
success =
|
|
FlagRegistry::Instance()->SetFlagValue(flag_name, env_var_value);
|
|
} else if (error_fatal) {
|
|
LOG_FLAG_ERROR("environment variable \"" + env_var_name +
|
|
"\" is not set.");
|
|
success = false;
|
|
}
|
|
}
|
|
if (error_fatal && !success) {
|
|
exit_with_errors();
|
|
}
|
|
}
|
|
|
|
static bool allow_undefined_flags = false;
|
|
|
|
TEST_API void AllowUndefinedFlags() { allow_undefined_flags = true; }
|
|
|
|
TEST_API void ParseCommandLineFlags(int* pargc, char*** pargv) {
|
|
assert(*pargc > 0);
|
|
size_t argv_num = *pargc - 1;
|
|
std::vector<std::string> argvs(*pargv + 1, *pargv + *pargc);
|
|
|
|
std::string arg_format_help =
|
|
"please follow the formats: \"--help(h)\", \"--name=value\""
|
|
" or \"--name value\".";
|
|
for (size_t i = 0; i < argv_num; i++) {
|
|
const std::string& argv = argvs[i];
|
|
|
|
if (argv.empty()) {
|
|
continue;
|
|
}
|
|
|
|
if (argv.size() < 2 || argv[0] != '-') {
|
|
LOG_FLAG_FATAL_ERROR("invalid commandline argument: \"" + argv + "\", " +
|
|
arg_format_help);
|
|
}
|
|
|
|
// parse arg name and value
|
|
size_t hyphen_num = argv[1] == '-' ? 2 : 1;
|
|
std::string name, value;
|
|
size_t split_pos = argv.find('=');
|
|
if (split_pos == std::string::npos) {
|
|
// the argv format is "--name" or "--name value"
|
|
name = argv.substr(hyphen_num);
|
|
if (name.empty()) {
|
|
std::string error_msg = "invalid commandline argument: \"";
|
|
error_msg += argv;
|
|
error_msg += "\", ";
|
|
error_msg += arg_format_help;
|
|
LOG_FLAG_FATAL_ERROR(error_msg);
|
|
}
|
|
|
|
// print help message
|
|
if (name == "help" || name == "h") {
|
|
FlagRegistry::Instance()->PrintAllFlagHelp(std::cout);
|
|
exit(1);
|
|
}
|
|
|
|
// get the value from next argv.
|
|
if (++i == argv_num) {
|
|
std::string error_msg = "expected value of flag \"";
|
|
error_msg += name;
|
|
error_msg += "\" but found none.";
|
|
LOG_FLAG_FATAL_ERROR(error_msg);
|
|
} else {
|
|
value = argvs[i];
|
|
}
|
|
} else {
|
|
// the argv format is "--name=value"
|
|
if (split_pos == hyphen_num || split_pos == argv.size() - 1) {
|
|
std::string error_msg = "invalid commandline argument: \"";
|
|
error_msg += argv;
|
|
error_msg += "\", ";
|
|
error_msg += arg_format_help;
|
|
LOG_FLAG_FATAL_ERROR(error_msg);
|
|
}
|
|
name = argv.substr(hyphen_num, split_pos - hyphen_num);
|
|
value = argv.substr(split_pos + 1);
|
|
}
|
|
|
|
// special case for flag value enclosed in ""
|
|
if (value[0] == '"') {
|
|
value = value.substr(1);
|
|
if (value.back() == '"') {
|
|
value.pop_back();
|
|
} else {
|
|
while (i < argv_num) {
|
|
value += " ";
|
|
value += argvs[++i];
|
|
if (value.back() == '"') {
|
|
break;
|
|
}
|
|
}
|
|
if (value.back() == '"') {
|
|
value.pop_back();
|
|
} else {
|
|
std::string error_msg = "unexpected end of flag \"";
|
|
error_msg += name;
|
|
error_msg += "\" value while looking for matching `\"'";
|
|
LOG_FLAG_FATAL_ERROR(error_msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (name == "fromenv" || name == "tryfromenv") {
|
|
// Value of --fromenv or --tryfromenv should be
|
|
// a comma separated list of env var names.
|
|
std::vector<std::string> env_flag_names;
|
|
for (size_t start_pos = 0, end_pos = 0;
|
|
start_pos < value.size() && end_pos != std::string::npos;
|
|
start_pos = end_pos + 1) {
|
|
end_pos = value.find(',', start_pos);
|
|
env_flag_names.push_back(value.substr(start_pos, end_pos - start_pos));
|
|
}
|
|
if (name == "fromenv") {
|
|
SetFlagsFromEnv(env_flag_names, true);
|
|
} else {
|
|
SetFlagsFromEnv(env_flag_names, false);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
FlagRegistry::Instance()->SetFlagValue(name, value);
|
|
}
|
|
if (!allow_undefined_flags && !ErrorStream().str().empty()) {
|
|
exit_with_errors();
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
T GetFromEnv(const std::string& name, const T& default_val) {
|
|
std::string value_str;
|
|
if (GetValueFromEnv(name, &value_str)) {
|
|
T value;
|
|
FlagType type = FlagTypeTraits<T>::Type;
|
|
Flag flag("tmp_" + name, "", "", type, &value, &value);
|
|
flag.SetValueFromString(value_str);
|
|
if (!ErrorStream().str().empty()) {
|
|
ErrorStream().str("");
|
|
std::string error_msg = "value \"";
|
|
error_msg += value_str;
|
|
error_msg += "\" of environment";
|
|
error_msg += "variable \"";
|
|
error_msg += name;
|
|
error_msg += "\" is invalid when ";
|
|
error_msg += "using GetFromEnv with ";
|
|
error_msg += FlagType2String(type);
|
|
error_msg += " type.";
|
|
LOG_FLAG_FATAL_ERROR(error_msg);
|
|
}
|
|
return value;
|
|
} else {
|
|
return default_val;
|
|
}
|
|
}
|
|
|
|
#define INSTANTIATE_GET_FROM_ENV(type) \
|
|
template TEST_API type GetFromEnv(const std::string& name, \
|
|
const type& default_val)
|
|
|
|
INSTANTIATE_GET_FROM_ENV(bool);
|
|
INSTANTIATE_GET_FROM_ENV(int32_t);
|
|
INSTANTIATE_GET_FROM_ENV(uint32_t);
|
|
INSTANTIATE_GET_FROM_ENV(int64_t);
|
|
INSTANTIATE_GET_FROM_ENV(uint64_t);
|
|
INSTANTIATE_GET_FROM_ENV(double);
|
|
INSTANTIATE_GET_FROM_ENV(std::string);
|
|
|
|
#undef INSTANTIATE_GET_FROM_ENV
|
|
|
|
} // namespace paddle::flags
|