Vectorised AD testing - Nested containers

Thanks for the link to the actual proposal.

This is a bit different than what I’ve called “vectorization”, which is itself different than what the CPU/compiler calls vectorization. By “vectorization” in Stan, we have two ambiguous meanings:

  • applying a scalar function elementwise; this can apply to any old functions, like generalizing real sqrt(real) to vector sqrt(vector) or generalizing vector softmax(vector) to vector[] softmax(vector[]).

  • allowing scalar functions to be generalized to containers in reductions; this is something like normal_lpdf(real | real, real) being extended to normal_lpdf(reals | reals, reals) where any of the arguments can be real, real[], vector, or row_vector.

The original distinction among std::vector, Eigen::VectorXd, and Eigen::RowVectorXd was intentional, not the result of laziness. I wanted to avoid the confusion you get in R about the type of a linear algebra result and allow minimal type returns for functions like real multiply(row_vector, vector); I wanted arrays to be use for holding elements in a container and linear algebra types to be used whenever there was a proper multivariate function (like in softmax or log_sum_exp, which do not operate elementwise). That got relaxed as I seemed to be the only person happy with that distinction.

There’s even been calls to eliminate real[] from the language. I like the generality of saying our basic types are real, int, vector, row_vector, matrix and that we can form arbitrary arrays. It’s very simple to explain and document, though apparently very confusing to a lot of Stan users coming from less strictly typed languages like R or Python.

We are never going to be able to get to where vector, row_vector, and real[] are exchangeable, nor do I think we should aim for that.

This approach assumes that the combination of Eigen::Map + Eigen functions is faster than using standard library functions.

Why would Eigen::Matrix<T, R, C> containers be faster than std::vector? They’re built with the same RAII pattern, though initialization’s a bit different. I don’t know if the compiler designers optimze the standard library beyond what’s possible for Eigen as an external lib. Both Eigen::Matrix and std::vector guarantee memory locality on the values. Is there an example somewhere? I would think rather than using Eigen’s templated operator-, we’d want to get Stan’s version in place that uses custom gradients and can remove a bunch of virtual function calls using a single callback chain() method for all entries.

Eigen::Matrix<T, -1, -1> is different than std::vector<std::vector<T>> in that the matrix uses a single array of contiguous memory and stores in column-major order. That means accessing rows of vector<vector<T>> is much much faster because no copying is required. Traversing column-major will be faster with matrices.

The bigger question I have is what’s the return type of something like softmax()? It sounds like the proposal uses a uniform return type, which in Stan works out to these three types:

vector softmax(vector);
vector softmax(row_vector)
vector softmax(real[]);

If we do extend these functions, wouldn’t it be more natural to have each return its own type?

For head, we absolutely need to keep our input and output types, so that’d have to be:

vector head(vector, int);
row_vector head(row_vector, int);
real[] head(real[], int);
int[] head(int[], int);

Given the proposed definition of log_softmax, it’ll still only apply to Eigen types as they’re the only ones that support an appropriate .array() method. The document says that Eigen::Map will be used around std::vector, but I don’t see where that happens in the code examples.

Adding all these lambdas isn’t free. Their constructors and destructors will get called and assignments will be made. So I would suggest profiling the resulting functions, which will probably be dominated by exp and log anyway.

1 Like