-
Notifications
You must be signed in to change notification settings - Fork 7
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
Reduce the work to add a new expr: unify the structure of exprs #2190
Conversation
@@ -8,74 +8,6 @@ namespace jit { | |||
namespace fuser { | |||
namespace cuda { | |||
|
|||
//! Clone an IR node, forwarding the arguments to the IrCloner constructor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to header file
@@ -15,20 +16,6 @@ class Kernel; | |||
|
|||
class IrCloner; | |||
|
|||
// Passkey for builder to register properties with statements, and to call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to a separate file torch/csrc/jit/codegen/cuda/ir_builder_key.h
so it can be included in torch/csrc/jit/codegen/cuda/ir_base_nodes.h
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I did that too in #2197 😄
// Creates a new instance of the expression with all its field copied. | ||
// Note that unlike IrCloner, this function only do a shallow copy | ||
virtual Expr* shallowCopy() const = 0; | ||
Expr* shallowCopy() const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method now is not even virtual
#define DECLARE_CLONE \ | ||
virtual Statement* clone(IrCloner* ir_cloner) const override; | ||
|
||
#define DEFINE_CLONE(ClassName) \ | ||
Statement* ClassName::clone(IrCloner* ir_cloner) const { \ | ||
return IrBuilder::clone(this, ir_cloner); \ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we undef these somewhere? Otherwise, we should use names like PYTORCH_NVFUSER_DECLARE_CLONE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. These macros will be used in cpp files. I will rename it as NVFUSER_DECLARE_CLONE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
//! to hold non-IR data, such as DataType, std::vector<int>, etc. Please don't | ||
//! use this class to hold IR nodes or their pointers. | ||
template <typename T> | ||
class TORCH_CUDA_CU_API PlainVal : public Val { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is intended to hold non-IR data, but then why is it a subclass of Val
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is used to hold IR node attributes. Does it need to be a subclass of Val
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because attributes_
are vectors of Val*
and I want to push plain values into attributes_
. Maybe I should change attributes_
to a vector of Statement*
? Currently, lots of kernel IR has expr members, and I am storing them as something like PlainVal<Statement*>
; maybe making attributes_
a vector of Statement*
would make it easier because I can directly store it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. Yeah, we want to store proper IR-node values as well. Can it be std:vector<std::any>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not today. std::any
is C++17 but PyTorch now is still using C++14
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right. Can it be a vector of unique_ptr
?
I just feel odd to have arbitrary attributes, like a DataType
and a bool flag, as a Val
. Please convince me if you really want to use PlainVal
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the biggest advantage of making it a Val
is that, IrCloner
will automatically help you do deep copy when necessary. For example, if I have an attribute bool
, and I clone the expr, then I will also have a new PlainVal<bool>
object for the new expr. If later, I changed the new expr's bool attribute, the old one's bool attribute is not changed. But if we manage this by hand, we could make mistakes that, when cloning an expr, the old and new expr are pointing to the same object for its attribute, and if we change one, the other is also changed.
So to answer your question:
Can it be a vector of
unique_ptr
?
Yes. Actually, it SHOULD be a unique_ptr
instead of a shared_ptr
, for the reason I described above.
But without using PlainVal
, we will have to manually do a deep copy of the std::vector<std::unique_ptr<AbstractExprAttribute>> attributes_
. It's a little inconvenient, but should be more efficient.
I don't have a strong preference about which one we should choose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, wait, no. It is not "a little inconvenient", but super convenient. Without knowing T
, we can not do a deep copy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that makes sense. (I assume you meant "super inconvenient")
Let's use PlainVal
, but I'd prefer a name like Attribute
, which I think a little more clearly implies what it's supposed to be used for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
//! to hold non-IR data, such as DataType, std::vector<int>, etc. Please don't | ||
//! use this class to hold IR nodes or their pointers. | ||
template <typename T> | ||
class TORCH_CUDA_CU_API PlainVal : public Val { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is used to hold IR node attributes. Does it need to be a subclass of Val
?
const std::vector<Val*>& initVals() const { | ||
return init_vals_; | ||
std::vector<Val*> initVals() const { | ||
return {attributes().begin() + 2, attributes().end()}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit concerning that we need to make sure we use correct offsets in the attribute vector. It seems like we are effectively losing static type safety, although the overall IR classes are more consistent.
Given that typically there are not many attributes in each class, I don't think the above concern is critical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks for the refactoring.
I added a new data member to
Expr
:std::vector<Val*> attributes_
. Subclasses ofExpr
are now not allowed to have their own data member in the class. Instead, if a data member is neither an input nor an output, then the subclass should register it as an attribute. Some subclass ofExpr
needs to store plain data that is not aVal
, such asDataType
,bool
, etc. These plain data are notVal
, therefore impossible to store as an attribute. To work around this, I created a classtemplate <typename T> class Attribute : public Val
which can be used to store data of typeT
.By following the contract that subclass of
Expr
must store data inside inputs, outputs, or attributes, every expr type can be constructed with the following ctor:Expr(IrBuilderPasskey, std::vector<Val*> inputs, std::vector<Val*> outputs, std::vector<Val*> attributes);
And IR cloning and shallow copying now become independent of expr type. Now adding a new expr type does not need to manually write down the cloning constructor,
sameAs
, andshallowCopy
. They can all be inherited fromExpr
. This is a big save that helps me to reduce ~800 lines of code.I also refactored
IrCloner
to move the type dispatch as a member function.I believe that with my change in this PR, we can also greatly simplify IR mutator. But I will not do it in this PR.
I recommend starting the review from
torch/csrc/jit/codegen/cuda/ir_base_nodes.h
.