Skip to content

[Bugfix] Take the VRAM usage of prompt_logprobs into account - #5355

Closed
Conless wants to merge 7 commits into
vllm-project:mainfrom
Conless:main
Closed

Conless wants to merge 7 commits into
vllm-project:mainfrom
Conless:main

Conversation

@Conless

@Conless Conless commented Jun 8, 2024 •

Copy link
Copy Markdown

As mentioned in #5067 , current profiler and scheduler logic do not consider the VRAM usage of computing log probabilities, which can consume up to 7.8 GB VRAM. This PR introduces a new engine arg called max_num_batched_logprobs, with its default value to max_seqs for simplicity, and take it into account in the profiler and scheduler logic.

Specifically, in profile_run, sampling params of the first $k$ prompt are set to prompt_logprobs=1, where $k=\lceil\frac{max\ num\ batched\ logprobs}{tokens}\rceil$. Then in scheduler.py, a new variable called logprob_budget is added into SchedulingBudget to track the number of tokens that require logprobs.

A simple unit test is added into test_logprobs.py for testing this new engine arg.

FIX #5067
FIX #1532
FIX #5147
FIX #5274
FIX #5550


PR Checklist (Click to Expand)

Thank you for your contribution to vLLM! Before submitting the pull request, please ensure the PR meets the following criteria. This helps vLLM maintain the code quality and improve the efficiency of the review process.

PR Title and Classification

Only specific types of PRs will be reviewed. The PR title is prefixed appropriately to indicate the type of change. Please use one of the following:

  • [Bugfix] for bug fixes.
  • [CI/Build] for build or continuous integration improvements.
  • [Doc] for documentation fixes and improvements.
  • [Model] for adding a new model or improving an existing model. Model name should appear in the title.
  • [Frontend] For changes on the vLLM frontend (e.g., OpenAI API server, LLM class, etc.)
  • [Kernel] for changes affecting CUDA kernels or other compute kernels.
  • [Core] for changes in the core vLLM logic (e.g., LLMEngine, AsyncLLMEngine, Scheduler, etc.)
  • [Hardware][Vendor] for hardware-specific changes. Vendor name should appear in the prefix (e.g., [Hardware][AMD]).
  • [Misc] for PRs that do not fit the above categories. Please use this sparingly.

Note: If the PR spans more than one category, please include all relevant prefixes.

Code Quality

The PR need to meet the following code quality standards:

  • We adhere to Google Python style guide and Google C++ style guide.
  • Pass all linter checks. Please use format.sh to format your code.
  • The code need to be well-documented to ensure future contributors can easily understand the code.
  • Include sufficient tests to ensure the project to stay correct and robust. This includes both unit tests and integration tests.
  • Please add documentation to docs/source/ if the PR modifies the user-facing behaviors of vLLM. It helps vLLM user understand and utilize the new features or changes.

Notes for Large Changes

Please keep the changes as concise as possible. For major architectural changes (>500 LOC excluding kernel/data/config/test), we would expect a GitHub issue (RFC) discussing the technical design and justification. Otherwise, we will tag it with rfc-required and might not go through the PR.

What to Expect for the Reviews

The goal of the vLLM team is to be a transparent reviewing machine. We would like to make the review process transparent and efficient and make sure no contributor feel confused or frustrated. However, the vLLM team is small, so we need to prioritize some PRs over others. Here is what you can expect from the review process:

  • After the PR is submitted, the PR will be assigned to a reviewer. Every reviewer will pick up the PRs based on their expertise and availability.
  • After the PR is assigned, the reviewer will provide status update every 2-3 days. If the PR is not reviewed within 7 days, please feel free to ping the reviewer or the vLLM team.
  • After the review, the reviewer will put an action-required label on the PR if there are changes required. The contributor should address the comments and ping the reviewer to re-review the PR.
  • Please respond to all comments within a reasonable time frame. If a comment isn't clear or you disagree with a suggestion, feel free to ask for clarification or discuss the suggestion.

Thank You

Finally, thank you for taking the time to read these guidelines and for your interest in contributing to vLLM. Your contributions make vLLM a great tool for everyone!

@Conless

Conless commented Jun 8, 2024

Copy link
Copy Markdown
Author

BTW, I found another bug in the sampler while testing this feature. When some sequences are preempted (which is common when the size of the KV cache is insufficient), the sampling_metadata becomes inconsistent with the new input, resulting in:

File "vllm/model_executor/layers/sampler.py", line 760, in _get_logprobs
[rank0]:     assert len(next_token_ids) == len(query_indices)
[rank0]: AssertionError

This bug appears to be unrelated to my changes, as it also occurs in the original code. I would like to fix it, but I am currently busy looking for a research internship, so I may work on it later.

@Yard1

Yard1 commented Jun 8, 2024

Copy link
Copy Markdown
Contributor

BTW, I found another bug in the sampler while testing this feature. When some sequences are preempted (which is common when the size of the KV cache is insufficient), the sampling_metadata becomes inconsistent with the new input, resulting in:

File "vllm/model_executor/layers/sampler.py", line 760, in _get_logprobs
[rank0]:     assert len(next_token_ids) == len(query_indices)
[rank0]: AssertionError

This bug appears to be unrelated to my changes, as it also occurs in the original code. I would like to fix it, but I am currently busy looking for a research internship, so I may work on it later.

I think this will be fixed by #5349

@Conless

Conless commented Jun 8, 2024

Copy link
Copy Markdown
Author

I think this will be fixed by #5349

Thank you for mentioning that. I apologize for not catching that earlier. I'll check #5349 to see if it resolves the issue.

@Conless

Conless commented Jun 8, 2024 •

Copy link
Copy Markdown
Author

The modifications to the scheduler logic of chunked prefills have issues, causing the tests to fail. Let me fix this now and submit a new commit shortly.

@robertgshaw2-redhat

Copy link
Copy Markdown
Collaborator

Thanks for taking this on!

@Conless

Conless commented Jun 10, 2024

Copy link
Copy Markdown
Author

I have carefully reviewed the failed checks and confirmed that they are not caused by my changes.

@robertgshaw2-neuralmagic could you please review this PR? Thank you!

Comment thread vllm/config.py Resolved
Comment thread vllm/core/scheduler.py Resolved
@mgoin

mgoin commented Jun 10, 2024

Copy link
Copy Markdown
Member

@Conless I tried using this PR to perform an evaluation on MMLU (where it is easy to run into the existing OOM issue because of the large number of logprobs required) and this PR seems to completely prevent that evaluation from working now. For context, the previous method of failure on main was just if the batch size was too large or memory utilization was too high, we would hit OOM trying to allocate dynamic structures to hold the logprobs.

The key issue seems to be the triggering of:

WARNING 06-10 17:21:28 scheduler.py:728] Input prompt (3082 tokens) is too long and exceeds limit of 256

If I reduce the --num_fewshot parameter to 0, I still get a smaller:

WARNING 06-10 17:30:17 scheduler.py:728] Input prompt (985 tokens) is too long and exceeds limit of 256

I thought this PR would enable the scheduler to make space for logprobs and not "over-allocate" for subsequent requests coming in. Or at least make vLLM aware of the "leftover" memory it has available and do some work of managing that in tandem with kv cache blocks to decide how to schedule the requests coming in. I believe this current method of having a fixed number is not sufficient and doesn't scale when we do have enough memory available to run the request.

The following are steps to reproduce.

Install lm-evaluation-harness:

git clone https://github.com/EleutherAI/lm-evaluation-harness.git
pip install -e lm-evaluation-harness

Eval:

lm_eval --model vllm --model_args pretrained=meta-llama/Meta-Llama-3-8B-Instruct --tasks mmlu --num_fewshot 5

INFO 06-10 17:17:56 llm_engine.py:161] Initializing an LLM engine (v0.4.3) with config: model='meta-llama/Meta-Llama-3-8B-Instruct', speculative_config=None, tokenizer='meta-llama/Meta-Llama-3-8B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, rope_scaling=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.bfloat16, max_seq_len=8192, download_dir=None, load_format=LoadFormat.AUTO, tensor_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, quantization_param_path=None, device_config=cuda, decoding_config=DecodingConfig(guided_decoding_backend='outlines'), seed=1234, served_model_name=meta-llama/Meta-Llama-3-8B-Instruct)
INFO 06-10 17:17:57 weight_utils.py:221] Using model weights format ['*.safetensors']
INFO 06-10 17:18:01 model_runner.py:157] Loading model weights took 14.9595 GB
INFO 06-10 17:18:01 gpu_executor.py:83] # GPU blocks: 27318, # CPU blocks: 2048
INFO 06-10 17:18:02 model_runner.py:887] Capturing the model for CUDA graphs. This may lead to unexpected consequences if the model is not static. To run the model in eager mode, set 'enforce_eager=True' or use '--enforce-eager' in the CLI.
INFO 06-10 17:18:02 model_runner.py:891] CUDA graphs can take additional 1~3 GiB memory per GPU. If you are running out of memory, consider decreasing `gpu_memory_utilization` or enforcing eager mode. You can also reduce the `max_num_seqs` as needed to decrease memory usage.
INFO 06-10 17:18:06 model_runner.py:957] Graph capturing finished in 4 secs.
...
WARNING 06-10 17:21:28 scheduler.py:728] Input prompt (3082 tokens) is too long and exceeds limit of 256
[rank0]: Traceback (most recent call last):
[rank0]:   File "/home/mgoin/venvs/vllm/bin/lm_eval", line 8, in <module>
[rank0]:     sys.exit(cli_evaluate())
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/__main__.py", line 369, in cli_evaluate
[rank0]:     results = evaluator.simple_evaluate(
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/utils.py", line 346, in _wrapper
[rank0]:     return fn(*args, **kwargs)
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/evaluator.py", line 276, in simple_evaluate
[rank0]:     results = evaluate(
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/utils.py", line 346, in _wrapper
[rank0]:     return fn(*args, **kwargs)
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/evaluator.py", line 442, in evaluate
[rank0]:     resps = getattr(lm, reqtype)(cloned_reqs)
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/api/model.py", line 370, in loglikelihood
[rank0]:     return self._loglikelihood_tokens(new_reqs, disable_tqdm=disable_tqdm)
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/models/vllm_causallms.py", line 414, in _loglikelihood_tokens
[rank0]:     answer = self._parse_logprobs(
[rank0]:   File "/home/mgoin/code/lm-evaluation-harness/lm_eval/models/vllm_causallms.py", line 459, in _parse_logprobs
[rank0]:     continuation_logprobs_dicts = [
[rank0]: TypeError: 'NoneType' object is not iterable
Running loglikelihood requests:   0%|                                                                                                                                                                           | 0/56168 [00:01<?, ?it/s]

@Conless

Conless commented Jun 11, 2024

Copy link
Copy Markdown
Author

@mgoin Sorry for the late reply. I was busy handling other stuff these days.

this PR seems to completely prevent that evaluation from working now.

To handle long requests with prompt_logprobs, you may increase max_num_batched_logprobs to change the limit. I left this argument for users to customize VRAM limitations. Additionally, I can add support to determine the default value based on VRAM usage, which may be helpful in your case.

I thought this PR would enable the scheduler to make space for logprobs and not "over-allocate" for subsequent requests coming in.

I'm not sure if I fully understand your point. Actually, I don't think this design conflicts with your idea. The current logic is as follows:

  1. Run the profiler with the maximum number of logprobs. We do not allocate space for the sampler after the profile run, but we determine a reasonable configuration for the KV cache to prevent OOM.
  2. When requests come in, the scheduler takes actions to ensure the total number of logprobs being computed does not exceed the limit. Then the VRAM would be allocated when the sampler works.

A possible improvement is to balance the KV cache and the number of logprobs in each iteration. However, that would be quite difficult since the KV cache is allocated statically.

@binxuan

binxuan commented Jul 19, 2024

Copy link
Copy Markdown

Hi thanks for the great work, it will fix a lot of OOM errors when we run prompt logprobs. Do you have any ETA on when it will get merged?

@Conless

Conless commented Jul 20, 2024

Copy link
Copy Markdown
Author

Hi thanks for the great work, it will fix a lot of OOM errors when we run prompt logprobs. Do you have any ETA on when it will get merged?

Sorry I’m busy with some other works these days.

As what is mentioned above, to implement a more fine-grained management, which may be expected by maintainers, requires some more work in memory management. Thus it may be hard to finish it soon.

If this bug also bothers you, we may work on it together. I’m looking forward to collaborators!

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any activity within 90 days. It will be automatically closed if no further activity occurs within 30 days. Leave a comment if you feel this pull request should remain open. Thank you!

@github-actions github-actions Bot added the stale Over 90 days of inactivity label Oct 26, 2024
@mergify

mergify Bot commented Nov 26, 2024

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Conless.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Nov 26, 2024
@github-actions github-actions Bot added unstale Recieved activity after being labelled stale and removed stale Over 90 days of inactivity labels Nov 28, 2024
@hmellor hmellor closed this Feb 18, 2025
@Conless
Conless deleted the main branch July 22, 2025 09:00
malaiwah added a commit to malaiwah/vllm-fairness that referenced this pull request Sep 1, 2026
`_get_prompt_logprobs_dict` materialized the full
[num_scheduled_tokens, vocab_size] logits tensor in one shot and then
upcast it to float32 via `log_softmax`. Both tensors are live at the
same time, so the transient peak is roughly
`num_scheduled_tokens * vocab_size * (2 + 4)` bytes: at 8192 scheduled
tokens and a 152k vocabulary that is ~7.5 GiB.

That peak is never observed by memory profiling. `profile_run` calls
`_dummy_sampler_run`, which builds its `SamplingMetadata` with
`max_num_logprobs=None`, so the logprobs path is not exercised and the
float32 score tensor is never allocated. KV cache capacity is therefore
sized against a peak that excludes prompt logprobs, and the allocation
has to come out of whatever is left at serving time.

This is the mechanism described in vllm-project#5907: "When determining the KV cache
size, we calculate peak memory running a long prefill *without
logprobs*. If a prompt requests many logprobs, however, this is an
additional source of memory usage which is not considered during warmup
and can cause OOM." That issue was closed as stale and its fix attempt
(vllm-project#5355) was closed unmerged; the behaviour is still present on main.

Rather than change profiling, bound the allocation directly, which is
the "chunked logits processing" approach proposed and endorsed in the
over row chunks of `VLLM_PROMPT_LOGPROBS_CHUNK_SIZE` (default 1024), and
each chunk's results are copied into the existing per-request CPU
tensors before the next chunk is materialized. The peak becomes
independent of `max_num_batched_tokens`.

Behaviour is unchanged: same logits, same scores, same destination rows,
same `logprobs_mode` handling. Only the number of rows materialized at
once differs. The default of 1024 leaves the common case (short prompts,
small `max_num_batched_tokens`) at a single chunk.

Signed-off-by: Michel Belleau <michel.belleau@malaiwah.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase unstale Recieved activity after being labelled stale

Projects

None yet

6 participants