Allocate the prediction output up front instead of concatenating batches - #125
Merged
Conversation
… batches Predicting with a multitask model and no `task_idx` returns one column per LC setup. At 6,543 setups that is 25.6 kB per peptide, so a 2.6 M PSM run needs a 63 GiB result. It was being built by collecting every batch in a list and concatenating at the end, which needs the result twice over and only fails once all the work is done. A user hit this through MS2Rescore after 100 minutes of prediction, and the message was a bare DefaultCPUAllocator failure. The output is now allocated once, on the first batch, and filled in place. That halves the peak, fails before the compute rather than after it, and reports the size that did not fit along with the `task_idx` argument that makes it smaller. Datasets that cannot report a length still fall back to collecting batches. The bucketed path already preallocated and now shares the same helper, so its failure carries the same message. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FactorHead finishes with `proj(trunk) @ embedding.T * scale + shift`, so the
(n_peptides, n_tasks) matrix it returns is determined by (n_peptides, rank).
At rank 64 and 6,543 setups that is 102x smaller: a 2.6 M peptide run needs
0.6 GiB of factors instead of 63 GiB of predictions.
`predict_kwargs={"factored": True}` with `return_matrix=True` now returns a
FactoredPredictionMatrix holding those factors. It reports the same shape and
indexes the same way, evaluating only the block asked for, so a caller that
reads a few thousand rows to choose a head and then one column per run never
builds the rest. `np.asarray()` still gives the dense matrix.
Opt-in rather than the default. It was tried as the default and broke eight
tests that call ndarray methods on the result (`out.min()`, `np.isfinite(out)`),
which is exactly what it would do to callers' code: no lazy object can satisfy
the whole ndarray contract, so the choice belongs to the caller who knows
whether they slice the matrix or reduce over it.
Not available for a head fine-tuned onto one setup, which returns that column
alone and has nothing to factor, nor for single-task models.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FactorHead ends with `proj(trunk) @ embedding.T * scale + shift`, so the
(n_peptides, n_tasks) matrix it returns is determined by (n_peptides, rank). At
rank 64 and 6,543 setups that is 102x smaller: 0.6 GiB of factors instead of
63 GiB of predictions for 2.6 M peptides.
`predict(..., return_matrix=True)` now hands back a FactoredPredictionMatrix.
It reports the same shape and indexes the same way, selecting rows alone gives
another factored matrix, and `__getattr__` falls through to the dense array for
anything the factors cannot answer, so a caller that reduces over the result
rather than slicing it is unaffected. `predict_kwargs={"factored": False}`
forces the dense array.
MultiHeadCalibration.fit no longer expands a lazy source either. Head ranking
already walked the heads in blocks and every later step reads single columns,
so nothing in fit needed the matrix whole; the block size is now chosen from
the row count, which holds the peak near 64 MiB whether the reference has a
thousand rows or a million rather than growing with it.
Together these mean a caller that predicts the matrix, slices calibration rows
out of it, fits a calibration and transforms per run never materialises the
wide matrix. That is the path MS2Rescore takes, so it is fixed by upgrading
DeepLC, with no change on its side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DeepLC 4.5.0: keep the multitask prediction matrix as its low-rank factors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
predict()on a multitask model withouttask_idxreturns one column per LC setup. At 6,543 setups that is 25.6 kB per peptide, so a 2.6 M PSM run needs a 63 GiB result._predict_epochbuilt it by collecting every batch in a list and callingtorch.catat the end. That needs the result twice over, once in the parts and once in the copy, so the peak is ~126 GiB. Worse, it only fails after every batch has been predicted.A user hit this through MS2Rescore, which calls
predict(..., return_matrix=True)for all PSMs at once. It failed after 100 minutes of prediction with a bare allocator error:Nothing in that tells the caller the output was 2,587,932 x 6,543, or that
task_idxexists.Change
Allocate the output once, on the first batch, and fill it in place:
task_idx.The bucketed path already preallocated; it now shares the same helper so its failure carries the same message. Datasets that cannot report a length still fall back to collecting batches.
Not addressed here
This makes the failure fast and legible. It does not make the 63 GiB request succeed.
FactorHeadis rank-64, so that matrix is a rank-64 product plus a per-column affine and could be held in(n, 64)instead, 102x smaller. That is a follow-up PR.Tests
MemoryErrornamingtask_idx230 tests pass.
ruff checkon the touched files reports the same 5 pre-existing findings asmain, none added.🤖 Generated with Claude Code