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

Reimplement jacobians using the vectorized forward mode #1121

Merged
merged 4 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 35 additions & 25 deletions include/clad/Differentiator/BuiltinDerivatives.h
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}

Expand All @@ -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};
}
Expand All @@ -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) {
Copy link
Owner

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?

Copy link
Collaborator Author

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 and clad::array<T> respectively.

return {::std::acos(x), ((-1) / (::std::sqrt(1 - x * x))) * d_x};
}

Expand All @@ -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.
Copy link
Owner

Choose a reason for hiding this comment

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

Why do we need this overload?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 auto derivative = ... was created as array_expression, which was a problem for a couple of reasons. I reimplemented this with better templates in the last commit. Does this look better?

if (d_exponent)
derivative += (::std::pow(x, exponent) * ::std::log(x)) * d_exponent;
return {val, derivative};
Expand Down
14 changes: 8 additions & 6 deletions include/clad/Differentiator/Differentiator.h
Original file line number Diff line number Diff line change
Expand Up @@ -624,12 +624,13 @@ CUDA_HOST_DEVICE T push(tape<T>& to, ArgsT... val) {
typename F, typename DerivedFnType = JacobianDerivedFnTraits_t<F>,
typename = typename std::enable_if<
!std::is_class<remove_reference_and_pointer_t<F>>::value>::type>
constexpr CladFunction<
DerivedFnType, ExtractFunctorTraits_t<F>> __attribute__((annotate("J")))
constexpr CladFunction<DerivedFnType, ExtractFunctorTraits_t<F>,
/*EnablePadding=*/true> __attribute__((annotate("J")))
jacobian(F f, ArgSpec args = "",
DerivedFnType derivedFn = static_cast<DerivedFnType>(nullptr),
const char* code = "") {
return CladFunction<DerivedFnType, ExtractFunctorTraits_t<F>>(
return CladFunction<DerivedFnType, ExtractFunctorTraits_t<F>,
/*EnablePadding=*/true>(
derivedFn /* will be replaced by Jacobian*/, code);
}

Expand All @@ -640,12 +641,13 @@ CUDA_HOST_DEVICE T push(tape<T>& to, ArgsT... val) {
typename F, typename DerivedFnType = JacobianDerivedFnTraits_t<F>,
typename = typename std::enable_if<
std::is_class<remove_reference_and_pointer_t<F>>::value>::type>
constexpr CladFunction<
DerivedFnType, ExtractFunctorTraits_t<F>> __attribute__((annotate("J")))
constexpr CladFunction<DerivedFnType, ExtractFunctorTraits_t<F>,
/*EnablePadding=*/true> __attribute__((annotate("J")))
jacobian(F&& f, ArgSpec args = "",
DerivedFnType derivedFn = static_cast<DerivedFnType>(nullptr),
const char* code = "") {
return CladFunction<DerivedFnType, ExtractFunctorTraits_t<F>>(
return CladFunction<DerivedFnType, ExtractFunctorTraits_t<F>,
/*EnablePadding=*/true>(
derivedFn /* will be replaced by Jacobian*/, code, f);
}

Expand Down
57 changes: 24 additions & 33 deletions include/clad/Differentiator/FunctionTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define FUNCTION_TRAITS

#include "clad/Differentiator/ArrayRef.h"
#include "clad/Differentiator/Matrix.h"

#include <type_traits>

Expand Down Expand Up @@ -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 {};
Expand All @@ -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
Expand All @@ -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) \
Copy link
Contributor

Choose a reason for hiding this comment

The 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)            \
        ^

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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?

Copy link
Owner

Choose a reason for hiding this comment

The 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) \
Expand Down Expand Up @@ -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<
Expand Down
4 changes: 0 additions & 4 deletions include/clad/Differentiator/ReverseModeVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -489,10 +489,6 @@ namespace clad {
"attempt to differentiate unsupported operator, ignored.",
args);
}
/// Builds an overload for the gradient function that has derived params for
/// all the arguments of the requested function and it calls the original
/// gradient function internally
clang::FunctionDecl* CreateGradientOverload();

/// Returns the type that should be used to represent the derivative of a
/// variable of type `yType` with respect to a parameter variable of type
Expand Down
2 changes: 1 addition & 1 deletion include/clad/Differentiator/VectorForwardModeVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace clad {
/// A visitor for processing the function code in vector forward mode.
/// Used to compute derivatives by clad::vector_forward_differentiate.
class VectorForwardModeVisitor : public BaseForwardModeVisitor {
private:
protected:
llvm::SmallVector<const clang::ValueDecl*, 16> m_IndependentVars;
/// Map used to keep track of parameter variables w.r.t which the
/// the derivative is being computed. This is separate from the
Expand Down
6 changes: 6 additions & 0 deletions include/clad/Differentiator/VisitorBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,12 @@ namespace clad {
/// Returns type clad::ConstructorReverseForwTag<T>
clang::QualType GetCladConstructorReverseForwTagOfType(clang::QualType T);

/// Builds an overload for the derivative function that has derived params
/// for all the arguments of the requested function and it calls the
/// original derivative function internally. Used in gradient and jacobian
/// modes.
clang::FunctionDecl* CreateDerivativeOverload();

public:
/// Rebuild a sequence of nested namespaces ending with DC.
clang::NamespaceDecl* RebuildEnclosingNamespaces(clang::DeclContext* DC);
Expand Down
15 changes: 13 additions & 2 deletions lib/Differentiator/BaseForwardModeVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Copy link
Contributor

Choose a reason for hiding this comment

The 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()));
        ^

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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?

Copy link
Owner

Choose a reason for hiding this comment

The 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) &&
Expand Down Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions lib/Differentiator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ llvm_add_library(cladDifferentiator
DiffPlanner.cpp
ErrorEstimator.cpp
EstimationModel.cpp
JacobianModeVisitor.cpp
HessianModeVisitor.cpp
MultiplexExternalRMVSource.cpp
PushForwardModeVisitor.cpp
Expand Down
5 changes: 3 additions & 2 deletions lib/Differentiator/DerivativeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "clad/Differentiator/DerivativeBuilder.h"

#include "JacobianModeVisitor.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/TemplateBase.h"
#include "clang/Sema/Lookup.h"
Expand Down Expand Up @@ -432,8 +433,8 @@ static void registerDerivative(FunctionDecl* derivedFD, Sema& semaRef) {
HessianModeVisitor H(*this, request);
result = H.Derive();
} else if (request.Mode == DiffMode::jacobian) {
ReverseModeVisitor R(*this, request);
result = R.Derive();
JacobianModeVisitor J(*this, request);
result = J.DeriveJacobian();
} else if (request.Mode == DiffMode::error_estimation) {
ReverseModeVisitor R(*this, request);
InitErrorEstimation(m_ErrorEstHandler, m_EstModel, *this, request);
Expand Down
9 changes: 1 addition & 8 deletions lib/Differentiator/DiffPlanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,21 +579,14 @@ namespace clad {

// If the function has no parameters, then we cannot differentiate it."
// and if the DiffMode is Jacobian, we must have atleast 2 parameters.
if (params.empty() || (params.size()==1 && this->Mode == DiffMode::jacobian)) {
if (params.empty()) {
utils::EmitDiag(semaRef, DiagnosticsEngine::Error,
CallContext->getEndLoc(),
"Attempted to differentiate a function without "
"parameters");
return;
}

// If it is a Vector valued function, the last parameter is to store the
// output vector and hence is not a differentiable parameter, so we must
// pop it out
if (this->Mode == DiffMode::jacobian){
params.pop_back();
}

// insert an empty index for each parameter.
for (unsigned i=0; i<params.size(); ++i) {
DiffInputVarInfo dVarInfo(params[i], IndexInterval());
Expand Down
Loading
Loading