-
-
Notifications
You must be signed in to change notification settings - Fork 183
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
Adds require_* template type traits #1344
Conversation
…stable/2017-11-14)
…into feature/require-templates
…f the meta functions
(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.0) |
…xp1~20180509124008.99 (branches/release_50)
Alright so all the tests are passing so I think this is ready for a general discussion. Should this be broken up into multiple PRs? The new code is only about 500 lines while the rest are tests that are pretty copy/pasta |
@syclik I'm tagging you because I'm not to sure how to move forward |
Thanks so much for the amazingly well documented PR. I agree it's important to eliminate copying and believe this is the right, modern C++ way to do it. I'm pretty sure we don't have any pointer types anywhere where we'd use Does P.S. That's "et voila" when you get to France :-) |
Thanks!! Before the PR is merged I'll probably add this PRs summary to either the docs or the wiki.
I can make this in a separate PR
It's only to primitives. I still can't decide whether we would want a (2) removes the
I'll be caché'ing as we et voila across Paris ;-) |
(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 0.98) |
…gs/RELEASE_500/final)
…into feature/require-templates
…stable/2017-11-14)
(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.02) |
…perfect forwarding
…into feature/require-templates
(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.0) |
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.
While you fixed what I asked for in previous reviews, there are some new changes I think need some fixes.
using require_all_not_same_t | ||
= require_all_not_t<std::is_same<std::decay_t<T>, std::decay_t<Types>>...>; |
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 name is a bit ambiguous. It could suggests this requires that at least one of types is different than others or that all types are different. I think a better name for what this does this would be require_any_not_same_t
.
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 think I flat out need to rethink how same works here. Reading it now it checks that the Types
match with just T
, but it really should check that each type is the same. If i figure that out I think this will make more sense / I can have an any
for same as well
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.
Actually I think just changing it to any
makes sense (also i don't feel like writing out the more complicated thing which I'm not sure we would use that much)
stan/math/opencl/matrix_cl.hpp
Outdated
cl::Context& ctx = opencl_context.context(); | ||
cl::CommandQueue& queue = opencl_context.queue(); | ||
// creates the OpenCL buffer to copy the Eigen | ||
// matrix to the OpenCL device | ||
buffer_cl_ = cl::Buffer(ctx, CL_MEM_READ_WRITE, sizeof(T) * size()); | ||
cl::Event write_event; |
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.
Why did you move this outside the loop? We need to add all write events, not just the last one.
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 yes! So, do we? I think it depends on whether OpenCL does async when it writes to an individual buffer or if writes to a single buffer happen sequentially. Maybe there is not a guarantee around that. If I can find something in the docs that shows that is true then I'll revert this and else we can discuss 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.
Yeah I think we do, fixed this
stan/math/opencl/matrix_cl.hpp
Outdated
queue.enqueueWriteBuffer(buffer_cl_, CL_FALSE, | ||
sizeof(double) * offset_size, | ||
sizeof(double) * rows_, A[i].data(), | ||
&this->read_events(), &write_event); |
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 is a constructor - this matrix was not used before. So it has no read events and this change is unnecessary.
stan/math/opencl/matrix_cl.hpp
Outdated
@@ -324,7 +324,7 @@ class matrix_cl<T, enable_if_arithmetic<T>> { | |||
buffer_cl_ = cl::Buffer(ctx, CL_MEM_READ_WRITE, sizeof(T) * A.size()); | |||
cl::Event transfer_event; | |||
queue.enqueueWriteBuffer(buffer_cl_, CL_FALSE, 0, sizeof(T) * A.size(), | |||
A.data(), nullptr, &transfer_event); | |||
A.data(), &this->read_events(), &transfer_event); |
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.
Again there are no read events in a new matrix.
stan/math/opencl/matrix_cl.hpp
Outdated
queue.enqueueWriteBuffer(buffer_cl_, CL_FALSE, 0, sizeof(T), &A, NULL, | ||
&transfer_event); | ||
queue.enqueueWriteBuffer(buffer_cl_, CL_FALSE, 0, sizeof(T), &A, | ||
&this->read_events(), &transfer_event); |
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.
No read events in a new matrix
stan/math/opencl/matrix_cl.hpp
Outdated
@@ -377,7 +378,7 @@ class matrix_cl<T, enable_if_arithmetic<T>> { | |||
buffer_cl_ = cl::Buffer(ctx, CL_MEM_READ_WRITE, sizeof(T) * A.size()); | |||
cl::Event transfer_event; | |||
queue.enqueueWriteBuffer(buffer_cl_, CL_FALSE, 0, sizeof(T) * A.size(), | |||
A.data(), nullptr, &transfer_event); | |||
A.data(), &this->read_events(), &transfer_event); |
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.
Again
stan/math/opencl/matrix_cl.hpp
Outdated
@@ -406,7 +408,7 @@ class matrix_cl<T, enable_if_arithmetic<T>> { | |||
buffer_cl_ = cl::Buffer(ctx, CL_MEM_READ_WRITE, sizeof(T) * size()); | |||
cl::Event transfer_event; | |||
queue.enqueueWriteBuffer(buffer_cl_, CL_FALSE, 0, sizeof(T) * size(), A, | |||
NULL, &transfer_event); | |||
&this->read_events(), &transfer_event); |
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.
Again
Dope! I want to make sure the doc you wrote up here:
I don’t think you need more doc, I just think it’d be a shame if it was buried in this PR and we’d have to find the PR to know what happened. Would it make sense to use something like doygen’s modules to put your text into the codebase and have it render like this: https://mc-stan.org/math/modules.html ? One question about naming (this is a question, not a suggestion or required change): you’ve got “not” mixed in with “or” like |
Ty!
Yes def, I've been thinking about that more and more, it would be nice to have modules for
I'll look into this later today
Yeah it's a good Q, naming things is hard! I wanted to keep all of the names the same to avoid gotchas, but I def agree that |
(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.0) |
…xp1~20180509124008.99 (branches/release_50)
…stable/2017-11-14)
@t4c1 Alrighty! I think I addressed all your review comments. I switched @syclik do you mind if I add the doxygen module in a separate PR? This one is already v big. |
…stable/2017-11-14)
…gs/RELEASE_500/final)
…gs/RELEASE_500/final)
(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 0.99) |
Alrighty! @t4c1 I did a look over the code and think I got all of your review comments and this is passing so if you want to have a look over I think it's good to go! |
Sorry -- missed the notification earlier. If it's not going into this PR,
then can you open a new issue with enough detail on what needs to be done?
Maybe someone else can tackle the doc or at the very least, it'll remind
you that there's outstanding work to be done on this PR.
…On Tue, Oct 8, 2019 at 6:05 PM Steve Bronder ***@***.***> wrote:
Alrighty! @t4c1 <https://github.com/t4c1> I did a look over the code and
think I got all of your review comments and this is passing so if you want
to have a look over I think it's good to go!
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1344?email_source=notifications&email_token=AADH6FYVZHOO77U7QLVVR3LQNT723A5CNFSM4IUQ57SKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAVYPAA#issuecomment-539723648>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AADH6F6NJLF7ECI5IMPOUULQNT723ANCNFSM4IUQ57SA>
.
|
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! Will someone else look this over or shall we merge?
Gonna do it today! |
Summary
Out of the 2.8K of line additions 1.6K are tests. Most of the other changes are changing
enable_if
torequire
. The only files I would say have meaningful changes/additions areis_matrix_cl
matrix_cl
is used as a test bed to show some of the features belowmatrix_cl scalar_type
givesscalar_type_t
formatrix_cl
matrix_cl value_type
givesvalue_type_t
for matrix_clrequire_generics.hpp
Holds all of therequire
aliases explained belowAdds higher order functions for enabling and disabling functions through type traits. This new type system is what I'm going to call "legacy concepts" aka I think the closest thing you can get to C++20 concepts without having the
requires
keyword.Legacy concepts are done through
enable_if_t
aliases which are calledrequire_*
. There are 5 basic types of requires.requires_*_t
: This means the function requires a certain type to turn on. For instancerequire_not_*_t
: When we want to disable for a typerequire_all_*_t
: Takes a parameter pack of types to enable if all types satisfy the check.require_any_*_t
: Takes a parameter pack of types to enable if any of the types satisfy the check.require_not_any_*_t
: Takes a parameter pack of types to enable if any one of the types are not satisfied.require_not_all_*_t
: Takes a parameter pack of types to enable if all of the types are not satisfied.The above are built out for the meta programs to detect
in addition,
std::vector
andEigen
types have additionalrequires
to detect if thevalue_type
(the first underlying type) or thescalar_type
(the containers underlying scalar type) satisfy a condition to enable a class or function.The container
requires
have a_vt
and_st
to symbolize the above. A function that accepts eigen matrices of whose whose value type is floating point types can be defined asA function that accepts standard vectors of Eigen vectors whose scalar type is floating point types can be defined as
Background
I'd like to start using perfect forwarding semantics in the math library. There's a few reasons for this
A lot of folks (including me) are looking to make Stan more performant through parallelization, but I think there is still a lot of money on the table when it comes to how we move and manage our memory in single threaded execution.
If we want to add sparse matrices we either have to use more general templates in functions that can take in Eigen matrices or write overloads specializing on those. So for addition we would need signatures for adding sparse-sparse, sparse-dense, dense-sparse, and dense-dense matrices. That's not super fun, but the good news is that with more general templates (as perfect forwarding demands) we can support sparse matrices while getting better performance on the things we do now.
When using OpenCL with a cpu, if the Eigen type or vector coming in is a non const rvalue, we can pretty safely steal that memory to do a 'zero cost copy' and not pay any copy cost while getting the multicore performance.
You can see a blog post by Meyer's here that goes in depth on what perfect forwarding means and how to implement it I'll give a tl;dr below. But the tl;dr of the tl'dr is that using perfect forwarding semantics allow rvalues to be moved instead of copied to lvalues in the case of
const Blah&
in signatures, but perfect forwarding is very hard to implement correctly.This PR will make signatures like this
Look like this
Where
(1)
require_*_eigen_vt
is a template template alias aroundstd::enable_if_t
that checks whether each type is both derived from theEigenBase
class and has an underlying arithmetic value type. There are tworequires_*
for each container,require_*_vt
andrequire_*_st
wherevt
stands forvalue_type
andst
stands forscalar_type
. When methods use containers of containers we want to verify both the underlying container is the correct type and the scalar type of the container is the correct type. For example, if we accept a vector of eigen vectors with arithmetic scalars for a certain function this would have the template.(2) The
...
comes from this blog post and may seem a bit odd. But it's a clever way to force the requirement type trait to execute through parameter pack expansion. In the case of success the parameter packrequire_all_eigen_t<condition, Mat1, Mat2>...
has nothing in it and so nothing happens, but because we forced execution, failure will SFINAE out the signature for this function.(3) In the signature the types only have to satisfy the requirements of (1), meaning that they can be Eigen matrices, expressions of eigen matrices, const/nonconst, and lvalues or rvalue refererences. The && in the signature acts like an
or
statement with lvalue (&) being true rvalue (&&) being false. If the type coming in is an lvalue reference then the type signature doesMat1&& || &
produces anMat1&
. If the type coming in is an rvalue then the type signature doesMat1&& || &&
and producesMat1&&
.One other important note for this, while in the process of implimenting this, in functions like
add
that take in two Eigen expressions, we will have to surround the return statements with(return_type).eval()
and also perform an.eval()
before coefficient access. The.eval()
on return is necessary for now since all the other math library functions can only take in Eigen matrices. Once all the signatures in the library can take in general Eigen types we can take off the training wheels and get a lot of benefit from Eigen's buildup of expression templates though the stan math functions.(3) The
std::forward
in the call ofanother_func
allows the matrices to keep their value type as they are passed to the next function. It's important to note thatstd::forward
is used at the last call site before the end of the function. Ifm1
was an rvalue and we usedstd::forward
incheck_nan
then by the time we tried using it foranother_func
the memory would have been moved and the program would seg fault.So that's pretty cool, we work with pretty large objects so not having to make a copy in each signature is good! There's some bureaucracy around the process but overall things are not too rough.
Out of the 2300 new lines in this pr, 1500 lines of this PR are testing, while out of the other 800 or so I'd say only 150 lines are actually meaningful. The important lines are at the top of
require_generics.hpp
where we define our base aliases that are used in the rest of the PR.require_t
is an alias forenable_if_t
that takes in a type trait with a static member namedvalue
. it will enable or disable a signature depending on whethervalue
is true or false. The rest of those base aliases are just clever manipulations of that.require_not_t
beingstd::enable_if_t<!...>
.require_all_t
andrequire_any_t
are variadic templated aliases for enable if that useconjunction<...>
anddisjunction<...>
to turn on and off signatures depending on whether all (logical and) or any (logical or) of the type traits are satisfied, respectively.The weirdest addition in this PR is the requirement type traits for containers which I'll paste here and then explain. In order to write something simplish like
require_any_vector_t<is_var, Var1, Var2>
we need the followingcontainer_type_check_base
is atemplate <template template>
, this means it takes in two types that will be templated in the alias. So here bothContainerCheck
andTypeCheck
use the the template typeCheck
when they are defined in the alias. The typeTypeCheck
usesvalue_type_t
to extract the underlying type from the variadic template typesCheck
.Check
is a variadic template type mostly to deal with when eitherContainerCheck
orTypeCheck
have more than one template type in their arguments, though in most cases this is usually something of the formwhich is used frequently for partial specializations for type traits.
the struct alias
is_std_vector_check
is a partial specialization ofcontainer_type_check_base
,It takes in only one templated template type and sets the first as
is_std_vector
. We then finish things off by making the final alias that gives us our nice pretty requirementAnd voila! We can now hide away all the dirt while using the above to make a bunch of pretty
requires
methods, which tbh are just copy/pasted versions ofrequire_std_vector
To example what this looks like I've added the
requires
and perfect forwarding templates to thematrix_cl
constructor.Edit: Sadly this won't work yet on the stuff in Stan math since the Stan upstream library has very fixed signatures. So this PR will need to happen in math, then I'll have to jump over to the Stan repo and implement the new signature style there first
Tests
Tests exist for checking all of the higher order functions included in
require_generics.hpp
files and can be tested withThese tests use the utilities in
test/unit/math/require_utils.hpp
which contain helper structs to define testing for unary and variadicrequires
aliases as well as larger structs containing the actual google tests used for the individual tests.Side Effects
value type has also been moved from the math namespace to the stan namespace where the rest of the type traits exist.
Checklist
Math issue Update internals to use more modern c++ #1308
Copyright holder: Steve Bronder
the basic tests are passing
./runTests.py test/unit
)make test-headers
)make doxygen
)make cpplint
)the code is written in idiomatic C++ (lol) and changes are documented in the doxygen
the new changes are tested