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

Save memory when doing bounded checking for sparse DTMCs #517

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ColonelPhantom
Copy link

Currently, Storm produces many copies of the transition matrix for the sparse engine. Concretely, there are 2 copies of the original matrix and 2 copies of the P=? submatrix resident at peak RSS.

These are

  1. The original constructed matrix
  2. The transpose of (1) which is used to find which states are P=?
  3. The submatrix in Storm's native format
  4. The submatrix in GMM++'s format.

By deferring the creation of (2), it is freed earlier (its scope is closed earlier), which allows the operating system to reuse its memory for (3) and (4). As such, peak memory usage is determined by max(m1+m2, m1+m3+m4) instead of m1+m2+m3+m4.

(Addtionally, (4) can be omitted entirely by using the Storm native solver, which is already a setting that can be changed.)

Other sparse model checking functions have the same weakness, but I only did it for this one as a proof-of-concept. Applying this patch and using the native solver nearly halves Storm's memory usage for the factories problem (f=12) from the Rubicon paper, from 1 GB to circa 0.5 GB. Either of these by themselves reduce it to 0.75 GB. This is because for this problem the submatrix has nearly the same size as the original matrix.

An alternative that could be considered is to keep the backwards transitions around in the original model. Currently, I believe that Storm recalculates the transpose over and over. Keeping it around would reduce time usage slightly, whereas dropping it early reduces memory usage.

SparseDeterministicStepBoundedHorizonHelper takes a matrix with
the backwards transitions. By constructing this inside the helper
we can deallocate it early, reducing peak memory.
@@ -101,8 +101,8 @@ std::unique_ptr<CheckResult> SparseDtmcPrctlModelChecker<SparseDtmcModelType>::c
storm::modelchecker::helper::SparseDeterministicStepBoundedHorizonHelper<ValueType> helper;
std::vector<ValueType> numericResult =
helper.compute(env, storm::solver::SolveGoal<ValueType>(this->getModel(), checkTask), this->getModel().getTransitionMatrix(),
this->getModel().getBackwardTransitions(), leftResult.getTruthValuesVector(), rightResult.getTruthValuesVector(),
pathFormula.getNonStrictLowerBound<uint64_t>(), pathFormula.getNonStrictUpperBound<uint64_t>(), checkTask.getHint());
leftResult.getTruthValuesVector(), rightResult.getTruthValuesVector(), pathFormula.getNonStrictLowerBound<uint64_t>(),
Copy link
Contributor

Choose a reason for hiding this comment

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

@tquatmann is it clear why we referred to the backward transitions previously?

Copy link
Author

Choose a reason for hiding this comment

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

They're used below the line where they are calculated now, in a call to performProbGreater0:

maybeStates = storm::utility::graph::performProbGreater0(backwardTransitions, phiStates, psiStates, true, upperBound);
which is defined in graph.cpp:
storm::storage::BitVector performProbGreater0(storm::storage::SparseMatrix<T> const& backwardTransitions, storm::storage::BitVector const& phiStates,

This function iterates over the rows of the backwards transitions, as it essentially does a reverse flood-fill to find the states which can reach the target state(s). Using the non-transposed matrix would thus require iterating over a column, which is slow and I'm not sure it's even possible with how sparse matrices are implemented.

I don't know off of the top of my head if the same result can be achieved in a forwards manner, but I think it would be much slower since it would probably require maximalSteps iterations over all vertices.

This was the only use of it in the helper, I'm not sure if there are any other potential reasons to refer to the backward transitions but I doubt it.

@sjunges
Copy link
Contributor

sjunges commented Apr 5, 2024

Thanks a lot. To me, this is a clear improvement. A slight alternative could be to have this as an optional argument -- but I think i would dislike the extra code complexity that would add. @tquatmann thoughts?

I think we have discussed in other threads that it make sense to create a class/type for submatrices that also would avoid further copies.

@tquatmann
Copy link
Member

Iirc, the original idea was to allow re-using backward transitions for cases where one solves multiple step-bounded reachability queries on the same matrix. However, I don't think that this is currently used somewhere (unless maybe through stormpy?), so I support the suggested changes.

@ColonelPhantom
Copy link
Author

Iirc, the original idea was to allow re-using backward transitions for cases where one solves multiple step-bounded reachability queries on the same matrix. However, I don't think that this is currently used somewhere (unless maybe through stormpy?), so I support the suggested changes.

Ah yeah, that would be a valid alternative. Doing that would probably be pretty easy and just put it as a std::optional<> member. But personally I think doing it like this to save memory is more valuable as transposing should be a fairly cheap operation, compared to the achieved memory usage reduction.

@sjunges sjunges added this to the 1.10 milestone Jun 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants