feat(memory): size the LoRA prefill scratch, not just adapter storage - #6
Open
dittops wants to merge 1 commit into
Open
feat(memory): size the LoRA prefill scratch, not just adapter storage#6dittops wants to merge 1 commit into
dittops wants to merge 1 commit into
Conversation
Serving a LoRA-enabled model allocates two very different things, and this
library reported only the small one.
`calculate_lora_adapter_memory` returns the adapter A/B *storage*: 0.22 GiB at
rank 256 on a 0.6B model. Punica's prefill working buffers at the same rank are
16.77 GiB -- 75x larger -- and were reported as nothing at all. A control plane
sizing a pod off the first number under-sizes it by that factor and the pod
OOMKills at "Warming up model for the compilation", which destroys the evidence
along with the pod.
Add `calculate_lora_prefill_scratch`, reported as its own `MemoryReport` field
rather than folded into the storage term: summed together it would read as a
rounding error on a number readers already believe is small.
Calibration (Qwen3-0.6B, 28 layers, rank 256, max_num_seqs=1, vLLM CPU, cgroup
limit 40 GiB so nothing was clipped). LoRA-attributable is the LoRA-on peak minus
a LoRA-off control at the identical token budget:
tokens | LoRA off | LoRA on | scratch
-------|-----------|-----------|------------
2394 | 5143 MiB | 22311 MiB | 16.77 GiB
4273 | 5113 MiB | 27119 MiB | 21.49 GiB
8192 | 5096 MiB | OOM @50G | > 45.02 GiB
Two results worth stating. Base memory is FLAT in the token budget -- 47 MiB
across a 3.4x range -- so every bit of a LoRA pod's context sensitivity is this
one term. And the term is sized by `max_num_batched_tokens`, the tokens in one
forward pass, NOT by the context length: with chunked prefill those differ, and
charging the context prices an 8192-context pod at 31 GiB it never allocates.
`max_num_batched_tokens` threads through `calculate_memory` and defaults to
`seq_length`, so callers that do not set it get the unchunked reading.
The fit is honest about where it stops. Above 4273 batched tokens the term goes
convex -- at 8192 the linear form under-predicts a measured floor by >13 GiB --
so it returns a note marking the figure a LOWER BOUND rather than an estimate. It
also notes when a request leaves the measured 28-layer/rank-256 geometry, since
linearity in depth and in rank is assumed, not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
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.
Scope
Serving a LoRA-enabled model allocates two very different things, and this library reported
only the small one.
calculate_lora_adapter_memoryreturns the adapter A/B storage — 0.22 GiB at rank 256 ona 0.6B model. Punica's prefill working buffers at the same rank are 16.77 GiB, 75x larger,
and were reported as nothing at all. A control plane sizing a pod off the first number
under-sizes it by that factor; the pod then OOMKills at
Warming up model for the compilation,which destroys the evidence along with the pod.
This adds
ModelMemoryCalculator.calculate_lora_prefill_scratchand reports it as its ownMemoryReportfield. It is deliberately not folded into the storage term: summed together itreads as a rounding error on a number readers already believe is small, which is how a 16 GiB
allocation stayed invisible.
Calibration
Qwen3-0.6B, 28 layers, rank 256,
max_num_seqs=1, vLLM CPU backend, cgroup limit 40 GiB sonothing was clipped. LoRA-attributable is the LoRA-on peak minus a LoRA-off control pod at the
identical token budget:
Two results worth stating:
pod's context sensitivity is this one term.
max_num_batched_tokens, the tokens in one forward pass, not by thecontext length. With chunked prefill those differ, and charging the context prices an
8192-context pod at 31 GiB it never allocates.
API
MemoryReportgainslora_prefill_scratch_bytesandnotes;total_memory_bytesincludesthe scratch.
calculate_memory(...)gainsmax_num_batched_tokens, defaulting toseq_length. Callersthat do not set it get the unchunked reading, so nothing existing changes shape.
Where it stops, and says so
Above 4273 batched tokens the term goes convex — at 8192 the linear form under-predicts a
measured floor by >13 GiB — so it returns a note marking the figure a LOWER BOUND rather
than an estimate. It also notes when a request leaves the measured 28-layer/rank-256 geometry,
since linearity in depth and in rank is assumed rather than measured.
Testing
14 passed. New file
tests/test_lora_prefill_scratch.pypins both calibration points, thebatch-not-context behaviour, rank and TP scaling, both notes, and the refusal paths (unknown
layer count, unset rank).
Full suite: 8 pre-existing failures, all missing packages in the local environment
(
pymoox3;llm_memory_calculatornot pip-installed, which breaks the 5test_bud_componenttests that spawn a subprocess). None relate to this change.
Downstream
BudCluster consumes this calibration for pod sizing in BudEcosystem/bud-runtime#2422. It
restates the constants rather than importing the library — it is a control plane, and this
package pulls matplotlib/plotly/seaborn/pandas — and pins the two copies together with a test.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.