Reimplement AutoDiff from the ground up#17492
Conversation
|
This is a rough cut for now, with some missing pieces. There are also a few bugs still showing up in tests, which indicates some of the computations might be wrong -- so possibly this is faster in some places by cheating (giving wrong answers) rather than for a good reason. However, a lot of unit tests do pass. Even with those caveats, I think the prototype shows promise and we should continue working. Here's the latest Cassie results for mass matix, inverse dynamics, and forward dynamics: The chart shows that not only is the throughput improved ~2x, the stddevs are narrower. Here's the raw numbers, for now:
It won't be part of this first prototype, but having our own AutoDiff scalar also paves the way to sharing partials storage across all of the elements in a |
|
Here's the quick sample from
|
jwnimmer-tri
left a comment
There was a problem hiding this comment.
Reviewable status: 2 unresolved discussions, needs platform reviewer assigned, needs at least two assigned reviewers, labeled "do not merge", commits need curation (https://drake.mit.edu/reviewable.html#curated-commits)
common/autodiff/auto_diff.h line 20 at r1 (raw file):
in case only one partial is non-zero) it runs much faster than Eigen::AutoDiffScalar<Eigen::VectorXd>. */ class AutoDiff {
This is the rough explanation of the implementation technique.
common/autodiff/internal/partials.h line 193 at r1 (raw file):
void CheckInvariants() const; // Representation invariant:
This it the key piece of the representation of the derivatives.
d955d7b to
8aa9be8
Compare
|
Wow! Awesome. |
10c5eb8 to
f07965a
Compare
bf6b740 to
b0f7158
Compare
|
Here's the updated perf summary for the
|
b0f7158 to
d271c0c
Compare
40de05f to
154f05a
Compare
154f05a to
07dd1b0
Compare
|
The timings look great! There are an assortment of independent tricks here and I'm wondering whether they all contribute meaningfully to the speedup. My thought is that if some aren't paying off the implementation could be simpler. Have you measured them independently? These seem independent to me:
|
07113a3 to
a3ca380
Compare
a3ca380 to
da186dc
Compare
da186dc to
621eab7
Compare
56d0e2f to
bd64e87
Compare
What I'll do is land the changes one by one. The #19628 has landed on master now with the scale factor. As the PR train continues, we'll see the separate effects of compact unit vectors and copy on write. |
bd64e87 to
a1cecf2
Compare
|
Thank you for your contribution. This pull request has been open for 180 days without activity and so is considered stale. It will be automatically closed in 30 days unless you comment or remove the "status: stale" label. |
Relates to #10991 and #16877.
There are two main ideas with the new implementation.
First, we change the storage type for
AutoDiffXd's derivatives fromEigen::VectorXdtodrake::internal::Partials. TheAutoDiffXd::derivatives()accessors now return an Eigen xpr-like view into the storage, instead of a direct reference to it.The
Partialsstorage type offers several unique advantages compared to aEigen::VectorXd:(1.a) Sparse storage. When the derivatives only contain a single non-zero element, it is stored in sparse form inline (no heap operations). Arithmetic combinations of same-index sparse derivatives preserve the sparsity.
(1.b) Copy-on-write storage. When a non-sparse
AutoDiffXd(with heap storage) needs to be copied, we increment the refcount of the derivatives instead of allocating a new copy.(1.c) Separately-stored scale factor. With the chain rule, an extremely common operation is to scale up the derivatives by a constant factor (e.g., this is true for all unary functions). By storing a separate scaling factor, we can re-scale with a single multiply (to the scale factor) instead of broadcasting it over the entire data vector. That allows us to defer repeated scaling until the full vector needs to be read out later, or even skip it entirely if we eventually get zeroed out anyway. It also makes the copy-on-write more efficient by reducing need for writes. Only binops (+, -, *, /) or assignment might induce full copies.
Second, we replace
Eigen::AutoDiffScalar<>template with a newdrake::autodiff::AutoDiffclass. The new class is no longer templated (i.e., the type of derivatives is alwaysinternal::Partials) but otherwise is very similar to the Eigen one.(2.a) By using our own Drake-specific class, we have free reign to specialize Eigen operations on it. In particular, Eigen's support for using move-efficient value categories is abysmal. We specialize a handful of performance-critical Eigen templates to avoid extra copying. Even though copies are cheap, copy-on-write performs better when it ends up with uniquely-owned storage to mutate in place. With more profiling, we can probably find additional places to specialize in order to reduce refcount chatter.
(2.b) This opens the door in the future to specializing
Eigen::Matrix<drake::autodiff::AutoDiff, ...>dense storage to store all of the derivatives as single block on the heap. Our xpr-like view objects use a runtimestridein anticipation of this.(2.c) It's nice to no longer depend on an "unsupported" Eigen type so heavily.
PR train listing:
This change is