-
Notifications
You must be signed in to change notification settings - Fork 122
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
Reimplement jacobians using the vectorized forward mode #1121
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -199,31 +199,31 @@ CUDA_HOST_DEVICE inline void __builtin_powf_pullback(float x, float exponent, | |
// FIXME: Add the rest of the __builtin_ routines for log, sqrt, abs, etc. | ||
|
||
namespace std { | ||
template <typename T> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, T> abs_pushforward(T x, T d_x) { | ||
template <typename T, typename dT> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> abs_pushforward(T x, dT d_x) { | ||
if (x >= 0) | ||
return {x, d_x}; | ||
else | ||
return {-x, -d_x}; | ||
} | ||
|
||
template <typename T> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, T> exp_pushforward(T x, T d_x) { | ||
template <typename T, typename dT> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> exp_pushforward(T x, dT d_x) { | ||
return {::std::exp(x), ::std::exp(x) * d_x}; | ||
} | ||
|
||
template <typename T> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, T> sin_pushforward(T x, T d_x) { | ||
template <typename T, typename dT> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> sin_pushforward(T x, dT d_x) { | ||
return {::std::sin(x), ::std::cos(x) * d_x}; | ||
} | ||
|
||
template <typename T> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, T> cos_pushforward(T x, T d_x) { | ||
template <typename T, typename dT> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> cos_pushforward(T x, dT d_x) { | ||
return {::std::cos(x), (-1) * ::std::sin(x) * d_x}; | ||
} | ||
|
||
template <typename T> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, T> sqrt_pushforward(T x, T d_x) { | ||
template <typename T, typename dT> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> sqrt_pushforward(T x, dT d_x) { | ||
return {::std::sqrt(x), (((T)1) / (((T)2) * ::std::sqrt(x))) * d_x}; | ||
} | ||
|
||
|
@@ -232,9 +232,9 @@ CUDA_HOST_DEVICE ValueAndPushforward<T, T> floor_pushforward(T x, T /*d_x*/) { | |
return {::std::floor(x), (T)0}; | ||
} | ||
|
||
template <typename T> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, T> atan2_pushforward(T y, T x, T d_y, | ||
T d_x) { | ||
template <typename T, typename dT> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> atan2_pushforward(T y, T x, dT d_y, | ||
dT d_x) { | ||
return {::std::atan2(y, x), | ||
-(y / ((x * x) + (y * y))) * d_x + x / ((x * x) + (y * y)) * d_y}; | ||
} | ||
|
@@ -246,8 +246,8 @@ CUDA_HOST_DEVICE void atan2_pullback(T y, T x, U d_z, T* d_y, T* d_x) { | |
*d_x += -(y / ((x * x) + (y * y))) * d_z; | ||
} | ||
|
||
template <typename T> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, T> acos_pushforward(T x, T d_x) { | ||
template <typename T, typename dT> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> acos_pushforward(T x, dT d_x) { | ||
return {::std::acos(x), ((-1) / (::std::sqrt(1 - x * x))) * d_x}; | ||
} | ||
|
||
|
@@ -263,17 +263,27 @@ ValueAndPushforward<float, float> sqrtf_pushforward(float x, float d_x) { | |
|
||
#endif | ||
|
||
template <typename T1, typename T2> | ||
CUDA_HOST_DEVICE ValueAndPushforward<decltype(::std::pow(T1(), T2())), | ||
decltype(::std::pow(T1(), T2()))> | ||
pow_pushforward(T1 x, T2 exponent, T1 d_x, T2 d_exponent) { | ||
auto val = ::std::pow(x, exponent); | ||
auto derivative = (exponent * ::std::pow(x, exponent - 1)) * d_x; | ||
template <typename T, typename dT> struct AdjOutType { | ||
using type = T; | ||
}; | ||
|
||
template <typename T, typename dT> struct AdjOutType<T, clad::array<dT>> { | ||
using type = clad::array<T>; | ||
}; | ||
|
||
template <typename T1, typename T2, typename dT1, typename dT2, | ||
typename T_out = decltype(::std::pow(T1(), T2())), | ||
typename dT_out = typename AdjOutType<T_out, dT1>::type> | ||
CUDA_HOST_DEVICE ValueAndPushforward<T_out, dT_out> | ||
pow_pushforward(T1 x, T2 exponent, dT1 d_x, dT2 d_exponent) { | ||
T_out val = ::std::pow(x, exponent); | ||
dT_out derivative = (exponent * ::std::pow(x, exponent - 1)) * d_x; | ||
// Only add directional derivative of base^exp w.r.t exp if the directional | ||
// seed d_exponent is non-zero. This is required because if base is less than or | ||
// equal to 0, then log(base) is undefined, and therefore if user only requested | ||
// directional derivative of base^exp w.r.t base -- which is valid --, the result would | ||
// be undefined because as per C++ valid number + NaN * 0 = NaN. | ||
// seed d_exponent is non-zero. This is required because if base is less than | ||
// or equal to 0, then log(base) is undefined, and therefore if user only | ||
// requested directional derivative of base^exp w.r.t base -- which is valid | ||
// --, the result would be undefined because as per C++ valid number + NaN * 0 | ||
// = NaN. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this overload? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was tricky to make this pushforward work as both vectorized and non-vectorized. The main reason is that the output type is hard to deduce. Also the line |
||
if (d_exponent) | ||
derivative += (::std::pow(x, exponent) * ::std::log(x)) * d_exponent; | ||
return {val, derivative}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
#define FUNCTION_TRAITS | ||
|
||
#include "clad/Differentiator/ArrayRef.h" | ||
#include "clad/Differentiator/Matrix.h" | ||
|
||
#include <type_traits> | ||
|
||
|
@@ -548,15 +549,25 @@ namespace clad { | |
using type = NoFunction*; | ||
}; | ||
|
||
template <class... Args> struct SelectLast; | ||
// OutputVecParamType is used to deduce the type of derivative arguments | ||
// for vector forward mode. | ||
template <class T, class R> struct OutputVecParamType { | ||
using type = array_ref<typename std::remove_pointer<R>::type>; | ||
}; | ||
|
||
template <class... Args> | ||
using SelectLast_t = typename SelectLast<Args...>::type; | ||
template <class T, class R> | ||
using OutputVecParamType_t = typename OutputVecParamType<T, R>::type; | ||
|
||
/// Specialization for vector forward mode type. | ||
template <class F, class = void> struct ExtractDerivedFnTraitsVecForwMode {}; | ||
|
||
template <class T> struct SelectLast<T> { using type = T; }; | ||
template <class F> | ||
using ExtractDerivedFnTraitsVecForwMode_t = | ||
typename ExtractDerivedFnTraitsVecForwMode<F>::type; | ||
|
||
template <class T, class... Args> struct SelectLast<T, Args...> { | ||
using type = typename SelectLast<Args...>::type; | ||
template <class ReturnType, class... Args> | ||
struct ExtractDerivedFnTraitsVecForwMode<ReturnType (*)(Args...)> { | ||
using type = void (*)(Args..., OutputVecParamType_t<Args, void>...); | ||
}; | ||
|
||
template <class T, class = void> struct JacobianDerivedFnTraits {}; | ||
|
@@ -569,7 +580,7 @@ namespace clad { | |
// JacobianDerivedFnTraits specializations for pure function pointer types | ||
template <class ReturnType, class... Args> | ||
struct JacobianDerivedFnTraits<ReturnType (*)(Args...)> { | ||
using type = void (*)(Args..., SelectLast_t<Args...>); | ||
using type = void (*)(Args..., OutputParamType_t<Args, void>...); | ||
}; | ||
|
||
/// These macro expansions are used to cover all possible cases of | ||
|
@@ -581,11 +592,12 @@ namespace clad { | |
/// qualifier and reference respectively. The AddNOEX adds cases for noexcept | ||
/// qualifier only if it is supported and finally AddSPECS declares the | ||
/// function with all the cases | ||
#define JacobianDerivedFnTraits_AddSPECS(var, cv, vol, ref, noex) \ | ||
template <typename R, typename C, typename... Args> \ | ||
struct JacobianDerivedFnTraits<R (C::*)(Args...) cv vol ref noex> { \ | ||
using type = void (C::*)(Args..., SelectLast_t<Args...>) cv vol ref noex; \ | ||
}; | ||
#define JacobianDerivedFnTraits_AddSPECS(var, cv, vol, ref, noex) \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: function-like macro 'JacobianDerivedFnTraits_AddSPECS' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage] #define JacobianDerivedFnTraits_AddSPECS(var, cv, vol, ref, noex) \
^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vgvassilev This is tricky to address and it's not related to the PR. Do we want to fix this now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No but we should probably open an issue to track it? |
||
template <typename R, typename C, typename... Args> \ | ||
struct JacobianDerivedFnTraits<R (C::*)(Args...) cv vol ref noex> { \ | ||
using type = void (C::*)( \ | ||
Args..., OutputParamType_t<Args, void>...) cv vol ref noex; \ | ||
}; | ||
|
||
#if __cpp_noexcept_function_type > 0 | ||
#define JacobianDerivedFnTraits_AddNOEX(var, con, vol, ref) \ | ||
|
@@ -739,27 +751,6 @@ namespace clad { | |
using ExtractDerivedFnTraitsForwMode_t = | ||
typename ExtractDerivedFnTraitsForwMode<F>::type; | ||
|
||
// OutputVecParamType is used to deduce the type of derivative arguments | ||
// for vector forward mode. | ||
template <class T, class R> struct OutputVecParamType { | ||
using type = array_ref<typename std::remove_pointer<R>::type>; | ||
}; | ||
|
||
template <class T, class R> | ||
using OutputVecParamType_t = typename OutputVecParamType<T, R>::type; | ||
|
||
/// Specialization for vector forward mode type. | ||
template <class F, class = void> struct ExtractDerivedFnTraitsVecForwMode {}; | ||
|
||
template <class F> | ||
using ExtractDerivedFnTraitsVecForwMode_t = | ||
typename ExtractDerivedFnTraitsVecForwMode<F>::type; | ||
|
||
template <class ReturnType, class... Args> | ||
struct ExtractDerivedFnTraitsVecForwMode<ReturnType (*)(Args...)> { | ||
using type = void (*)(Args..., OutputVecParamType_t<Args, void>...); | ||
}; | ||
|
||
/// Specialization for free function pointer type | ||
template <class F> | ||
struct ExtractDerivedFnTraitsForwMode< | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1228,7 +1228,16 @@ StmtDiff BaseForwardModeVisitor::VisitCallExpr(const CallExpr* CE) { | |
callDiff = m_Builder.BuildCallToCustomDerivativeOrNumericalDiff( | ||
customPushforward, customDerivativeArgs, getCurrentScope(), | ||
const_cast<DeclContext*>(FD->getDeclContext())); | ||
|
||
// Custom derivative templates can be written in a | ||
// general way that works for both vectorized and non-vectorized | ||
// modes. We have to also look for the pushforward with the regular name. | ||
if (!callDiff && m_DiffReq.Mode != DiffMode::forward) { | ||
customPushforward = | ||
clad::utils::ComputeEffectiveFnName(FD) + "_pushforward"; | ||
callDiff = m_Builder.BuildCallToCustomDerivativeOrNumericalDiff( | ||
customPushforward, customDerivativeArgs, getCurrentScope(), | ||
const_cast<DeclContext*>(FD->getDeclContext())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: do not use const_cast [cppcoreguidelines-pro-type-const-cast] const_cast<DeclContext*>(FD->getDeclContext()));
^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vgvassilev I don't think there's a way to avoid this easily right now. The same thing is done in all visitors. Should I silence the warning? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, that needs deeper refactoring |
||
} | ||
if (!isLambda) { | ||
// Check if it is a recursive call. | ||
if (!callDiff && (FD == m_DiffReq.Function) && | ||
|
@@ -1446,7 +1455,9 @@ BaseForwardModeVisitor::VisitBinaryOperator(const BinaryOperator* BinOp) { | |
derivedR = BuildParens(derivedR); | ||
opDiff = BuildOp(opCode, derivedL, derivedR); | ||
} else if (BinOp->isAssignmentOp()) { | ||
if (Ldiff.getExpr_dx()->isModifiableLvalue(m_Context) != Expr::MLV_Valid) { | ||
if ((Ldiff.getExpr_dx()->isModifiableLvalue(m_Context) != | ||
Expr::MLV_Valid) && | ||
!isCladArrayType(Ldiff.getExpr_dx()->getType())) { | ||
diag(DiagnosticsEngine::Warning, BinOp->getEndLoc(), | ||
"derivative of an assignment attempts to assign to unassignable " | ||
"expr, assignment ignored"); | ||
|
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.
Are these changes not good for a separate PR?
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.
They can exist on their own but they will have no use. The types don't match in the vectorized fwd mode because those will be
T
andclad::array<T>
respectively.