#include <algorithm>
#include <limits>
#include <memory>
#include "common/verbose.hpp"
#include "graph/interface/op_schema.hpp"
#include "graph/interface/opset.hpp"
#include "graph/utils/utils.hpp"
#include "graph/utils/verbose.hpp"
namespace dnnl {
namespace impl {
namespace graph {
op_schema_t::op_schema_t() : op_kind_(op_kind::LastSymbol), version_(0) {}
op_schema_t::op_schema_t(op_kind_t kind, opset_version version)
: op_kind_(kind), version_(version) {}
op_schema_t &op_schema_t::set_op_kind(op_kind_t kind) {
op_kind_ = kind;
return *this;
}
op_kind_t op_schema_t::get_op_kind() const {
return op_kind_;
}
op_schema_t &op_schema_t::since_version(opset_version n) {
version_ = n;
return *this;
}
opset_version op_schema_t::get_since_version() const {
return version_;
}
op_schema_t &op_schema_t::set_num_inputs(std::set<size_t> &&input_num) {
num_inputs_ = std::move(input_num);
return *this;
}
op_schema_t &op_schema_t::set_num_inputs(size_t input_num) {
num_inputs_.insert(input_num);
return *this;
}
std::set<size_t> op_schema_t::get_num_inputs() const {
return num_inputs_;
}
op_schema_t &op_schema_t::set_num_outputs(std::set<size_t> &&output_num) {
num_outputs_ = std::move(output_num);
return *this;
}
op_schema_t &op_schema_t::set_num_outputs(size_t output_num) {
num_outputs_.insert(output_num);
return *this;
}
std::set<size_t> op_schema_t::get_num_outputs() const {
return num_outputs_;
}
op_schema_t &op_schema_t::set_input(
size_t in_offset, std::string &&in_name, std::string &&dtype_string) {
verify_input_(in_offset);
inputs_.emplace_back(std::move(in_name), std::move(dtype_string));
return *this;
}
const std::vector<op_schema_t::op_parameter_t> &
op_schema_t::get_inputs() const {
return inputs_;
}
op_schema_t &op_schema_t::set_output(
size_t out_offset, std::string &&out_name, std::string &&dtype_string) {
verify_output_(out_offset);
outputs_.emplace_back(std::move(out_name), std::move(dtype_string));
return *this;
}
op_schema_t &op_schema_t::set_commutative_inputs(
const std::set<size_t> &inputs) {
assertm(inputs.size() == 2, "the number of commutative inputs must be 2");
assertm(num_inputs_.size() == 1 && *(num_inputs_.begin()) > 1,
"The number of inputs is at least two");
assertm(std::all_of(inputs.begin(), inputs.end(),
[&](size_t idx) { return idx < *(num_inputs_.begin()); }),
"each input value must be less than input numbers");
commutative_inputs_ = inputs;
return *this;
}
bool op_schema_t::is_commutative_inputs(
const size_t input0, const size_t input1) const {
return is_commutative_op() && commutative_inputs_.count(input0)
&& commutative_inputs_.count(input1);
}
bool op_schema_t::is_commutative_op() const {
return !commutative_inputs_.empty();
}
op_schema_t &op_schema_t::set_type_constraints(
std::string &&dtype_string, std::set<data_type_t> &&dtypes) {
op_parameter_dtype_map_[std::move(dtype_string)] = std::move(dtypes);
return *this;
}
const std::vector<op_schema_t::op_parameter_t> &
op_schema_t::get_outputs() const {
return outputs_;
}
op_schema_t &op_schema_t::set_attr(op_attr_t name, bool required,
attribute_kind_t attr_kind,
const std::vector<const char *> &candidates) {
assertm(attributes_.count(name) == 0,
"provided attribute has already been set");
std::vector<utils::attribute_value_t> candidates_tmp(candidates.size());
std::transform(candidates.begin(), candidates.end(), candidates_tmp.begin(),
[](const char *c) {
return utils::attribute_value_t {std::string(c)};
});
attributes_[name]
= attribute_t(name, required, attr_kind, std::move(candidates_tmp));
return *this;
}
op_schema_t &op_schema_t::set_attr(op_attr_t name, bool required,
attribute_kind_t attr_kind, const char *value,
const std::vector<const char *> &candidates) {
assertm(attributes_.count(name) == 0,
"provided attribute has already been set");
std::vector<utils::attribute_value_t> candidates_tmp(candidates.size());
std::transform(candidates.begin(), candidates.end(), candidates_tmp.begin(),
[](const char *c) {
return utils::attribute_value_t {std::string(c)};
});
attributes_[name] = attribute_t(name, required, attr_kind,
utils::attribute_value_t {std::string(value)},
std::move(candidates_tmp));
return *this;
}
const std::unordered_map<op_attr_t, op_schema_t::attribute_t> &
op_schema_t::get_attrs() const {
return attributes_;
}
op_schema_t &op_schema_t::set_shape_inference_function(shape_infer_fn fn) {
tensor_inference_function_ = std::move(fn);
return *this;
}
shape_infer_fn op_schema_t::get_shape_inference_function() const {
return tensor_inference_function_;
}
op_schema_t &op_schema_t::set_op_def_constraint_function(
op_def_constraint_fn fn) {
op_def_constraint_functions_.emplace_back(std::move(fn));
return *this;
}
std::vector<op_def_constraint_fn>
op_schema_t::get_op_def_constraint_functions() const {
return op_def_constraint_functions_;
}
op_schema_t &op_schema_t::set_additional_item(
const std::string &key, const utils::any_t &value) {
additional_items_map_.insert({key, value});
return *this;
}
const utils::any_t &op_schema_t::get_additional_item(
const std::string &key) const {
auto it = additional_items_map_.find(key);
assertm(it != additional_items_map_.end(), "don't have such item");
return it->second;
}
bool op_schema_t::has_additional_item(const std::string &key) const {
return additional_items_map_.count(key);
}
bool op_schema_t::verify_param_num(size_t actual_num,
const std::set<size_t> &expected_num, param_num_option option) const {
switch (option) {
case param_num_option::fixed: {
if (expected_num.size() != 1
|| expected_num.find(actual_num) == expected_num.end()) {
return false;
}
} break;
case param_num_option::optional: {
if (expected_num.find(actual_num) == expected_num.end())
return false;
} break;
case param_num_option::variadic: {
if (expected_num.size() != 2) return false;
auto lower = expected_num.begin();
auto upper = lower++;
if (*lower > *upper) {
upper = expected_num.begin();
lower = upper++;
}
if (*lower > actual_num || *upper < actual_num) return false;
} break;
default: return false;
}
return true;
}
bool op_schema_t::verify_param_dtype(
const std::vector<std::shared_ptr<value_t>> &actual_values,
const std::vector<op_schema_t::op_parameter_t> &expected_params,
param_num_option option,
std::unordered_map<std::string, std::set<data_type_t>>
&dtype_constraints) const {
size_t offset = 0;
for (size_t i = 0; i < actual_values.size(); ++i) {
const auto &v = actual_values.at(i);
const logical_tensor_t < = v->get_logical_tensor();
const std::string &dtype_string = expected_params[offset].dtype_string_;
if (dtype_string == "any") continue;
std::set<data_type_t> &expected_dtypes
= dtype_constraints[dtype_string];
VCONDCHECK(graph, create, check, add_op,
(expected_dtypes.find(lt.data_type) != expected_dtypes.end()),
false, "%s,given data type for input%zu is %s v.s. expected %s",
op_t::kind2str(op_schema_t::get_op_kind()).c_str(), i,
utils::data_type2str(lt.data_type),
utils::set2str(expected_dtypes, utils::data_type2str).c_str());
if (expected_dtypes.size() != 1) {
dtype_constraints[dtype_string] = {lt.data_type};
}
if (option != param_num_option::variadic) { offset += 1; }
}
return true;
}
bool op_schema_t::verify_attributes(
const std::unordered_map<op_attr_t, utils::attribute_value_t>
&actual_attrs,
const std::unordered_map<op_attr_t, attribute_t> &expected_attrs,
bool check_undefined_attrs) const {
for (const auto &elem : expected_attrs) {
VCONDCHECK(graph, create, check, add_op,
!(elem.second.required_ && actual_attrs.count(elem.first) == 0),
false, "%s,attribute %s is required but not set",
op_t::kind2str(op_schema_t::get_op_kind()).c_str(),
op_t::attr2str(elem.first).c_str());
}
for (const auto &elem : actual_attrs) {
const op_attr_t attr_name = elem.first;
if (expected_attrs.count(attr_name) == 0) continue;
VCONDCHECK(graph, create, check, add_op,
(elem.second.get_kind()
== expected_attrs.at(attr_name).attr_kind_),
false, "%s,attribute %s has invalid type",
op_t::kind2str(op_schema_t::get_op_kind()).c_str(),
op_t::attr2str(elem.first).c_str());
const auto &candidates
= expected_attrs.find(attr_name)->second.candidates_;
if (!candidates.empty()) {
VCONDCHECK(graph, create, check, add_op,
(std::find(
candidates.begin(), candidates.end(), elem.second)
!= candidates.end()),
false, "%s,attribute %s has invalid value",
op_t::kind2str(op_schema_t::get_op_kind()).c_str(),
op_t::attr2str(elem.first).c_str());
}
}
if (check_undefined_attrs) {
for (const auto &elem : actual_attrs) {
VCONDCHECK(graph, create, check, add_op,
(expected_attrs.count(elem.first) != 0), false,
"%s,attribute %s is not defined in spec",
op_t::kind2str(op_schema_t::get_op_kind()).c_str(),
op_t::attr2str(elem.first).c_str());
}
}
return true;
}
void op_schema_t::set_default_attribute(op_t *l_op) const {
const std::unordered_map<op_attr_t, utils::attribute_value_t> &actual_attrs
= l_op->get_attributes();
const std::unordered_map<op_attr_t, op_schema_t::attribute_t>
&expected_attrs = this->get_attrs();
for (auto iter = expected_attrs.begin(); iter != expected_attrs.end();
++iter) {
if (iter->second.has_default_value_
&& actual_attrs.count(iter->first) == 0) {
utils::attribute_value_t value = iter->second.attr_;
op_attr_t name = iter->first;
l_op->set_attr(name, value);
}
}
}
bool op_schema_t::verify(const op_t *l_op, bool check_undefined_attrs) const {
size_t actual_num_inputs = l_op->num_inputs();
std::set<size_t> expected_num_inputs = get_num_inputs();
bool param_num_verify_result = verify_param_num(
actual_num_inputs, expected_num_inputs, inputs_option);
VCONDCHECK(graph, create, check, add_op, (param_num_verify_result), false,
"%s,given num inputs %zu v.s. expected %s",
op_t::kind2str(op_schema_t::get_op_kind()).c_str(),
actual_num_inputs, utils::set2str(expected_num_inputs).c_str());
bool param_dtype_verify_result = true;
std::unordered_map<std::string, std::set<data_type_t>> dtype_constraints
= op_parameter_dtype_map_;
param_dtype_verify_result = verify_param_dtype(l_op->get_input_values(),
inputs_, inputs_option, dtype_constraints);
if (!param_dtype_verify_result) { return false; }
size_t actual_num_outputs = l_op->num_outputs();
std::set<size_t> expected_num_outputs = get_num_outputs();
param_num_verify_result = verify_param_num(
actual_num_outputs, expected_num_outputs, outputs_option);
VCONDCHECK(graph, create, check, add_op, (param_num_verify_result), false,
"%s,given num outputs %zu v.s. expected %s",
op_t::kind2str(op_schema_t::get_op_kind()).c_str(),
actual_num_outputs, utils::set2str(expected_num_outputs).c_str());
param_dtype_verify_result = verify_param_dtype(l_op->get_output_values(),
outputs_, outputs_option, dtype_constraints);
if (!param_dtype_verify_result) { return false; }
const auto &attrs = l_op->get_attributes();
bool attr_verify_result
= verify_attributes(attrs, attributes_, check_undefined_attrs);
if (!attr_verify_result) { return false; };
auto op_def_constraint_funcs = get_op_def_constraint_functions();
bool additional_verify_result = true;
for (auto &op_def_fn : op_def_constraint_funcs) {
additional_verify_result = op_def_fn(l_op);
if (!additional_verify_result) { return false; }
}
return true;
}
status_t op_schema_t::shape_infer(op_t *n,
std::vector<logical_tensor_t *> &inputs,
std::vector<logical_tensor_t *> &outputs) const {
shape_infer_fn fn = get_shape_inference_function();
return fn(n, inputs, outputs);
}
size_t op_schema_t::get_max_valid_param_num(
const std::set<size_t> ¶m_num, param_num_option option) const {
size_t max_valid_num = 0;
if (option == param_num_option::fixed
|| option == param_num_option::optional) {
if (!param_num.empty()) { max_valid_num = *param_num.rbegin(); }
} else {
max_valid_num = std::numeric_limits<size_t>::max();
}
return max_valid_num;
}
void op_schema_t::verify_input_(size_t in_offset) {
assertm(inputs_offset.find(in_offset) == inputs_offset.end(),
"provided `in_offset` has already been set");
inputs_offset.insert(in_offset);
size_t max_valid_num = get_max_valid_param_num(num_inputs_, inputs_option);
assertm(max_valid_num > 0, "input set before setting num_inputs_");
assertm(in_offset < max_valid_num,
"input offset exceeds declared num of inputs");
UNUSED(max_valid_num);
}
void op_schema_t::verify_output_(size_t out_offset) {
assertm(outputs_offset.find(out_offset) == outputs_offset.end(),
"provided `out_offset` has already been set");
outputs_offset.insert(out_offset);
size_t max_valid_num
= get_max_valid_param_num(num_outputs_, outputs_option);
assertm(max_valid_num > 0, "output set before setting num_outputs_");
assertm(out_offset < max_valid_num,
"output offset exceeds declared num of outputs");
UNUSED(max_valid_num);
}
op_schema_t &op_schema_t::set_inputs_option(param_num_option option) {
inputs_option = option;
return *this;
}
op_schema_t::param_num_option op_schema_t::get_inputs_option() const {
return inputs_option;
}
op_schema_t &op_schema_t::set_outputs_option(param_num_option option) {
outputs_option = option;
return *this;
}
op_schema_t::param_num_option op_schema_t::get_outputs_option() const {
return outputs_option;
}
op_schema_registry_t::op_schema_registry_once_t::op_schema_registry_once_t(
op_schema_t &&schema) {
op_kind_version_schema_map &op_map
= get_map_without_ensuring_registration();
const op_kind_t kind = schema.get_op_kind();
opset_version op_version = schema.get_since_version();
get_rw_mutex().lock_write();
op_map[kind].insert(std::pair<opset_version, op_schema_t &&>(
op_version, std::move(schema)));
get_rw_mutex().unlock_write();
}
op_kind_version_schema_map &
op_schema_registry_t::get_map_without_ensuring_registration() {
static op_kind_version_schema_map op_map;
return op_map;
}
impl::utils::rw_mutex_t &op_schema_registry_t::get_rw_mutex() {
static impl::utils::rw_mutex_t mutex;
return mutex;
}
op_kind_version_schema_map &op_schema_registry_t::get_map() {
op_kind_version_schema_map &op_map
= get_map_without_ensuring_registration();
class register_opset_t {
public:
register_opset_t() { register_opset_schema(); }
};
static register_opset_t ro;
return op_map;
}
const op_schema_t *op_schema_registry_t::get_op_schema(op_kind_t kind) {
auto &op_map = get_map();
op_schema_t *schema = nullptr;
get_rw_mutex().lock_read();
if (op_map.count(kind)) { schema = &op_map[kind].rbegin()->second; }
get_rw_mutex().unlock_read();
return schema;
}
void register_schema(op_schema_t &&schema) {
op_schema_registry_t::op_schema_registry_once_t DNNL_GRAPH_UNUSED
registration(std::move(schema));
}
} } }