Skip to content

Reimplement AutoDiff from the ground up#17492

Draft
jwnimmer-tri wants to merge 1 commit into
RobotLocomotion:masterfrom
jwnimmer-tri:autodiff-repave
Draft

Reimplement AutoDiff from the ground up#17492
jwnimmer-tri wants to merge 1 commit into
RobotLocomotion:masterfrom
jwnimmer-tri:autodiff-repave

Conversation

@jwnimmer-tri

@jwnimmer-tri jwnimmer-tri commented Jul 4, 2022

Copy link
Copy Markdown
Collaborator

Relates to #10991 and #16877.

There are two main ideas with the new implementation.


First, we change the storage type for AutoDiffXd's derivatives from Eigen::VectorXd to drake::internal::Partials. The AutoDiffXd::derivatives() accessors now return an Eigen xpr-like view into the storage, instead of a direct reference to it.

The Partials storage type offers several unique advantages compared to a Eigen::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 new drake::autodiff::AutoDiff class. The new class is no longer templated (i.e., the type of derivatives is always internal::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 runtime stride in 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 Reviewable

@jwnimmer-tri

Copy link
Copy Markdown
Collaborator Author

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:

AutoDiff rewrite (time per call)

The chart shows that not only is the throughput improved ~2x, the stddevs are narrower.


Here's the raw numbers, for now:

Case Min Time Relative reduction
MM master 1854 us  
MM PR 883 us 52.3%
ID master 2793 us  
ID PR 1001 us 64.2%
FD master 4241 us  
FD PR 2478 us 41.6%

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 MatrixX<AutoDiff> down the road.

@jwnimmer-tri

jwnimmer-tri commented Jul 4, 2022

Copy link
Copy Markdown
Collaborator Author

Here's the quick sample from /multibody/inverse_kinematics:position_constraint_experiment for IiwaPositionConstraintFixture:

Benchmark Time Relative reduction
EvalAutoDiffMbpDouble_mean (master) 80 us
EvalAutoDiffMbpDouble_mean (PR) 66 us 17%
EvalAutoDiffMbpAutoDiff_mean (master) 2288 us
EvalAutoDiffMbpAutoDiff_mean (PR) 809 us 64%

@jwnimmer-tri jwnimmer-tri left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

@sherm1

sherm1 commented Jul 4, 2022

Copy link
Copy Markdown
Member

Wow! Awesome.

@jwnimmer-tri

jwnimmer-tri commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator Author

The functional tests are passing now, though apparently there are still some memory leaks to chase down.
(Edit: Fixed now; no meaningful change in the numbers.)

Here's the updated perf summary for the //examples/multibody/cassie_benchmark:experiment and //multibody/inverse_kinematics:position_constraint_experiment cases.

AutoDiff rewrite (time per call)

. Cassie MM Cassie ID Cassie FD IK MbpD IK MbpAD
Master (min) 1854 us 2793 us 4241 us 79 us 2254 us
PR (min) 815 us 833 us 2373 us 68 us 658 us
Reduction 56.0% 70.2% 44.0% 13.5% 70.8%

@sherm1

sherm1 commented Jul 11, 2022

Copy link
Copy Markdown
Member

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:

  • compact representation of unit vectors
  • scale factor
  • copy on write

@jwnimmer-tri
jwnimmer-tri force-pushed the autodiff-repave branch 3 times, most recently from 56d0e2f to bd64e87 Compare August 19, 2025 15:45
@jwnimmer-tri

Copy link
Copy Markdown
Collaborator Author

There are an assortment of independent tricks here and I'm wondering whether they all contribute meaningfully to the speedup ...

  • compact representation of unit vectors
  • scale factor
  • copy on write

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.

@github-actions

Copy link
Copy Markdown

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.

@github-actions github-actions Bot added the status: stale Will be automatically closed soon due to inactivity. label Jun 16, 2026
@jwnimmer-tri jwnimmer-tri removed the status: stale Will be automatically closed soon due to inactivity. label Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants