Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[draft] add registry for Op, OpProto and OpAttrChecker #2739

Merged
merged 11 commits into from
Jul 7, 2017

Conversation

JiayiFeng
Copy link
Collaborator

@JiayiFeng JiayiFeng commented Jul 4, 2017

This is a draft to show our design about how to implement registry mechanisms of Op, OpProto and OpAttrChecker.

For convenience, a small part of attr_checker.h is written with pseudocode. It is indicated by comments ==pseudocode begin== and ==pseudocode end==.

};

class OpRegistry {
typedef std::function<OperatorBase*(OpDesc& op_desc)> OpCreator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const OpDesc& ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In TypedAttrChecker::operator(), we check attributes and complete default value, so the OpDesc will be mutated in inner checkers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, OpDesc& -> OpDesc* better ?

ProtoMakerType(&op_proto, &op_checker);
}

static OpBase* CreateOp(const std::string& op_type, OpDesc op_desc) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const OpDesc& ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above~

}
};

#define REGISTER_OP(op_class, op_maker_class, op_type) \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

op_class -> __op_class ? other arguments are the same
a different name method to indicate these arguments are used in macro.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Thanks!

}
};

REGISTER_OP(CosineOp, CosineOpProtoAndCheckerMaker, "cos");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cos -> cos_sim or cossim ?

bool operator()(OpDesc& op_desc) const {
//========= pseudocode begin ==========//
// check whether attr need default value;
if (!op_disc.has_fild(attr_name_)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

op_disc -> op_desc?

class LargerThanChecker {
public:
LargerThanChecker(T lower_bound) : lower_bound_(lower_bound) {}
bool operator()(const T& value) const { return value > lower_bound_; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe bool is not enough to give the user a details information.

There can be two options:

  • Use PADDLE_ENFORCE and return void.
  • Use std::string as return value. An empty string represents OK, others represent Errors.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think the interface of this function could be

(T* attr, bool alreadySet) -> void

And then the Default can be a function, too. Like

[](T* attr, bool alreadySet) {
  if (!alreadySet) {  // not set by user
    *attr = xxx;  // set by us.
  }
}

and Required can be a function, too.

[](T* attr, bool alreadySet) {
  if (!alreadySet) {  // not set by user
    return "error!!";
  }
}

private:
std::string attr_name_;
std::vector<ValueChecker> value_checkers_;
bool has_default_;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only Default is needed, but also Required is needed too.

private: \
const static OpRegisterHelper<#op_class, #op_maker_class> reg; \
}; \
const Register op_class##Register::reg(#op_type);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should move paddle/util/InitFunction here.

@reyoung
Copy link
Collaborator

reyoung commented Jul 5, 2017

既然我们从protobuf解析出Attribute的用来传递给C++的OpBase。并且在解析完毕传递之前,需要做一些约束检查。

那么,我们为啥不先将Protobuf中的所有Attributes解析成标准C++类型,以便之后进一步处理。而约束检查函数都以C++类型为输入类型就好。

可能的C++类型为:

// boost::variant can be replaced by our implementaiton
using Attribute = boost::variant<boost::blank,  int, float, std::string, std::vector<int>, std::vector<float>, std::vector<std::string>>;
using AttributeMap = std::unordered_map<std::string, Attribute>;
using AttributeConstraint = std::function<void(Attribute* attr)>;

那么,在注册OpCreator的时候,注册的函数可以为:

std::unordered_map<string, std::vector<AttributeConstraint>> attr_constraints_;
auto creator = [] (const OpDesc& desc) {
  AttributeMap attrs = ParseProtoAttrs(desc.attrs());
  for (auto it = attr_constraints_.begin(); it != attr_constraints_.end(); ++it) {
     auto attr_name = it->first;
     auto& constraints = it->second;
     for (auto& cons : constraints) {
        cons(&attrs[attr_name]);  // if attr_name is not exist, a boost::variant<blank> will be created.
     }
  }

  return new OpBase(desc.inputs(), desc.outputs(), attrs);  // just pass attrs to OpBase.
}

这样做的好处是:

  1. 解析检查参数的过程注册在了OpCreator中,对于Op的Developer和框架的使用者,都是透明的。
  2. 最开始就讲protobuf的Attribute转换成C++类型,后续的读取,查找操作都可以保证时间复杂度很小。
  3. 简化了AttributeConstraint或者Checker的设计,将其变成一个很简单的,可以读写Attribute函数。

这个实现依赖了boost::variant, 不过我们可以给用户传一个Attributes的接口,进而隔绝Op开发者直接使用boost::variant。譬如

class Attributes {
public:
  // also, maybe argument use `AttributeMap` is better than `const T&`, because compiler might RVO
  Attributes(AttributeMap attrs): attrs_(attrs) {}

  template<typename T>
  T Get(const std::string& name) const;

private:
  AttributeMap attrs_;
};
class OpBase {
public:
  // In Op, user can only use Attributes::Get method.
  // We can implement Attribute by boost::variant firstly, then change to our implementation,
  // if we think boost::variant is heavy.
  OpBase(std::unique_ptr<Attributes> attrs) {}
};

@JiayiFeng
Copy link
Collaborator Author

JiayiFeng commented Jul 5, 2017

仔细想了一下 @reyoung 提出的建议。和我目前的代码实现的主要差异在于:

Attribute从Protobuf解析出来后会以boost::variant封装,以兼容多种可能的类型。在我的实现中,Attribute先从boost::variant中取出,转化成确定类型的值,再接受对应类型的各项checker的检查。在 @reyoung 的建议中,checker写成boost的visitor的形式,因此可以直接接受boost::variant类型的Attribute,无需确定类型。

两种方法最后的代码实现应该没有什么大的差别,就是底层checker是写成模板类还是boost visitor的区别。

除此之外, @reyoung 还建议将default的设置也抽取出来做成一个checker,以及用PADDLE_ENFORCE报错,这对我很有启发性,我稍后修改一下

…Map` is a unordered_map of <string, Attribute>, and `Attribute` is a boost::variant object to hold multiple types of attribute value.

2. Use `PADDLE_ENFORCE` to print checkers' fail message.
3. Abstract default value operations to a new function: `DefaultChecker`.
}
T& attr_value = boost::get<T>(attr);
for (const auto& checker : value_checkers_) {
checker(attr_value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The advantage of this design is checker contains a template parameter T.

private:
std::string attr_name_;
std::vector<ValueChecker> value_checkers_;
std::vector<ValueChecker> default_checker_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default_checker_ is a little confusing, maybe default_setter_ would be better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

void operator()(AttributeMap& attr_map) const {
Attribute& attr = attr_map.at(attr_name_);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// an attribute can have more than one limits
template <typename T>
class TypedAttrChecker {
typedef std::function<void(const T&)> ValueChecker;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not important, but using is better than typedef.

http://blog.csdn.net/big_yellow_duck/article/details/52224068

public:
TypedAttrChecker(const std::string& attr_name) : attr_name_(attr_name) {}

TypedAttrChecker& LargerThan(const T& lower_bound) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should provide a base API named

AddChecker(ValueChecker checker) {
  checkers_.push_back(checker);
  return *this;
}

Other API will invoke AddChecker

Copy link
Collaborator Author

@JiayiFeng JiayiFeng Jul 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe AddCustomChecker has done the same thing?

TypedAttrChecker& AddCustomChecker(const ValueChecker& checker) {
  value_checkers_.push_back(checker);
  return *this;
}

return *(checker.target<TypedAttrChecker<T>>())
}

void Check(AttributeMap& attr_map) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe Check could mark to const?

}

void Check(AttributeMap& attr_map) {
for (const auto& checker : attr_checkers_) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, const is not need if Check is marked as const

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

private: \
const static OpRegisterHelper<#__op_class, #__op_maker_class> reg; \
}; \
const Register __op_class##Register::reg(#__op_type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the meaning of this line? const Register?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

miss type... It should be OpRegisterHelper<#__op_class, #__op_maker_class>

1. Complete the development of interfaces between OpRegistry and
Protobuf.
2. Add unit test for op_registry.h
Copy link
Member

@jacquesqiao jacquesqiao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jacquesqiao jacquesqiao merged commit 1d2ef1d into PaddlePaddle:develop Jul 7, 2017
@JiayiFeng JiayiFeng deleted the dev_registry branch July 7, 2017 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants