-
-
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
[WIP] Reverse Mode For Static Matrix Multiplication #1884
[WIP] Reverse Mode For Static Matrix Multiplication #1884
Conversation
…4.1 (tags/RELEASE_600/final)
…4.1 (tags/RELEASE_600/final)
…4.1 (tags/RELEASE_600/final)
…4.1 (tags/RELEASE_600/final)
…4.1 (tags/RELEASE_600/final)
…4.1 (tags/RELEASE_600/final)
@SteveBronder Yo my hope by merging Any advice on what I should be doing to getting these things more in sync? |
Hmm, and now there are all these merge conflicts! Not sure I'm doing my gits correctly. |
tbh this is kind of why I wanted to do this in pieces. There's enough very dramatic changes here that when a bunch of stuff at one level changes it causes a bunch of conflicts in the larger branch. I think we should focus efforts on #1915 so then we can start the Eigen var PR and then the adj_jac_apply PR. |
Well I just know I'm not gonna understand #1915 until I know how it filters up to matrices and stuff. You think there might be a difference in rebase and merge here? |
Eh I'll just try it lol. |
Lemme look at this rq |
Huh seems fine, tbh just pulled it down and |
I wonder if I was merging in my local version of your branch or something? But it seems like that wouldn't have given me conflicts twice. Oh well. |
¯\_(ツ)_/¯ |
@bbbales2 moving this convo over from #1915 the convo here I think we could do something like the below where (T1 and T2 are /**
* Deduces the return type for matrix multiplication of two types
*/
template <typename T1, typename T2, typename = void>
struct mat_mul_return_type {};
// arithmetic is just double
template <typename T1, typename T2>
struct mat_mul_return_type<T1, T2, require_all_arithmetic_t<T1, T2>> {
using type = double;
};
template <typename T1, typename T2>
struct mat_mul_return_type<T1, T2, require_any_eigen_t<T1, T2>> {
using type = decltype((std::declval<T1>() * std::declval<T2>()).eval());
};
// helper alias
template <typename T1, typename T2>
using mat_mul_return_t = typename mat_mul_return_type<T1, T2>::type;
template <typename T1, typename T2>
class multiply_vari<T1, T2, require_all_var_t<T1, T2>>
final : public op_vari<mat_mul_return_t<value_type_t<T1>, value_type_t<T2>>, vari_type_t<T2>*, vari_type_t<T2>*> {
using op_vari<mat_mul_return_t<value_type_t<T1>, value_type_t<T2>>, vari_type_t<T2>*, vari_type_t<T2>*>::avi;
using op_vari<mat_mul_return_t<value_type_t<T1>, value_type_t<T2>>, vari_type_t<T2>*, vari_type_t<T2>*>::bvi;
using lhs_type = vari_type_t<T1>;
using rhs_type = vari_type_t<T2>;
using return_t = mat_mul_return_t<value_type_t<T1>, value_type_t<T2>>;
public:
multiply_vari(lhs_type* avi, rhs_type* bvi)
: op_vari<return_t, lhs_type*, rhs_type*>(avi->val_ * bvi->val_, avi, bvi) {}
template <typename TT1 = T1, typename TT2 = T2,
require_all_var_vt<std::is_arithmetic, TT1, TT2>* = nullptr>
inline void chain_impl() {
avi()->adj_ += bvi()->val_ * this->adj_;
bvi()->adj_ += avi()->val_ * this->adj_;
}
template <typename TT1 = T1, typename TT2 = T2,
require_all_var_vt<is_eigen, TT1, TT2>* = nullptr>
inline void chain_impl() {
avi()->adj_ += this->adj_ * bvi()->val_.transpose();
bvi()->adj_ += avi()->val_.transpose() * this->adj_;
}
void chain() {
if (unlikely(is_any_nan(avi()->val_, bvi()->val_))) {
fill(avi()->adj_, NOT_A_NUMBER);
fill(bvi()->adj_, NOT_A_NUMBER);
} else {
chain_impl();
}
}
};
template <typename T1, typename T2, require_all_var_t<T1, T2>* = nullptr>
inline auto operator*(const T1& a, const T2& b) {
using multiply_type = internal::multiply_vari<T1, T2>;
// store the return type multiply_vari
using mat_return = typename multiply_type::return_t;
return var_value<mat_return>(new multiply_type(a.vi_, b.vi_)};
} We could try to simplify this with something like a default template with a |
From @bbbales2 comment here wrt to template <typename T1, typename T2, require_all_var_t<T1, T2>* = nullptr>
inline auto operator*(const T1& a, const T2& b) {
using multiply_type = internal::multiply_vari<T1, T2>;
// store the return type multiply_vari
using mat_return = typename multiply_type::return_type;
return var_value<mat_return>(new multiply_type(a.vi_, b.vi_)};
}
We can deduce the constructor's parameters from T1 and T2
Yeah we need the boilerplate somewhere, imo it's simpler to have in the function in than in the class (though is annoying). One thing we could do is put the type traits stuff into op_vari. Then multiply etc. could look like template <typename T1, typename T2>
class multiply_vari<T1, T2, require_all_var_t<T1, T2>>
final : public op_vari<mat_mul_return_t<T1, T2>, T1, T2> {
using return_t = mat_mul_return_t<T1, T2>;
using op_vari<return_t, T1, T2>::avi;
using op_vari<return_t, T1, T2>::bvi;
using lhs_vari = vari_type_t<T1>;
using rhs_vari = vari_type_t<T2>;
public:
multiply_vari(lhs_vari* avi, rhs_vari* bvi)
: op_vari<return_t, T1, T2>(avi->val_ * bvi->val_, avi, bvi) {}
// yada yada Then in
Yeah it would be good to have less, the Q is just where do they go |
clicked close by accident |
Yes, and how much this will need repeated in other places. |
I got something almost working here. https://github.com/stan-dev/math/tree/feature/eigen-vari-ops-vari-deduction I'm doing something dumb with inheritance though and getting an error that it doesn't understand it can use the |
^That works now. Some parts are a little icky but I'd clean that up when we are at this stage |
…o feature/eigen-vari-ops
…s/RELEASE_600/final)
Summary
If all these WIPs are getting annoying I can close all the others since this one has everything in it.
This is adds the reverse mode matrix multiplication for static matrices. At uses a trick in the
chain()
method to call either the standard multiplication chain method or the matrix chain method. Thechain_impl()
function has a doc going over how this works It also adds amultiply_vari
specialization for Arith * eigen_var vs. eigen_var * Arith.This leaks memory right now as op_vari holds the matrix which is not allocated on our stack. I've been having some trouble writing the code to allocate that memory with
op_vari
, if @t4c1 or @bbbales2 know some tuple magic to write that it would be very appreciated! Another option is just to removeop_vari
and templatedv_vari
,vd_vari
etc. to take in and allocate the mem for eigen matrices. It's a code density vs maintanence tradeoff. If we can find a nice solution thenop_vari
is fine, but if it's too confusing then it may be better to go back to the old op code. I have the start of the code for op_vari with stack allocated mem for the eigen matrices hereTests
I just wrote an informal test right now that checks if static vs dynamic matrices return the same adjoint calculations after calling .grad()
Side Effects
Release notes
Checklist
Math issue #21
Copyright holder: Steve Bronder
The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
- Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
- Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
the basic tests are passing
./runTests.py test/unit
)make test-headers
)make test-math-dependencies
)make doxygen
)make cpplint
)the code is written in idiomatic C++ and changes are documented in the doxygen
the new changes are tested