Repository navigation
Prototype: sparse difference quotient Jacobian for KLU, without Enzyme - #1428
matthiaskoenig wants to merge 6 commits into
Conversation
Integrator::getStatistics returns the counters of the work of the integrator since the last resetStatistics, which RoadRunner calls when a simulation starts (simulate, and oneStep when it resets the integrator). In Python, r.integrator.getStatistics() returns them as a dict. CVODE returns the counters of CVODE (steps, RHS evaluations, error test failures, linear solver setups, nonlinear solver iterations and convergence failures, Jacobian evaluations, root function evaluations, ...) plus the number of events and of reinitializations, and the step sizes and orders of the last step. CVODE sets its counters to zero when roadrunner reinitializes it after an event, so the integrator sums them over the reinitializations. The counters of the linear solver interface are set to zero at the first step after a reinitialization, which the sum takes into account. With the new setting collect_timing it also measures the time of the integration, of the RHS and root function evaluations, and of the setups and solves of the linear solver, which it wraps for that. rk4, rk45, euler and gillespie return the counters that apply to them under the same keys. The SWIG typemap for a map of settings did not compile for a map returned by value, and leaked the keys and values of the dict. Both typemaps use the new settingsMap_to_py. Fixes sys-bio#1411.
CVODE is reinitialized at every root of its root functions, which includes a piecewise condition that switches without an event, and after a delayed event. The documentation of getStatistics said only after events.
Prototype for the open question of sys-bio#1418: a sparse difference quotient with the coloring of the Jacobian, without Enzyme, as the Jacobian of the sparse direct solver KLU. The sparsity pattern and the column coloring from the SBML math come from the Enzyme prototype (JacobianSparsity), and are computed for every model at load. getStateVectorJacobianDQ evaluates the reaction rates and rate rule rates once at the state and once per color, and assembles J = cf * N * dv/dy plus the rate rule rows. The CVODE callback uses the increments of CVODE's dense difference quotient, so every entry is the forward difference CVODE would compute. The CVODE settings jacobian ("difference_quotient" or "sparse_difference_quotient") and linear_solver ("dense" or "klu") choose it; KLU needs the CMake option RR_USE_KLU. A model without a pattern (conserved moieties, a stoichiometry set by a rule, species conversion factors, a state-dependent conversion factor) falls back to the dense difference quotient.
On the four benchmark models of sys-bio#1418, KLU with the sparse difference quotient is as fast as KLU with the Enzyme Jacobian and takes the same steps. RESULTS.md has the tables and the validation on the SBML test suite.
|
@luciansmith This is a smaller solution for the sparse matrix with KLU which gives the same speedups. I.e. the numerical Jacobian methods can be used to figure out the sparsity pattern which then speeds up simulation (and probably also memory usage because the complete matrices don't have to be in memory, only the sparse parts). This does not require to calculate the Jacobian, just the sparsity pattern. If we could bring this into roadrunner it would be great, the full Jacobian does not help too much at this point. |
…bian memory.py measures the RSS of the load and the heap after the simulation per model, build and configuration. The sparse difference quotient adds at most 0.16 MiB, KLU saves the 3 MiB of the dense matrices on Chen_MSB2009, and the Enzyme Jacobian adds 7 to 100 MiB at load.
|
Also the memory savings here in addition to the speedup is very big for us. We are running ~50 000 roadrunner instances in FEM meshes, if we save 3 MB per instance that is a huge thing for the servers ;) |
A smaller prototype for the open question of #1418:
On the four benchmark models of #1418, it is the whole KLU speedup. KLU with the sparse difference quotient is as fast as KLU with the Enzyme Jacobian, takes the same steps, and gives the same error against a reference. Exactness does not matter for these models. The models load as fast and with as much memory as before, KLU saves the dense matrices, and the build needs no Enzyme.
It is a draft for discussion, stacked on #1425 (the counters of the integrators, which the benchmark needs); the first two commits are those of #1425.
What it does
It reuses the parts of #1418 that do not need Enzyme: the sparsity pattern and the column coloring from the SBML math (
JacobianSparsity), the assemblyJ = cf * N * dv/dyplus the rate rule rows, the CVODE settings and the CMake optionRR_USE_KLU. The Enzyme tangents are replaced by forward differences:LLVMExecutableModel::getStateVectorJacobianDQevaluates the reaction rates and rate rule rates aty, and once per color with the states of that color perturbed.cvLsDenseDQJac), so every entry is the forward difference CVODE would compute, with fewer model evaluations.factorial,rateOf, ...) are no problem for differences.Results
The models and simulations of #1418:
simulate(0, end, 201)atrtol = atol = 1e-10, LLJit atDEFAULT. The Enzyme numbers come from the #1418 branch, with a small patch that sums its counters over the reinitializations of CVODE like #1425 does (enzyme-counter-totals.patch). Every number is the median over 5 rounds, in which the two builds and the models take turns, each in a process of its own; a simulation time is the median of 10 runs, in which the configurations take turns. The machine was shared with other jobs.collect_timing); for Enzyme, the timing tool of Prototype: exact Jacobian from Enzyme and the sparse KLU solver for CVODE (#1412, #1409) #1418 at the middle of the timecourse.|x - x_ref| / (atol + rtol |x_ref|)over the output, against dense + DQ atrtol = 1e-12,atol = 1e-14.Load time, with recompilation:
Memory
Each model, build and configuration in a fresh process, median of 5 processes (
memory.py):simulate(0, end, 201)(glibcmallinfo2): the CVODE memory, the matrices and factorizations of the linear solver and the workspace of the Jacobian. Unlike the RSS, it drops when the dense matrices are replaced by sparse ones.Validation
SparseDQJacobianTests.SbmlTestSuitecompares the sparse difference quotient with the dense forward difference of the state vector rate with the same increments on the semantic cases of the SBML test suite, at the start and at t = 2, with MCJit and LLJit. They agree to rounding in all 1465 cases with a pattern, and the dense difference has no nonzero outside the pattern. 150 cases fall back (104 with a stoichiometry set by a rule, 46 with species conversion factors), and 208 do not load. The test runs whenRR_SBML_TEST_SUITEpoints totest/sbml-test-suite/semantic.SparseDQJacobianTests(6 tests, both JITs) also checks that CVODE gives the result of its dense difference quotient with the dense solver and with KLU, including events, the fallbacks, and the settings in any order.ctestpasses withRR_USE_KLU=ON, apart from the tests that collide underctest -j(ModelEditingTests and StateSavingTests fail under ctest -j because they share one output directory #1417).How to reproduce
prototype/sparse_dq_jacobian/hasRESULTS.md,benchmark.py(writesbenchmark.csv),memory.py(writesmemory.csv),table.py(prints the tables above from them),models.pyandenzyme-counter-totals.patch. Both scripts need this build withRR_USE_KLU=ONand a build of the #1418 branch with the patch.Not covered
Jinstead ofdv/dy. Conserved moiety analysis needs the Jacobian with respect to the independent species.loadStatehave no pattern and fall back.getFullJacobian, the steady-state solvers and forward sensitivities could use it too.prototype/sparse_dq_jacobian/would not go upstream as it is.