From 1d8af932f4930b81e89d2941ab946241c78030be Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Sat, 1 Feb 2025 01:37:39 +0000 Subject: [PATCH] Squashed commit of the following: commit 5d5071cc663707673def790b65c07f6bdab18168 Author: Lucas Wilkinson Date: Sat Feb 1 01:13:23 2025 +0000 reduce split kv amount Signed-off-by: Lucas Wilkinson commit 5fe1d1defcb62ad22a1e5ea450769b8b3e08098b Author: Lucas Wilkinson Date: Sat Feb 1 00:56:45 2025 +0000 format Signed-off-by: Lucas Wilkinson commit 0d66687ecab0b9cda0fd25236a86862ed9635b69 Author: Simon Mo Date: Fri Jan 31 16:39:19 2025 -0800 Update loader.py Co-authored-by: Michael Goin Signed-off-by: Lucas Wilkinson commit 5002734095ff4d744860ba15bf25f6d01196853d Author: Lucas Wilkinson Date: Sat Feb 1 00:14:14 2025 +0000 simplification Signed-off-by: Lucas Wilkinson commit fac827feea324d036ab625849c12ca50874fa721 Merge: db2c5838 44bbca78 Author: Lucas Wilkinson Date: Sat Feb 1 00:09:36 2025 +0000 Merge remote-tracking branch 'origin/main' into mla-fp8 commit db2c58383bf4645ef5c6f6a2992dd011c8bdd8f8 Author: Lucas Wilkinson Date: Sat Feb 1 00:06:10 2025 +0000 filter compressed tensor models better Signed-off-by: Lucas Wilkinson commit e144da83544f5815d9acfa05c886d144549de29a Author: Lucas Wilkinson Date: Fri Jan 31 18:41:35 2025 -0500 Update vllm/model_executor/model_loader/loader.py Co-authored-by: Simon Mo Signed-off-by: Lucas Wilkinson commit 1621381cced37b44ed05beaeb4109d129caf95ca Author: Lucas Wilkinson Date: Fri Jan 31 18:41:22 2025 -0500 Update vllm/model_executor/model_loader/loader.py Co-authored-by: Simon Mo Signed-off-by: Lucas Wilkinson commit 9829faee1492b2ff38d2beaf9ef1cbb328490a36 Author: Lucas Wilkinson Date: Fri Jan 31 23:40:12 2025 +0000 misc Signed-off-by: Lucas Wilkinson commit 44bbca78d71330909dbfdde232debdc73a4d5a81 Author: Brian Dellabetta Date: Fri Jan 31 17:38:48 2025 -0600 [Doc] int4 w4a16 example (#12585) Based on a request by @mgoin , with @kylesayrs we have added an example doc for int4 w4a16 quantization, following the pre-existing int8 w8a8 quantization example and the example available in [`llm-compressor`](https://github.com/vllm-project/llm-compressor/blob/main/examples/quantization_w4a16/llama3_example.py) FIX #n/a (no issue created) @kylesayrs and I have discussed a couple additional improvements for the quantization docs. We will revisit at a later date, possibly including: - A section for "choosing the correct quantization scheme/ compression technique" - Additional vision or audio calibration datasets --------- Signed-off-by: Brian Dellabetta Co-authored-by: Michael Goin commit 60808bd4c7a27b6d28f82657e38a5b303f7534a9 Author: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Fri Jan 31 23:38:35 2025 +0000 [Doc] Improve installation signposting (#12575) - Make device tab names more explicit - Add comprehensive list of devices to https://docs.vllm.ai/en/latest/getting_started/installation/index.html - Add `attention` blocks to the intro of all devices that don't have pre-built wheels/images --------- Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> commit fc542144c4477ffec1d3de6fa43e54f8fb5351e8 Author: Ryan Nguyen <96593302+xpbowler@users.noreply.github.com> Date: Fri Jan 31 18:37:30 2025 -0500 [Feature] Fix guided decoding blocking bitmask memcpy (#12563) **[Guided decoding performance optimization]** Sending the guided decoding bitmask in xgrammar to the GPU (`self.token_bitmask.to(scores.device)`) is a blocking operation that prevents the CPU from pre-launching the sampler kernels. The CPU waits until decode is complete, then copies the bitmask over. This PR changes the operation to async via setting `non-blocking=True`. (Current) The CPU is blocked on a `cudaStreamSynchronize` and only pre-empts the sampling kernels after bitmask application. Below is the Nsys profile for one decode phase from Llama 3.1 8B. ![image](https://github.com/user-attachments/assets/8997eae1-b822-4f52-beb8-ef19a7c6b824) With the optimization, this is no longer the case: ![image](https://github.com/user-attachments/assets/6d5ea83f-f169-4f98-a8c1-41c719b3e1e7) --------- Signed-off-by: Ryan N commit eb5741ad422f04d0bac60c9b6c07183e0431ce8c Author: Tyler Michael Smith Date: Fri Jan 31 18:29:11 2025 -0500 [Kernel][Quantization] Integrate block-quantized CUTLASS kernels for DeepSeekV3 (#12587) Integrates the block-quantized kernels introduced in https://github.com/vllm-project/vllm/pull/11868 for use in linear layers. Signed-off-by: Tyler Michael Smith commit 145c2ff648ad0a300f880ac38811d0d8a2eb3e79 Author: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com> Date: Fri Jan 31 18:28:47 2025 -0500 [Bugfix] Revert MoE Triton Config Default (#12629) SUMMARY: * previous PR for pulling in block configs also changed defaults (https://github.com/vllm-project/vllm/pull/11589/files) for FP8 * this broke L4 MoE since there was not enough SHM for the default configuration * this reverts the non-block example to the default Signed-off-by: rshaw@neuralmagic.com commit 415f19474dedc69934cab79cfb8f5bdc19e2ae0d Author: Kevin H. Luu Date: Fri Jan 31 13:39:36 2025 -0800 [release] Add input step to ask for Release version (#12631) Instead of having to create a new build with release version put in as env var. commit 4251506b09fdea3a2f66dafac98451667c57f3a4 Author: Lucas Wilkinson Date: Fri Jan 31 21:26:13 2025 +0000 fixes Signed-off-by: Lucas Wilkinson commit c9d72cb61dbb1656957303f3a2961d39d4dff500 Author: Lucas Wilkinson Date: Fri Jan 31 21:17:23 2025 +0000 more cleanup Signed-off-by: Lucas Wilkinson commit 3cdd2ceda3ba57d2aa0a84113bfe01b799f961cf Author: Lucas Wilkinson Date: Fri Jan 31 21:16:42 2025 +0000 cleanup Signed-off-by: Lucas Wilkinson commit 89003c4082db880e103e84f5015424e79f9aa762 Author: Chen Zhang Date: Sat Feb 1 05:13:04 2025 +0800 [v1][Bugfix] Add extra_keys to block_hash for prefix caching (#12603) This pr adds extra key to block hash, to generate different hash value for two blocks with the same token string but different extra_keys in their parent blocks. For example, it can generate different hash value for the second block of the following two requests: ```python request1 = make_request( request_id=0, prompt_token_ids=[_ for _ in range(6)], mm_positions=[{ "offset": 0, "length": 3 }, { "offset": 3, "length": 3 }], mm_hashes=["hash1", "hash2"], ) request2 = make_request( request_id=1, prompt_token_ids=[_ for _ in range(6)], mm_positions=[{ "offset": 0, "length": 3 }, { "offset": 3, "length": 3 }], mm_hashes=["hash3", "hash2"], ) ``` --------- Signed-off-by: Chen Zhang commit f51cbe057dceacab670d09bb63c236c7e7542ced Author: Lucas Wilkinson Date: Fri Jan 31 21:04:22 2025 +0000 review comments Signed-off-by: Lucas Wilkinson commit 3d12a0489237b62d0da09c52795d1616649afdcd Author: Lucas Wilkinson Date: Fri Jan 31 20:45:14 2025 +0000 working but messy Signed-off-by: Lucas Wilkinson commit 60bcef000ebcfaf120edc1972a8136344d9bfa0d Author: Cody Yu Date: Fri Jan 31 12:30:46 2025 -0800 [Docs][V1] Prefix caching design (#12598) - Create v1 design document section in docs. - Add prefix caching design doc. @WoosukKwon @ywang96 --------- Signed-off-by: Cody Yu commit 847f883232cedef583775d6f4e13baa2446ba1c7 Author: Cody Yu Date: Fri Jan 31 12:30:33 2025 -0800 [Git] Automatically sign-off commits (#12595) It's very annoying when I forgot to add `-s` in `git commit` to sign-off, because I then need to `git rebase HEAD~1 --signoff` and `git push -f` to fix the DCO. This PR adds a hook to sign off commits automatically when `-s` is missing to solve this problem. The only change from the user side is now users have to install 2 hooks, so instead of just ``` pre-commit install ``` Now we need to ``` pre-commit install --hook-type pre-commit --hook-type commit-msg ``` Note that even if users still only install the pre-commit hook, they won't get any error in `git commit`. Just the sign-off hook won't run. cc @hmellor @youkaichao --------- Signed-off-by: Cody Yu commit 325f679f324c1044cfaa0c594bf0d817eeda4451 Author: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com> Date: Fri Jan 31 15:06:39 2025 -0500 [BugFix] Fix Torch.Compile For DeepSeek (#12594) Co-authored-by: simon-mo commit 548ec44ba97a6562b94f644649e8ed5db328cd0e Author: Lucas Wilkinson Date: Fri Jan 31 19:13:22 2025 +0000 simon changes Signed-off-by: Lucas Wilkinson commit a57cd3d6f416759f0a91b7b92fff75e6525fc6e9 Merge: 076cbe55 cabaf4ef Author: simon-mo Date: Fri Jan 31 07:52:26 2025 +0000 Merge branch 'main' of github.com:vllm-project/vllm into mla-fp8 commit 076cbe55dce001e63f513c507ba42b6dc626ce83 Merge: 0ccbcce4 a1fc18c0 Author: Lucas Wilkinson Date: Thu Jan 30 23:31:41 2025 -0500 Merge branch 'main' into mla-fp8 commit 0ccbcce458c1ef0136757c308d5901ff306f840d Author: Lucas Wilkinson Date: Fri Jan 31 04:29:17 2025 +0000 deepseek v3 support Signed-off-by: Lucas Wilkinson commit 645622c1e91e539632a16b224b3f9d084f23d836 Author: Lucas Wilkinson Date: Fri Jan 31 03:08:36 2025 +0000 cleanup Signed-off-by: Lucas Wilkinson commit 2d6105441c17e6abce2927853a07c293b891c39c Author: Lucas Wilkinson Date: Fri Jan 31 03:03:07 2025 +0000 cleanup Co-authored-by: Alexander Matveev <59768536+alexm-neuralmagic@users.noreply.github.com> Signed-off-by: Lucas Wilkinson commit f2b2500a78ed7822e86aee9020419ed455f8c1b2 Author: Lucas Wilkinson Date: Fri Jan 31 02:47:05 2025 +0000 Fix TP > 1 cuda graphs Co-authored-by: Alexander Matveev <59768536+alexm-neuralmagic@users.noreply.github.com> Signed-off-by: Lucas Wilkinson commit 433322bbec2b2465b6db2d3847580d561649b509 Author: Lucas Wilkinson Date: Fri Jan 31 02:26:11 2025 +0000 Revert "add cuda graph support" Signed-off-by: Lucas Wilkinson commit 31c34bf4205438b1cc7a92eb3fd22eb2ee3d5328 Author: Lucas Wilkinson Date: Thu Jan 30 23:06:09 2025 +0000 ci fix Signed-off-by: Lucas Wilkinson commit 54ba87d99078335ccbf4c7475133591165552467 Author: Lucas Wilkinson Date: Thu Jan 30 21:23:09 2025 +0000 add cuda graph support Signed-off-by: Lucas Wilkinson commit 5afc1bfc8c4f8311557e81446bb291497aed9dfa Author: Lucas Wilkinson Date: Thu Jan 30 20:58:53 2025 +0000 fix mypy Signed-off-by: Lucas Wilkinson commit cfb2d26aa11ea6b9b8559b708d19f7b190b741ae Author: Lucas Wilkinson Date: Thu Jan 30 19:42:36 2025 +0000 fix mypy Signed-off-by: Lucas Wilkinson commit 37e39f4d96f81bd7f3c7414748b30547ddb45b5b Author: Lucas Wilkinson Date: Thu Jan 30 18:04:58 2025 +0000 fix failing test Signed-off-by: Lucas Wilkinson commit 08814757e5f32794eb5af4bcf9009bf5a058bbb2 Author: Lucas Wilkinson Date: Thu Jan 30 17:18:55 2025 +0000 disable MLA for v3 for now Signed-off-by: Lucas Wilkinson commit 4a46014a1208d420b0cb66400cfd81a79feec924 Author: Lucas Wilkinson Date: Thu Jan 30 11:12:48 2025 -0500 Update vllm/attention/backends/mla/utils.py Co-authored-by: Tyler Michael Smith Signed-off-by: Lucas Wilkinson commit 09d814ce248293ccd05f91264a19fe6479bd588e Author: Lucas Wilkinson Date: Thu Jan 30 15:11:58 2025 +0000 review comments Signed-off-by: Lucas Wilkinson commit 8bdc14a3e72299402034843a405f2e6ed0a3b51e Author: Lucas Wilkinson Date: Thu Jan 30 14:09:46 2025 +0000 review comments Signed-off-by: Lucas Wilkinson commit d27826da8a6f5b30614e3098e8e35e56a80b3550 Author: Lucas Wilkinson Date: Thu Jan 30 08:51:42 2025 -0500 Update vllm/config.py Co-authored-by: Zhuohan Li Signed-off-by: Lucas Wilkinson commit 74874292bc4446963ceed15c292ce6ec858a7220 Author: Lucas Wilkinson Date: Thu Jan 30 04:00:26 2025 +0000 renaming for consistency Signed-off-by: Lucas Wilkinson commit 634eee6ecfb0ab3b6c6a4664f2832d4482ce7a71 Author: Lucas Wilkinson Date: Thu Jan 30 03:52:59 2025 +0000 review comments Signed-off-by: Lucas Wilkinson commit 31b802c6a888721fd844e2fb37c9e9cefa3ee052 Author: Lucas Wilkinson Date: Wed Jan 29 22:51:37 2025 -0500 Update vllm/attention/backends/mla/utils.py Co-authored-by: Michael Goin Signed-off-by: Lucas Wilkinson commit 068e672fcff8f16eb53c408068479b087934e07a Author: Lucas Wilkinson Date: Wed Jan 29 22:46:43 2025 -0500 Update utils.py Co-authored-by: Michael Goin Signed-off-by: Lucas Wilkinson commit f2cac91114dc086ca16cb07e4ab26b2229ecafae Author: Lucas Wilkinson Date: Thu Jan 30 03:11:43 2025 +0000 more cleanups Signed-off-by: Lucas Wilkinson commit c34e5ca0493a0eb0307910043540dd27dd2e7149 Author: Lucas Wilkinson Date: Thu Jan 30 03:02:58 2025 +0000 fix VLLM_MLA_PERFORM_MATRIX_ABSORPTION=0 Signed-off-by: Lucas Wilkinson commit 27ad92c9c889b64a6a2811ff29548fb031ddcb45 Author: Lucas Wilkinson Date: Thu Jan 30 02:29:40 2025 +0000 squashed commits Co-authored-by: Woosuk Kwon Co-authored-by: simon-mo Signed-off-by: Lucas Wilkinson --- .buildkite/release-pipeline.yaml | 9 +- .pre-commit-config.yaml | 13 + csrc/ops.h | 1 + .../cutlass_w8a8/scaled_mm_c3x.cu | 8 +- .../cutlass_w8a8/scaled_mm_entry.cu | 15 +- csrc/torch_bindings.cpp | 7 + .../v1/prefix_caching/example-time-1.png | Bin 0 -> 34837 bytes .../v1/prefix_caching/example-time-3.png | Bin 0 -> 37069 bytes .../v1/prefix_caching/example-time-4.png | Bin 0 -> 41530 bytes .../v1/prefix_caching/example-time-5.png | Bin 0 -> 39727 bytes .../v1/prefix_caching/example-time-6.png | Bin 0 -> 25462 bytes .../v1/prefix_caching/example-time-7.png | Bin 0 -> 33144 bytes .../assets/design/v1/prefix_caching/free.png | Bin 0 -> 17933 bytes .../design/v1/prefix_caching/overview.png | Bin 0 -> 33028 bytes docs/source/contributing/overview.md | 2 +- docs/source/design/v1/prefix_caching.md | 228 ++++++++++++++++++ docs/source/features/quantization/index.md | 1 + docs/source/features/quantization/int4.md | 166 +++++++++++++ docs/source/features/quantization/int8.md | 4 +- .../ai_accelerator/hpu-gaudi.inc.md | 4 + .../installation/ai_accelerator/index.md | 33 +-- .../installation/ai_accelerator/neuron.inc.md | 4 + .../ai_accelerator/openvino.inc.md | 4 + .../installation/ai_accelerator/tpu.inc.md | 4 + .../installation/cpu/apple.inc.md | 4 + .../installation/cpu/arm.inc.md | 4 + .../getting_started/installation/cpu/index.md | 13 +- .../installation/cpu/x86.inc.md | 12 +- .../getting_started/installation/gpu/index.md | 49 ++-- .../installation/gpu/rocm.inc.md | 20 +- .../installation/gpu/xpu.inc.md | 4 + .../getting_started/installation/index.md | 15 ++ docs/source/index.md | 7 + format.sh | 3 +- tests/v1/core/test_kv_cache_utils.py | 34 ++- vllm/_custom_ops.py | 5 + vllm/attention/backends/mla/utils.py | 220 ++++++++++++++--- vllm/attention/backends/triton_mla.py | 18 +- vllm/attention/layer.py | 4 +- vllm/config.py | 34 ++- vllm/envs.py | 12 +- .../guided_decoding/xgrammar_decoding.py | 4 +- .../layers/fused_moe/fused_moe.py | 41 +--- .../model_executor/layers/quantization/fp8.py | 59 +++-- .../layers/quantization/utils/fp8_utils.py | 220 ++++++++++++----- .../layers/quantization/utils/quant_utils.py | 116 ++++++++- .../layers/quantization/utils/w8a8_utils.py | 10 + vllm/model_executor/model_loader/loader.py | 24 +- vllm/model_executor/models/deepseek_v3.py | 154 +++++++++++- vllm/v1/core/kv_cache_utils.py | 6 +- vllm/worker/cache_engine.py | 4 +- 51 files changed, 1354 insertions(+), 245 deletions(-) create mode 100644 docs/source/assets/design/v1/prefix_caching/example-time-1.png create mode 100644 docs/source/assets/design/v1/prefix_caching/example-time-3.png create mode 100644 docs/source/assets/design/v1/prefix_caching/example-time-4.png create mode 100644 docs/source/assets/design/v1/prefix_caching/example-time-5.png create mode 100644 docs/source/assets/design/v1/prefix_caching/example-time-6.png create mode 100644 docs/source/assets/design/v1/prefix_caching/example-time-7.png create mode 100644 docs/source/assets/design/v1/prefix_caching/free.png create mode 100644 docs/source/assets/design/v1/prefix_caching/overview.png create mode 100644 docs/source/design/v1/prefix_caching.md create mode 100644 docs/source/features/quantization/int4.md diff --git a/.buildkite/release-pipeline.yaml b/.buildkite/release-pipeline.yaml index 51618a2955fb1..829414bf8a3ba 100644 --- a/.buildkite/release-pipeline.yaml +++ b/.buildkite/release-pipeline.yaml @@ -56,6 +56,11 @@ steps: env: DOCKER_BUILDKIT: "1" + - input: "Provide Release version here" + fields: + - text: "What is the release version?" + key: "release-version" + - block: "Build CPU release image" key: block-cpu-release-image-build depends_on: ~ @@ -66,7 +71,7 @@ steps: queue: cpu_queue_postmerge commands: - "aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/q9t5s3a7" - - "DOCKER_BUILDKIT=1 docker build --build-arg max_jobs=16 --build-arg GIT_REPO_CHECK=1 --tag public.ecr.aws/q9t5s3a7/vllm-cpu-release-repo:$RELEASE_VERSION --progress plain -f Dockerfile.cpu ." - - "docker push public.ecr.aws/q9t5s3a7/vllm-cpu-release-repo:$RELEASE_VERSION" + - "DOCKER_BUILDKIT=1 docker build --build-arg max_jobs=16 --build-arg GIT_REPO_CHECK=1 --tag public.ecr.aws/q9t5s3a7/vllm-cpu-release-repo:$(buildkite-agent meta-data get release-version) --progress plain -f Dockerfile.cpu ." + - "docker push public.ecr.aws/q9t5s3a7/vllm-cpu-release-repo:$(buildkite-agent meta-data get release-version)" env: DOCKER_BUILDKIT: "1" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 084f865604fff..fd2c18b30407d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -85,9 +85,22 @@ repos: entry: tools/png-lint.sh language: script types: [png] + - id: signoff-commit + name: Sign-off Commit + entry: bash + args: + - -c + - | + if ! grep -q "^Signed-off-by: $(git config user.name) <$(git config user.email)>" .git/COMMIT_EDITMSG; then + printf "\nSigned-off-by: $(git config user.name) <$(git config user.email)>\n" >> .git/COMMIT_EDITMSG + fi + language: system + verbose: true + stages: [commit-msg] - id: suggestion name: Suggestion entry: bash -c 'echo "To bypass pre-commit hooks, add --no-verify to git commit."' language: system verbose: true pass_filenames: false + diff --git a/csrc/ops.h b/csrc/ops.h index 830424a4a9d20..f9f0f49faa292 100644 --- a/csrc/ops.h +++ b/csrc/ops.h @@ -156,6 +156,7 @@ torch::Tensor ggml_mul_mat_a8(torch::Tensor W, torch::Tensor X, int64_t type, #ifndef USE_ROCM bool cutlass_scaled_mm_supports_fp8(int64_t cuda_device_capability); +bool cutlass_scaled_mm_supports_block_fp8(int64_t cuda_device_capability); void cutlass_scaled_mm(torch::Tensor& out, torch::Tensor const& a, torch::Tensor const& b, torch::Tensor const& a_scales, diff --git a/csrc/quantization/cutlass_w8a8/scaled_mm_c3x.cu b/csrc/quantization/cutlass_w8a8/scaled_mm_c3x.cu index e6f06d72fbfd4..72d549e597df5 100644 --- a/csrc/quantization/cutlass_w8a8/scaled_mm_c3x.cu +++ b/csrc/quantization/cutlass_w8a8/scaled_mm_c3x.cu @@ -58,7 +58,13 @@ void cutlass_scaled_mm_sm90(torch::Tensor& c, torch::Tensor const& a, vllm::cutlass_scaled_mm_blockwise_sm90_fp8(c, a, b, a_scales, b_scales); } else { - TORCH_CHECK(false, "Unsupported scale group shapes for CUTLASS 3.x GEMM"); + TORCH_CHECK(false, + "Unsupported scale group shapes for CUTLASS 3.x GEMM.\n " + "a_scale_group_shape must be [1, 128], got: [", + a_scale_group_shape[0], ", ", a_scale_group_shape[1], + "]\n" + "b_scale_group_shape must be [128, 128], got: [", + b_scale_group_shape[0], ", ", b_scale_group_shape[1], "]"); } } diff --git a/csrc/quantization/cutlass_w8a8/scaled_mm_entry.cu b/csrc/quantization/cutlass_w8a8/scaled_mm_entry.cu index da77312bc4b98..6bef55088682a 100644 --- a/csrc/quantization/cutlass_w8a8/scaled_mm_entry.cu +++ b/csrc/quantization/cutlass_w8a8/scaled_mm_entry.cu @@ -81,6 +81,19 @@ bool cutlass_scaled_mm_supports_fp8(int64_t cuda_device_capability) { return false; } +bool cutlass_scaled_mm_supports_block_fp8(int64_t cuda_device_capability) { + // CUTLASS block-quantized FP8 kernels need at least CUDA 12.0 + // and at least SM90 (Hopper) + +#if defined CUDA_VERSION + if (cuda_device_capability >= 90) { + return CUDA_VERSION >= 12000; + } +#endif + + return false; +} + void cutlass_scaled_mm(torch::Tensor& c, torch::Tensor const& a, torch::Tensor const& b, torch::Tensor const& a_scales, torch::Tensor const& b_scales, @@ -212,4 +225,4 @@ void cutlass_scaled_mm_azp(torch::Tensor& c, torch::Tensor const& a, "No compiled cutlass_scaled_mm_azp for a compute capability less than " "CUDA device capability: ", version_num); -} \ No newline at end of file +} diff --git a/csrc/torch_bindings.cpp b/csrc/torch_bindings.cpp index 4ea67bbac3525..235373240ac36 100644 --- a/csrc/torch_bindings.cpp +++ b/csrc/torch_bindings.cpp @@ -330,6 +330,13 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) { ops.def("cutlass_scaled_mm_supports_fp8(int cuda_device_capability) -> bool"); ops.impl("cutlass_scaled_mm_supports_fp8", &cutlass_scaled_mm_supports_fp8); + // Check if cutlass scaled_mm supports block quantization (used by DeepSeekV3) + ops.def( + "cutlass_scaled_mm_supports_block_fp8(int cuda_device_capability) -> " + "bool"); + ops.impl("cutlass_scaled_mm_supports_block_fp8", + &cutlass_scaled_mm_supports_fp8); + // Check if cutlass sparse scaled_mm is supported for CUDA devices of the // given capability ops.def( diff --git a/docs/source/assets/design/v1/prefix_caching/example-time-1.png b/docs/source/assets/design/v1/prefix_caching/example-time-1.png new file mode 100644 index 0000000000000000000000000000000000000000..8849ca0237c39b4c428c4ab74c08b512812846f5 GIT binary patch literal 34837 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 F{{ta%lm7q! literal 0 HcmV?d00001 diff --git a/docs/source/assets/design/v1/prefix_caching/example-time-3.png b/docs/source/assets/design/v1/prefix_caching/example-time-3.png new file mode 100644 index 0000000000000000000000000000000000000000..71b9e9b60ab9aae080a6e70a9594dd3374c3591b GIT binary patch literal 37069 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�WS79xjr{r_1B8A& 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 literal 0 HcmV?d00001 diff --git a/docs/source/assets/design/v1/prefix_caching/example-time-7.png b/docs/source/assets/design/v1/prefix_caching/example-time-7.png new file mode 100644 index 0000000000000000000000000000000000000000..fc33ef50d4fdb415de61cb9e75a085d776a971e3 GIT binary patch literal 33144 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 Rbxr_(a#G6oiY1JE{|6ePQ%e8< literal 0 HcmV?d00001 diff --git a/docs/source/assets/design/v1/prefix_caching/free.png b/docs/source/assets/design/v1/prefix_caching/free.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc2f22222e0443072e70f7d8ac1ee8c5daf3590 GIT binary patch literal 17933 zcmdtKbyQSs8#k&C0xG2G{QY@i|iJb(I})2hDTSHJ(zm&58? z-5bmwZau&LKYW~b#e|s1|9+;H)@RJ*-UzmN332xtfU8F98)Re-nZ;2R zut(l{9vuD&yIULaAjd8;$TrhDz^D-n6JXaLS8o$Y)u{m*b(MSdUp^<@X*!IU*CSo(3D{;O~@sAe@@9YnBi)!MuYrdy>{pM^#Nw7s~qwL z!Qy888g$ptTsj%@Yh9UqnKy<9AAY!%CjI^&%_yDQULDX}q3cJw-{qnQzd+0t$#cIc zTxXUEx`vI!i{w@Gi@d@XMcUSv+pnYcIRFu($N zsU`G<7m^*z4a@?=ox8qiAk`Yxp!dC`FXj=3V&|NJy@H&J!Ra578@>_a0j!A-!(*b<`B@=tT8MGS& znxayEb3l1aSG=yP@ox{iAJ~7-;Dwa+iz;A#K&bFh?0G5m#}g+Tyc$d33SZ)BVxp)cL@@eo0@h69by^<`c;L z`Oz5n>y#lT$;7)3Pu=l6`uJeuGGWuR3D6Lb*#7I|gozwe(kWyDsWsi^kv14EDo+J< zYm*9?uS7H;eJ@VMlv~Ss`CNR~mn^PLR-`E*m%s(*UELsxex7FHZ#*z^k7-r|r}2ji zjhKAevVCQdDH>Zpa`(yne*?)jVW@{Qt+Rh>bEN)gtl-c*z9SLzluP1#3~xc!p_bD! z_u{O*s7+YU)!L^iN45pgc>V#sB@3Ea8QDFya-kn#JB~2$*$F%eT_6o95)a_L5S&L4S}e~AqnHMAK%xu65to`h3D-nsM$+0@O6=82_0 zDvO7-n0B_l5F8pi|6RXb>l!J$airaPEG9p?e{9Hv@WM2abhoL``;Rr$68ssg+q^Nl zO8iBE1x?SqPF?{ulkbm|!~Vv2jJkj8t3MkB7+;)r-N`|^e!=x~s#8T&UgUmg@Ti{K z$CI!1?xBt4tJ$9Qf?A)jo3Kv%a|;J?%6_rqBzqd+jnkE6F#~94>pU`i22~(=9*;NW z5#2s!8sxN_A{Op}MK8})FDFfJ`cJj3x0UtrO&vy?o-3o?ObyeAq0H(}KKr_dQ_+=w zFOrSV|vIboC~LnT5gfS^Vq zyDs0X(gyr*g5-+M*Pb#@EGcw{R)3?6@ZaeQINZEAh?egYPjUO%MB)H3y~P`AD~)9{ z_#n51fX?MZoob!vst-HQ4gCk$xqSr^20rv+#T@Dr#7H4CoxYu%NAkre_Yb`7b;zm7 zHM)U}9Ld2}X`=F(x_sV#J(o7w%|_W`2*+;${Y5K-@4JX@6H2y?s&C#UOF6Yg&ic>g z?sHweFwSH6HxE}RzO)IvSX?61E07-a9Jtowo>y2E%70USz$qouvX<#X$$B^ER>L`^ zTUpgsrB*mqlEaJTtJw(n5pH>iN*fezB~ia$_j%YsQ-q zl4jdR=>OisF;L3eke{55`?+P)7BlgSP}9ov8=rM#FAy$A+S9$F)r_WE!mxJ6)iY|= zQKW5hzIF@d;V5=9&S5n1n->gu9GNb3V?hqlwyz{H#TBi+5pC31u=*{?-c_*ufN;~* zk`^>LBwu4+z2R!n71fQNTtQsXpQ|+Zfm%0m zd;eFQUc*x6dM&LiaOrqEbvaHlJ(a?Aj8X#g0mQw2A2rySPge}nqI$mpVS<( zs>$#AMSQ-k#@g-@5#L;&x*2!Ak<2#TObH4a-~8|0rD_Ph%`a z!5=(yWv_yNq2jb50JHR>x0`I}bupt?SqJR406#htIx;XPaVjJLjgX2xm4J-UqZ+5X zbd!ieliKU?A*h$uHlC9cOiV~Q@(0aziklA!6IwS!4?E6(Lt8`qzPY3G_t3@NWg$V7 zj0T=x$F1@p$?Uq87hd{UaIGCKf;G)~V5%&aX50=%`o#G- z$yf!d$>2R>*r%h(RK&f{_xJBt8vGtHb&PIrzvs*?06v>&ho*@rA$I6m#TF)=447S~Q8q&1>Jt4j2M>t;= z!2XYcP(|E>va#y_31IP-7r5^+sLp;kNh0!pJKp(!1>ms(Iip!irQmB^hP!~}uOBEG z20r9hsm3tXBmImgMXAyR*d}g}4juvViStQus^R1kIOO0ncWs>|361#4F#ulwN5K1k z_1BJ`_>jKrbBfD$(%vCCLTvm$UOvR~8C2!+Spwky2Y)cviEjlzp74IGQ`F#A(nGF+ z+~=xg`KZq^(ZapK7AQ09nh(|b{=bx zW6DN4^6d}Z!35CkRd~onQ5rU9^;pHt(7(_U+2UTnqXmvm|Lu2Co`wy}MN3Fjs}K_B zuGVMQV3&GoG{>^iupznV)0GJnLhHMX2_24?xc>Mq07Dg6x{@2e==yPBL`fEwi>j|k zn=3b`E;iIWI1YI+5?lWBzREEDmL6I>Kg~P+x6@g=`{BlFB4Lnw?|hWXaEpocN&(ko zahiY8DtY5&0En9Ez*_@0L+&H=2RWpa{@u=L-@{<;I_Dq}GTP|CQFTtWNB+*LA_ z!>R@$?#^E!O8KkEY^337naaNqwA>fhMa=*%zoiDiC2?taB%2+o=UkSBgpQ1vxbTr z;(WkX+yhJ|I}Am+0gGJqG*{x*j;cZy)ahNw#T^YBP2VJ;h+N*etJ1iwa*kVWNmy=q zpk_R8gfI*N)(E&JkY#f1LM(SAmn5Hy5H8mkPTPA$Fv=hHlU6pA8vXhgr}BL7d>ln@ zHL)&| zbO@uOc2=7`ku-JVKCl{GD?fGHu-XHRog{oecZ3#$ILNz21z|(1evBIZhlANV(dTmN z(`@C)&tn*N^meXb2p{DU!Gd}v)jQgp26zLPU3J5ux+@; zDOJifZVBD)b+YoI16i=jZ(Ijl2W5V*yxZLCdW=&8BQj?icA!rFwJfS^8#XP?#c+aRJj88k*uX(2P8muoAu?@D{9s87cU!y2@`Cnr`WZ4g;)A64AY zY-tW>eicN$t4S8{cSq&#TDUkkWQ-d%hH|tqvg%=xAN|q=;`~vCxT8U4J!Y7bFNU;x zQxP;YF_594mhI2{k(Q%}`g}bq>5j%R@4#hH+o+@`&@pI`1QjuS$k2YkFmz$;>B;=? zlS4yh;4L4~jqb;FIlayXC&doZ&C=!Oev-XoY`OOBN1bu+Cd}2<3sS$AyYp)vI02KT z)IdDMoh&5HPb~}*8rM+|R5WR|+OwrTr(_Qo}kx~!i1R}O#v3M~|6@;la_Ow%^ipUKJ ztu&?N?aSyVec~slzF8|GSazM-)f(TaKomS$hC`sV$+i9=&JOi zu<(WiI^E0MDN`8_9*|6;(MUW*6ZLE-s+g*IWbxb+6%4N+%f&79&1cY(%CE3z{v!=rA4ze239mSxL2P}c9+NBNUxNkqKvQv+4cRv#NWS~4+c^CK1t*4(HLX`6K%{D~; zG}U~PX_?upKno^#Liy(crLxS``a6@ji=PKGwh4pz&*}UVDhi6cVlMZp4B1>_TKz^{ z%jM;1LGnUitX9U_@MT9nGzo)ImRgrLUzbw*PB8bU)tp~eB6IG|&){I*O#MC?HF;T2JuSpoi7mros zX@q>1C10>x#fBhfnWeF}+?dPJ296S{CAp_9k1iJT8WkR@amjhzqSt+_+0sJ5F8lsK zt`$}__UudWy?WIW7FHoS{pXI{s=F-QHUE|Kah)1uM38esDHXX z$U(tjViGX)@Y)Is1!z8PHb!0IiOBz2jdI~zG^xN^JR~kk(b#xoU&M-`<6KQcdC%T% z=0YXR!TaQN*1OmBdj4l*in>Q_op*R46;`r}Sif-bf!5@^>Ar zCf;N{oxiUnF?sIak1P^gj8lPeGZsCCMxTVjieQo#hbY{G9}4?{49vsI7x1YV863*? zQ+7EJKDTX=u>VAX)tjAD54{GjZ7P3fM7v$3g0!EG{Wpz;P4`JLzq0}+e3TvQULR?# za;Pk*LZ^?cH`Q_E>VS#k12~E7-wnM>`AV zP$f3w6ydud6#xw_syci(TWd9e{(4`Z<+`i02%1jF$FtZM)Je!j_}(<@)0ln9X5SXp zV&=5x8(I3Z{HhYuyDKRk4W7*v1J80ac8bFx+ydbuQ!ie%v5cPgC3`BAVzu-6WDE(q-Y^;G z>G=Q@87m!rZeQF$)mmQs?(uD@H^}dOfr+i;WH?!F@;<1_rDnGwOV6)*?Oc+mlqIJsP%PvKAt(yx&M1{@>-bA0}OykN;K_>=U=@<^FH}Fn)tJjM@CR>-b zhokM%fuu0QbF&b%V$=CAqUeCls0p9MH9L(J@oeY*hvPJ+>9>G9Pj_v>X(+}9cOT@He)SHcX@2c!1YJtl>cN3&yAz~#4v zKQJ6)!qEu0taa>$(CUDeL@*&K?cIoJYeEaY(Md~AHT7y%`&zrAPc}9IG53K9VTWli zraGPUl(kUlxzwT+CDOXzP4~elZw?lpZ=tIiFK0qAsS7s~=De_A)@RlH4eta1+ef0D(t(+{x|47aI>(y;WuP*hn2*;Rf=?xD*D%OMYjxu>WZU~qQ!o;Z^ z)n>6C5?E0`_dSayA%g~?f%TaE!s``l;Ne196#)_8Q|{^}y9wyq#}8}uRZniuG|qY#xThu|P_0$0%)&^pe9Z*LvL4eJHPee8!3b~F810_Tc1C(Jh;}c_d0=p!zJ2+MUr@F0)Ws@ni{F>-v~IE` zAi&NWq8HS5Yw0DBae#R93R(G9nYZQn)63}6|Lw-n)R|YrDc9feGN=C`f{o%%Yt{) zUCw;_>u&p?tbbkv)%yOD4eM_xn)rYhejPW{|49Ns%x6gQIn<$+3Vj zAQdi1VA?GkD&ZS-qZdub$nWhQq;BYpkJ5r6Ve>V>HZFxmCgMYar+7$3&+cke8OuZ$9$P>C~t$re$lg+#ZN{&ec}!u>|uexS`dA!`E1Zm9dKLsA{f6GB;&&G zpwutkop0YroZM`27C(kt31GX?-xx$Hj&RXo0;tj2&){MXcZWL`0-E{A$rJa zB`7T%Vq=V_!Senflf9QybzeH=UZ3t(acwuKQ>WEt%O~T4fn#KMM7%&H^t3bJvEblE zFWP`|bc7&B0kyo{@9(RazJ;xz&nx{mFeGTjkXVvhJ5a9s z(^7mwyV~w+7Rx7yhL52UaYIe*NxuU{<@~ZhWu60v9<5m^{^rF=yiq(%?q2^MeoU!M z%g{lx24a(bPxVpX?cKob19Kpe5zoBO9FO#Sd7x4LUx0!2rWq5z0cWw7$vks=3t8iV zL9<6Gpi#ia+P_pjrOsam-=_Mza&^C!b?;E;wk%T-oM-i1_9B?t8de%`5u&?U6;!)6 zY4=72K#6~b*^(f>fO5{9|GAtM-B-=&x6bBod1Am{=Gkrv!biz!+dRzjxEd3-3QR5 z$>mDCYz>JYyXSVJsTzk*A3b^a|C2_6XqIN+X}QsyIp!8>KD1WM7FyC8uj}qb$=h(p zv}Rmof+n}ApLfD;uc`ALa3oDQZp7|(t{5gP-Jnp?(I`+?0u!{vB$Z8;do-^l9oX)Ni{Uip?2MXAtKA`3_Mzs z>awwCfLnv#cW|f!e{_lwhcla;p`X+BOq85z3n_SCq_<(tU$i`Qaij)?*&&x-o3)(V zY`+nBJrhFBkvAQA9A{wvFiuF4^edGxy<+%u1Kr{!CqT+b_1n_Bi!8h{9@^Z~Y#Fn` zJlXv7G~9|?nY^H^I7AAvmg7JnTaueoaUBKjc;@OQ6R(Ha*!|gUR$E>xu>ShJSR)aB zD35ob{!Bsq zCu#P~!p(te>sgfx>c36UZvj<8 zBjN3dOrC9<2&oUzXrs55_HRc~`yX^22tCYo9uvGZXCNP)6ga5#adf^1WE^1s6p{zH zxFl1*5)o-+#mk;?+>W-_caCLppfp6!X>l9sIWHXlzQ2EenZ$3k)g!=Ex#-{JD~Jc* zv|sjS+p3f}&}**9Pp`N^9*Nm}Q+9HoE9s1?1yCfd4(XPkvnpn)R+1$AX?@6l<_9=i z7b}&=rKZ>wBrx-!uyyODV#G3;SJ}y6P@`kJ5x29X5&xUjkDY~XJ&fLO{#|P|4(Kj` zwyH7CGa8jbF}!{Z(SffB9&U>O>fzRoeiw7&RsjMtb9!4$W9fji&}w-6mn=6OdT0 zU8#1PdhoaD&jiEQ<-8F zFfwgvEAIH~>q$ir!ejl>Te-={Hu;-y#dw3w;n8+JSYLZFi4FImcBo_x$ z%$_ba0o2V!9|BD@?K{ht5O?KuI2bE%cQpTJ=^bJYudM3VDtNkw{=&OcJNb3{#;c<- zWM0+CW$JQ(1HvNlwBCNGt3K?J^b79d&m8=(+nXOCgvV^_ID$IKFBsoxXDFs(oKSff z#KuJF@*6t0DgDeu8Gt<3e3;c61avs1F|1=8RR{SZduH^3{&EL^VTy}unVkCP{UrtO zK_k&rDep0z$Cv=_vU*2ipLb6$4j%UjyExWq+p;=PRFA1KU>XV zL^IF!g$a9DKK_(7ycqU{`pMdh`st=z=kXEJzg{MXpkPHjQGjBDj-ZA*EITLoQ(kn4BTJ#pf0);evW^O?z0p8gQ+v#X zm;>gd(!N16<#$4NG}G6jhhH5&^%E^#U0(9}9i}{OVbY8j?EiU|5YxqGc+CoKpix^0 zz6~U>8BU>SVOS4EO43Y(4)gNoW&hay+T(TZ`n8#pmNJkV)|9e(7mZqX^N?I#4m~D& zj~Nl$j2nXF>W`M*0T4z!xB>z+5QDCTh*w=O_@h!eQ{CSjm?#X54$!*(P+_aaR`nT?sCKul z82ak^P6Oa4yYho`(Zu%7!uoplk3GM)BYc?PBFo?S!-X~#vnNbk3=`F__1d%n)n;7l_9kf~2hPpvAg}BZiG9}Sp z4^L+8VG;JDL+?&*j~Hd-bYGGdqc{^ zmW*kMP0kzzzZGRCh`8@wdrSKGbOS=hVuMN}=-iGQK}ATeZdk_yD8lLFX2>p(jo7rMe{38_e1k0TelC_&4vCj{e}VqRbIY!v-138W>~swBTtu z>~e+oQ9B2$Wn+&D6E4*$c^r95O%%A^0$(<}$dZTuoG4oGw@y0+>VJen{u4(4fgRKm z5eX$FgKr=%j28t_cGr!sY0L7b_n4$takIXd1{6me1M+Hrx5uUp{XDu#8l;lpK2d4E zmjIX6={n)WJrZ|H@xlb8oFj8oG90s@RxMNeZA9GS6LzxLJ8)*N`;>T15s9>7BamLs zL5E^(NQxjV`1!{J;Gq5C;!sOV0&iO*l2v;o7>JW5bWw5Ii&k;_q6<;$`g33uejoek zk-C5PpR|Xg{P@=@7P!FHpLrLCI0rc){xa6&yrBm~sezHKR}4ZD;M$8dOdf3?n@Z}l z-+fh1#43_gc4I7hg1RL?LG$=v|Jj8AX9Z5$`d^`n;$BU}(sC$`(B`E3%kI7uo#f(k z(f%hhLp}2ngiI8ME-Uw&N^XOm-`ZXbCdde)3&kojnkU-xB9dx9sU=(=Oausgq$aa9 z(OT3q1^n~rPEZWfqF~pKOJP;#5*3^x=XM&|IA9T+o1UgTt)TJ(i9E*xS+jPF)u?_p z%rwWB^8XK{#__QSDRtRh(yK^^i(eW$j%0f&0TVWRw8y^UH+(acMOXML3qfUa1`j|2 z>-@daJOz&~_$A(LMmuarUo>o4nUjwSxHx7PEPROGJTn+#RdH#-3QuFddj|yj$q5Lf zpC>fDn7Vgzb+Gcop@8J+Pq@wt`#nEI-IY01EZ8>za7;Hp(+B^2pq&WLoW-XPhZwe+ zF|mg#?JfT#38iwxSe!k7mAyM{=RQ+r&zJS(FAO^=`L*4{Z83HgtNM?ym9=B_3rw+R zVv=9>%)u@ttpi`F-^Pg8h#2{Px{^EVi$w-R7QlQT$|=J=)sk)CLZNp{GIo~y1AorH z3yU|U9Z%W&=^={U`lwZFeOR8f?$M-7joxa8gF9+Mnk%2|n18A^$FXM;r+ z{JS)|2=?&wKXgdCQtgrL4N)3D(pJV`6r3>2yrG@vr8A@Sot*9@0NwqBeroW+WpcN~Jqm7(j5g6@2~0B1zFAv8l<;@WsYyHA5KecIft*pIBb&sX^{D?qhrfU5=usZF7eLrvBc!_Q%#6cvTQDOpOEO0!@ zXIx4|%CBl?LhQHKTPgG>uTssg4f#?19b$gBjt%aP4z=Vc0;2}-mG?7ent(KZX(GJi zXvq$a7&P(M-h&d`X2U(QvPK?}Yuz1eizIu&s5ry6r-7SM{Ei3kze<1toCoGU0&00S z-NWCHC07MD@bhs4+di?(;1S?+qaa90IHm@+tn!J-LAYMij4F} zOUZ^+nXF3YEOHJw-77PE8=3rZ?L59AB1eMVAzm;bgwPrmpB5HNvh~m}6SO6`c)Jm_ zM|%1x;U^FIqF7=e-($O*(@L#^<$qKFHU2WW>Yt&UnErgO#oHC8G>jk7eXFevOHh21 z$MR77)D7mMNW-Y(Bnqu_cVB`hd-l!**#_iJo%BHAM#3FJt?>BZL?#fv$fOY^FX4sN z_NP$^Cl`K`YZ$+ZDPs?+R~PNK2HW3aZ#-}b0vO9Vm%W+dal77#yZ-opgoC-T$m5cB z;=&nQ*pkWFm)H)HKEX_He{c?Ws? zcmhEjo3bOrA2k?Wr)`!CL9m63F6mLJ`Hk8JXfGaP0SfJgu(Ez|;cC8%o;%(q^2iKG z_t{zJ30ma$-e3XyjeElEoI%q6(z>cSzLm;;kYbZu_4`KvUfh7`&|{MUOH3c3ba%H{ zYX3fV-?!^tqdn%RR#0(Y+uit1=RPuRV@`D9Ql84_lq5c>`{17J`I*DPa@dTnhh-0y znr5>257F@X4KfPx9@V*4thgnQKJU6^r{fo@%)>k_upS>_dm{Lz<4q442($fUty7Ol zDVAm3^Kk|ugi08i^mIMJKY^C;qW!_0j*CJ_8o^tyEMCDOOQ0a}S8~vFq3vIwmiElh z@*wSStf+eCMPV0;?kccK(qH<;3wE2AmbXnKO1^e}O1!%6RXn>4GtLlV7HQo7e%EWh zn7lu91#V#`Zr1V2zW^vsQ{{U4m#T5sW4aE--^sV|@KHSmR{7k4VL;}I(T^)yN+;q{ z>sPsmF09+AlGTrk{cz}Xo9ufq^d)A)$U&hV9y>9FlKl31HBzeI*?jKnS{q!x?Zs&fCVFP|N zA&HLI3Bv6ltGsCvUd)df!lSH1*AOTznv(MwA_3<0V+1-sFfUc$WKk^Ko= z;iEYdv+p$xIU8K0O2dJBtZ_pEYNRZ%343Oz8k%H#gWyrr(g@Nb^5#QE_Z;ffx>EFp z#_&APW)F+60u(SN1C=`Jq#)%**gAAWP!*f3_1!4Yvx7L#-T4NK8OL7jb%#>0IGWz2 zW&&LM?@k@c)s_Ls5h2Ecn4mne!5R@4pM8dMAFbEp|31C)J*uC)UF+tZn|Jpfz4&3T z6;l=dSFhXz-jYB~-8=7AUIm<%R@o#U_w#-E6+KFay4I1x)}~_!hmJ&JlIzj8T~RuF z+I}5g0X)LxM8ki#q>dnL>U)mJ;>T-1oxMfZ&BwGSJtWC~Rzs>OZ!W$xNpwH5?z7rj zoDs%8PuH`7O=qg%?&S zf2C0O9v0BZyd>t9<9A2Zky$B>khtTlY==ju??8PxUpHgAmJEHV8d$t&CD$UZ^KknhbW^YHy|<3 zQ)hX0z9&`1;cSR~%{)#8_ z?|MT_jxim9<&l13T{hPq$l&O*3<9tV6yU){^$xF9`go3PoM{A+VjB_z(-EGI<;};t zw-l?Px+0x%_2O6Yapx4f;s0``!vA^u1Aw+u=Ph|#MDB7#Etp?N6o;Cv!5Ey(Ra~d_ zr<@15n6*~gJkHZ`1l6@?vPFfAt*lY$Pp%Xz);|CO4QF`|3ak+t*0^T&*0gZmNbXzf z@l0O4QCp|gT3+#|6ryE=dZA+S;p@JnecYcvD~7gO#Q}E2A9F_mPQ4#@I?m*`Rv)-T z+}I)!A2j2aMZs$<fps@V1EGZFCn!$EC^@$^oRaPSpzjabX1c6yCp)mlp}6BXZ8Q4UnH&|}-Wqzyo~9KgZxaR?0PKxKzH}m66p!xorpz_NyZP-ut7Acz)A{1>wK#BAlIr?RnPY!+JzK1rd$E zmLfH^S#oKl;yg;+$k^56UR#f!9oPVc9;5yTbQTwnX1D#f3eKFZ@uLtlx8f2g3@%Qz z>|Y8)JyLy&+qb|@1$KW*FpMcXLHCzaDkYZK05?^tlAZjKbo_8DC~K;EeOHUNV*m7` zH%Xb(jz|KPH%=7)!+)I6ZVu3pcl+Vbq8~x-;;BY zM-lN5V)B}G3BsfAgz_OaaFHi7oP0$bl3j~2zCd~Nm3SME#N^{pwhjL(U5~Y}<0`kF zj-N=TtMfrh#Ro9RGTwpQi&#i2+|7-*^^ zpOG3r(=#K}b8Sg81gEU?2i{%n!GomsRVXf1&1Qp>M9f0u2BpZqFo-nbN^>p^MyoyO zZYmXP`+cg{*~UU2Gqf|Vw9)gR_|+_V1?gDdy2Vf^9(II2UCeUhAWOBCWi--}>k7wJ zF+Fhy3TytFQ9#)B(8Z8-vZJE_LszbcsG)7bRF8`A!s3n4h|>b#h`g?cMrjMicT?g0 zAOER`}`=GrjA=7%TcYykNtZtdy<~=nzzC<#=_ujwmgBrI->;P}> z5H1gOXZDY(tm^N5y6wa5kPHx*rV?)zTm3Xmiq-s?~Lxa**`uIG_HoH(k%rbBYJG8hHx^m110Yn<^+?JC|~Be*tt^Wp8#z(}d~>x(}^v8zAkh@B22J^(By z$A`x8bXmjGAJao#p0oU?L4@F&ho_XU$A-B}^?H8+6&I7wD4xlhUl%rIyj7*`5|Bl2 z=3IPC=CH$W6-ARvKh;a{N8)NfvT+L#QC^wQ=7R+aKCo22zWCmTRv zgR#FDWURmU1}s~|EWyEVnf=yiADNg)Q9ht4`Z%6NVu6<-9&zpjm}Ye*f-fYub9Ix) zy#bd3fah&j(q~O0D7Ae$16f%^o8Ml=n>TInHtOAI-10pjvQlkP&yFqc{CuX}F3e3RSQwDlZ+Y*9V`sv>{n!|vSbp~YxsU6a1`PSVjcEn@_{;_@EUQ6| z)4+jJ3NdSfH5;IKzXfhCqleQ7cJx?v(eSA?( zNc5NzN#k%?o zu@6fi8))Eu2l35|Vt49#5;l}Z*K=FhCdr=ma@H)bx(PXo?fFt#8Qe1(dSc*lY&x9P z4!EJ~o=vU7PhX|~AmVQLe2?euKYq65K!xN;{WW9c0LNa_n(T$jKo&)^+CUoxNgfv& zkbZy?o%6tx24R?h!oD*(8DXdj7_dB|;L@Unp(zHL(W@ntPYB)h` zN)lpsWUxUYjM=N7Lo^F49b1B!qe*`PRgcOPipTCKn#od ziqA3FLKmW^SDOUup|iXoxi9l7TPkDw#&V<%xQAKrCUMq$<$|EwO+0&X%ny%5&*F7- zi>N(!yn=U~K_r=y&?Gv~-Khw3}`p&vz)G7VdU-!$% z$HjYC;%}`IOx}mmS_Z{jh;s!{Jts~)y~6N?`M_M6G&#^m#Bt8eV(6E;@Gw1nkC3S;;GMpbwlw1oS3=DFMVgtSA5YG1 zlI`7{d?<^WZHfh6nN?b2k)7Eq|6I>KA0_fcwc(F&@u$=rC~uJklrEI2jzQ!fM%J}R8{jh~hAzg(ltPt>fja!Z3TkC>i> zKLr&0OqtfE5<6fH1Rp*IW%OnOTEAM_)(6)~V75kM*w@ZWDAtV&);*0gTB*LExC S0{`aaiac0JrtrDZ`~M9-Us$C8 literal 0 HcmV?d00001 diff --git a/docs/source/assets/design/v1/prefix_caching/overview.png b/docs/source/assets/design/v1/prefix_caching/overview.png new file mode 100644 index 0000000000000000000000000000000000000000..14fb985adca032eac15543d559fc2f725a2b3bcf GIT binary patch literal 33028 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 literal 0 HcmV?d00001 diff --git a/docs/source/contributing/overview.md b/docs/source/contributing/overview.md index 908c7cb4d38ee..af09bfecc6499 100644 --- a/docs/source/contributing/overview.md +++ b/docs/source/contributing/overview.md @@ -26,7 +26,7 @@ Check out the [building from source](#build-from-source) documentation for detai pip install -r requirements-dev.txt # Linting, formatting and static type checking -pre-commit install +pre-commit install --hook-type pre-commit --hook-type commit-msg # You can manually run pre-commit with pre-commit run --all-files diff --git a/docs/source/design/v1/prefix_caching.md b/docs/source/design/v1/prefix_caching.md new file mode 100644 index 0000000000000..dc8432baef9d9 --- /dev/null +++ b/docs/source/design/v1/prefix_caching.md @@ -0,0 +1,228 @@ +# Automatic Prefix Caching + +Prefix caching kv-cache blocks is a popular optimization in LLM inference to avoid redundant prompt computations. The core idea is simple – we cache the kv-cache blocks of processed requests, and reuse these blocks when a new request comes in with the same prefix as previous requests. Since prefix caching is almost a free lunch and won’t change model outputs, it has been widely used by many public endpoints (e.g., OpenAI, Anthropic, etc) and most open source LLM inference frameworks (e.g., SGLang). + +While there are many ways to implement prefix caching, vLLM chooses a hash-based approach. Specifically, we hash each kv-cache block by the tokens in the block and the tokens in the prefix before the block: + +```text + Block 1 Block 2 Block 3 + [A gentle breeze stirred] [the leaves as children] [laughed in the distance] +Block 1: |<--- block tokens ---->| +Block 2: |<------- prefix ------>| |<--- block tokens --->| +Block 3: |<------------------ prefix -------------------->| |<--- block tokens ---->| +``` + +In the example above, the KV cache in the first block can be uniquely identified with the token “A gentle breeze stirred”. The third block can be uniquely identified with the tokens in the block “laughed in the distance”, along with the prefix tokens “A gentle breeze stirred the leaves as children”. Therefore, we can build the block hash of `hash(tuple[components])`, where components are: + +* Parent hash value: The hash value of the parent hash block. +* Block tokens: A tuple of tokens in this block. The reason to include the exact tokens is to reduce potential hash value collision. +* Extra hashes: Other values required to make this block unique, such as LoRA IDs and multi-modality input hashes (see the example below). + +Note 1: We only cache full blocks. + +Note 2: The above hash key structure is not 100% collision free. Theoretically it’s still possible for the different prefix tokens to have the same hash value, but this should be nearly impossible to happen. Of course, contributions are welcome if you have an awesome idea to eliminate collusion entirely. + +**A hashing example with multi-modality inputs** +In this example, we illustrate how prefix caching works with multi-modality inputs (e.g., images). Assuming we have a request with the following messages: + +```text +messages = [ + {"role": "user", + "content": [ + {"type": "text", + "text": "What's in this image?" + }, + {"type": "image_url", + "image_url": {"url": image_url}, + }, + ]}, +] +``` + +It will become the following prompt: + +```text +Prompt: + [INST]What's in this image?\n[IMG][/INST] + +Tokenized prompt: + [1, 3, 7493, 1681, 1294, 1593, 3937, 9551, 10, 4] + +Prompt with placeholders (

): + [1, 3, 7493, 1681, 1294, 1593, 3937, 9551,

,

, ...,

, 4] +``` + +As we can see, after the tokenization, the `[IMG]` will be replaced by a sequence of placeholder tokens, and these placeholders will be replaced by image embeddings during prefill. The challenge for prefix caching to support this case is we need to differentiate images from the placeholders. To address this problem, we encode the image hash generated by the frontend image processor. For example, the hash of the blocks in the above prompt would be (assuming block size 16, and we have 41 placeholder tokens): + +```text +Block 0 + Parent hash: None + Token IDs: 1, 3, 7493, 1681, 1294, 1593, 3937, 9551,

, ...,

+ Extra hash: +Block 1 + Parent hash: Block 0 hash + Token IDs:

, ...,

+ Extra hash: +Block 2 + Parent hash: Block 1 hash + Token IDs:

, ...,

+ Extra hash: +Block 3 + Parent hash: Block 2 hash + Token IDs:

, ...,

, 4 + Extra hash: +``` + +In the rest of this document, we first introduce the data structure used for prefix caching in vLLM v1, followed by the prefix caching workflow of major KV cache operators (e.g., allocate, append, free, eviction). Finally, we use an example to illustrate the end to end prefix caching workflow. + +## Data Structure + +The prefix caching in vLLM v1 is implemented in the KV cache manager. The basic building block is the “Block” data class (simplified): + +```python +class KVCacheBlock: + # The block ID (immutable) + block_id: int + # The block hash (will be assigned when the block is full, + # and will be reset when the block is evicted). + block_hash: BlockHashType + # The number of requests using this block now. + ref_cnt: int + + # The pointers to form a doubly linked list for the free queue. + prev_free_block: Optional["KVCacheBlock"] = None + next_free_block: Optional["KVCacheBlock"] = None +``` + +There are two design points to highlight: + +1. We allocate all KVCacheBlock when initializing the KV cache manager to be a block pool. This avoids Python object creation overheads and can easily track all blocks all the time. +2. We introduce doubly linked list pointers directly in the KVCacheBlock, so that we could construct a free queue directly. This gives us two benefits: + 1. We could have O(1) complexity moving elements in the middle to the tail. + 2. We could avoid introducing another Python queue (e.g., `deque`) which has a wrapper to the elements. + +As a result, we will have the following components when the KV cache manager is initialized: + +:::{image} /assets/design/v1/prefix_caching/overview.png +:alt: Component Overview +::: + +* Block Pool: A list of KVCacheBlock. +* Free Block Queue: Only store the pointers of head and tail blocks for manipulations. +* Cache blocks: Mapping from hash key to block IDs. +* Request blocks: Mapping from request ID to allocated block IDs. + +## Operations + +### Block Allocation + +**New request:** Workflow for the scheduler to schedule a new request with KV cache block allocation: + +1. The scheduler calls `kv_cache_manager.get_computed_blocks()` to get a sequence of blocks that have already been computed. This is done by hashing the prompt tokens in the request and looking up Cache Blocks. +2. The scheduler calls `kv_cache_manager.allocate_slots()`. It does the following steps: + 1. Compute the number of new required blocks, and return if there are no sufficient blocks to allocate. + 2. “Touch” the computed blocks. It increases the reference count of the computed block by one, and removes the block from the free queue if the block wasn’t used by other requests. This is to avoid these computed blocks being evicted. See the example in the next section for illustration. + 3. Allocate new blocks by popping the heads of the free queue. If the head block is a cached block, this also “evicts” the block so that no other requests can reuse it anymore from now on. + 4. If an allocated block is already full of tokens, we immediately add it to the Cache Block, so that the block can be reused by other requests in the same batch. + +**Running request:** Workflow for the scheduler to schedule a running request with KV cache block allocation: + +1. The scheduler calls `kv_cache_manager.append_slots()`. It does the following steps: + 1. Compute the number of new required blocks, and return if there are no sufficient blocks to allocate. + 2. Allocate new blocks by popping the heads of the free queue. If the head block is a cached block, this also “evicts” the block so that no other requests can reuse it anymore from now on. + 3. Append token IDs to the slots in existing blocks as well as the new blocks. If a block is full, we add it to the Cache Block to cache it. + +**Duplicated blocks** +Assuming block size is 4 and you send a request (Request 1\) with prompt ABCDEF and decoding length 3: + +```text +Prompt: [A, B, C, D, E, F] +Output: [G, H, I] + +Time 0: + Tokens: [A, B, C, D, E, F, G] + Block Table: [0 (ABCD), 1 (EFG)] + Cache Blocks: 0 +Time 1: + Tokens: [A, B, C, D, E, F, G, H] + Block Table: [0 (ABCD), 1 (EFGH)] + Cache Blocks: 0, 1 +Time 2: + Tokens: [A, B, C, D, E, F, G, H, I] + Block Table: [0 (ABCD), 1 (EFGH), 2 (I)] + Cache Blocks: 0, 1 +``` + +Now block 0 and block 1 are cached, and we send the same request again (Request 2\) with greedy sampling, so that it will produce exactly the same outputs as the Request 1: + +```text +Prompt: [A, B, C, D, E, F] +Output: [G, H, I] + +Time 0: + Tokens: [A, B, C, D, E, F, G] + Block Table: [0 (ABCD), 3 (EFG)] + Cache Blocks: 0, 1 +Time 1: + Tokens: [A, B, C, D, E, F, G, H] + Block Table: [0 (ABCD), 3 (EFGH)] + Cache Blocks: 0, 1, 3 +``` + +As can be seen, block 3 is a new full block and is cached. However, it is redundant as block 1, meaning that we cached the same block twice. In v0, when detecting block 3 is duplicated, we free block 3 and let Request 2 use block 1 instead, so its block table becomes `[0, 1]` in Time 1. However, the block table in vLLM v1 is append-only, meaning that changing the block table from `[0, 3]` to `[0, 1]` is not allowed. As a result, we will have duplicated blocks for the hash key E-H. This duplication will be eliminated when the request is freed. + +### Free + +When a request is finished, we free all its blocks if no other requests are using them (reference count = 0). In this example, we free request 1 and block 2, 3, 4, 8 associated with it. We can see that the freed blocks are added to the tail of the free queue in the *reverse* order. This is because the last block of a request must hash more tokens and is less likely to be reused by other requests. As a result, it should be evicted first. + +:::{image} /assets/design/v1/prefix_caching/free.png +:alt: Free Queue after Free a Request +::: + +### Eviction (LRU) + +When the head block (least recently used block) of the free queue is cached, we have to evict the block to prevent it from being used by other requests. Specifically, eviction involves the following steps: + +1. Pop the block from the head of the free queue. This is the LRU black to be evicted. +2. Remove the block ID from the Cache Block. +3. Remove the block hash. + +## Example + +In this example, we assume the block size is 4 (each block can cache 4 tokens), and we have 10 blocks in the KV-cache manager in total. + +**Time 1: The cache is empty and a new request comes in.** We allocate 4 blocks. 3 of them are already full and cached. The fourth block is partially full with 2 of 4 tokens. + +:::{image} /assets/design/v1/prefix_caching/example-time-1.png +:alt: Example Time 1 +::: + +**Time 3: Request 0 makes the block 3 full and asks for a new block to keep decoding.** We cache block 3 and allocate block 4. + +:::{image} /assets/design/v1/prefix_caching/example-time-3.png +:alt: Example Time 3 +::: + +**Time 4: Request 1 comes in with the 14 prompt tokens, where the first 11 tokens are the same as request 0.** We can see that only 2 blocks (11 tokens) hit the cache, because the 3rd block only matches 3 of 4 tokens. + +:::{image} /assets/design/v1/prefix_caching/example-time-4.png +:alt: Example Time 4 +::: + +**Time 5: Request 0 is finished and free.** Blocks 2, 3 and 4 are added to the free queue in the reverse order (but block 2 and 3 are still cached). Block 0 and 1 are not added to the free queue because they are being used by Request 1. + +:::{image} /assets/design/v1/prefix_caching/example-time-5.png +:alt: Example Time 5 +::: + +**Time 6: Request 1 is finished and free.** + +:::{image} /assets/design/v1/prefix_caching/example-time-6.png +:alt: Example Time 6 +::: + +**Time 7: Request 2 comes in with the 33 prompt tokens, where the first 16 tokens are the same as request 0\.** Note that even the block order in the free queue was `7 - 8 - 9 - 4 - 3 - 2 - 6 - 5 - 1 - 0`, the cache hit blocks (i.e., 0, 1, 2) are touched and removed from the queue before allocation, so the free queue becomes `7 - 8 - 9 - 4 - 3 - 6 - 5`. As a result, the allocated blocks are 0 (cached), 1 (cached), 2 (cached), 7, 8, 9, 4, 3 (evicted). + +:::{image} /assets/design/v1/prefix_caching/example-time-7.png +:alt: Example Time 7 +::: diff --git a/docs/source/features/quantization/index.md b/docs/source/features/quantization/index.md index d972dc85fc23c..1c98620aa2145 100644 --- a/docs/source/features/quantization/index.md +++ b/docs/source/features/quantization/index.md @@ -12,6 +12,7 @@ supported_hardware auto_awq bnb gguf +int4 int8 fp8 quantized_kvcache diff --git a/docs/source/features/quantization/int4.md b/docs/source/features/quantization/int4.md new file mode 100644 index 0000000000000..f8939e5bf0150 --- /dev/null +++ b/docs/source/features/quantization/int4.md @@ -0,0 +1,166 @@ +(int4)= + +# INT4 W4A16 + +vLLM supports quantizing weights to INT4 for memory savings and inference acceleration. This quantization method is particularly useful for reducing model size and maintaining low latency in workloads with low queries per second (QPS). + +Please visit the HF collection of [quantized INT4 checkpoints of popular LLMs ready to use with vLLM](https://huggingface.co/collections/neuralmagic/int4-llms-for-vllm-668ec34bf3c9fa45f857df2c). + +:::{note} +INT4 computation is supported on NVIDIA GPUs with compute capability > 8.0 (Ampere, Ada Lovelace, Hopper, Blackwell). +::: + +## Prerequisites + +To use INT4 quantization with vLLM, you'll need to install the [llm-compressor](https://github.com/vllm-project/llm-compressor/) library: + +```console +pip install llmcompressor +``` + +## Quantization Process + +The quantization process involves four main steps: + +1. Loading the model +2. Preparing calibration data +3. Applying quantization +4. Evaluating accuracy in vLLM + +### 1. Loading the Model + +Load your model and tokenizer using the standard `transformers` AutoModel classes: + +```python +from transformers import AutoTokenizer, AutoModelForCausalLM + +MODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct" +model = AutoModelForCausalLM.from_pretrained( + MODEL_ID, device_map="auto", torch_dtype="auto", +) +tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) +``` + +### 2. Preparing Calibration Data + +When quantizing weights to INT4, you need sample data to estimate the weight updates and calibrated scales. +It's best to use calibration data that closely matches your deployment data. +For a general-purpose instruction-tuned model, you can use a dataset like `ultrachat`: + +```python +from datasets import load_dataset + +NUM_CALIBRATION_SAMPLES = 512 +MAX_SEQUENCE_LENGTH = 2048 + +# Load and preprocess the dataset +ds = load_dataset("HuggingFaceH4/ultrachat_200k", split="train_sft") +ds = ds.shuffle(seed=42).select(range(NUM_CALIBRATION_SAMPLES)) + +def preprocess(example): + return {"text": tokenizer.apply_chat_template(example["messages"], tokenize=False)} +ds = ds.map(preprocess) + +def tokenize(sample): + return tokenizer(sample["text"], padding=False, max_length=MAX_SEQUENCE_LENGTH, truncation=True, add_special_tokens=False) +ds = ds.map(tokenize, remove_columns=ds.column_names) +``` + +### 3. Applying Quantization + +Now, apply the quantization algorithms: + +```python +from llmcompressor.transformers import oneshot +from llmcompressor.modifiers.quantization import GPTQModifier +from llmcompressor.modifiers.smoothquant import SmoothQuantModifier + +# Configure the quantization algorithms +recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"]) + +# Apply quantization +oneshot( + model=model, + dataset=ds, + recipe=recipe, + max_seq_length=MAX_SEQUENCE_LENGTH, + num_calibration_samples=NUM_CALIBRATION_SAMPLES, +) + +# Save the compressed model +SAVE_DIR = MODEL_ID.split("/")[1] + "-W4A16-G128" +model.save_pretrained(SAVE_DIR, save_compressed=True) +tokenizer.save_pretrained(SAVE_DIR) +``` + +This process creates a W4A16 model with weights quantized to 4-bit integers. + +### 4. Evaluating Accuracy + +After quantization, you can load and run the model in vLLM: + +```python +from vllm import LLM +model = LLM("./Meta-Llama-3-8B-Instruct-W4A16-G128") +``` + +To evaluate accuracy, you can use `lm_eval`: + +```console +$ lm_eval --model vllm \ + --model_args pretrained="./Meta-Llama-3-8B-Instruct-W4A16-G128",add_bos_token=true \ + --tasks gsm8k \ + --num_fewshot 5 \ + --limit 250 \ + --batch_size 'auto' +``` + +:::{note} +Quantized models can be sensitive to the presence of the `bos` token. Make sure to include the `add_bos_token=True` argument when running evaluations. +::: + +## Best Practices + +- Start with 512 samples for calibration data, and increase if accuracy drops +- Ensure the calibration data contains a high variety of samples to prevent overfitting towards a specific use case +- Use a sequence length of 2048 as a starting point +- Employ the chat template or instruction template that the model was trained with +- If you've fine-tuned a model, consider using a sample of your training data for calibration +- Tune key hyperparameters to the quantization algorithm: + - `dampening_frac` sets how much influence the GPTQ algorithm has. Lower values can improve accuracy, but can lead to numerical instabilities that cause the algorithm to fail. + - `actorder` sets the activation ordering. When compressing the weights of a layer weight, the order in which channels are quantized matters. Setting `actorder="weight"` can improve accuracy without added latency. + +The following is an example of an expanded quantization recipe you can tune to your own use case: + +```python +from compressed_tensors.quantization import ( + QuantizationArgs, + QuantizationScheme, + QuantizationStrategy, + QuantizationType, +) +recipe = GPTQModifier( + targets="Linear", + config_groups={ + "config_group": QuantizationScheme( + targets=["Linear"], + weights=QuantizationArgs( + num_bits=4, + type=QuantizationType.INT, + strategy=QuantizationStrategy.GROUP, + group_size=128, + symmetric=True, + dynamic=False, + actorder="weight", + ), + ), + }, + ignore=["lm_head"], + update_size=NUM_CALIBRATION_SAMPLES, + dampening_frac=0.01 +) +``` + +## Troubleshooting and Support + +If you encounter any issues or have feature requests, please open an issue on the [`vllm-project/llm-compressor`](https://github.com/vllm-project/llm-compressor) GitHub repository. The full INT4 quantization example in `llm-compressor` is available [here](https://github.com/vllm-project/llm-compressor/blob/main/examples/quantization_w4a16/llama3_example.py). diff --git a/docs/source/features/quantization/int8.md b/docs/source/features/quantization/int8.md index fedb16f4350e5..b381f34bccd34 100644 --- a/docs/source/features/quantization/int8.md +++ b/docs/source/features/quantization/int8.md @@ -8,7 +8,7 @@ This quantization method is particularly useful for reducing model size while ma Please visit the HF collection of [quantized INT8 checkpoints of popular LLMs ready to use with vLLM](https://huggingface.co/collections/neuralmagic/int8-llms-for-vllm-668ec32c049dca0369816415). :::{note} -INT8 computation is supported on NVIDIA GPUs with compute capability > 7.5 (Turing, Ampere, Ada Lovelace, Hopper). +INT8 computation is supported on NVIDIA GPUs with compute capability > 7.5 (Turing, Ampere, Ada Lovelace, Hopper, Blackwell). ::: ## Prerequisites @@ -132,4 +132,4 @@ Quantized models can be sensitive to the presence of the `bos` token. Make sure ## Troubleshooting and Support -If you encounter any issues or have feature requests, please open an issue on the `vllm-project/llm-compressor` GitHub repository. +If you encounter any issues or have feature requests, please open an issue on the [`vllm-project/llm-compressor`](https://github.com/vllm-project/llm-compressor) GitHub repository. diff --git a/docs/source/getting_started/installation/ai_accelerator/hpu-gaudi.inc.md b/docs/source/getting_started/installation/ai_accelerator/hpu-gaudi.inc.md index 704a16233981f..f3b0d6dc9bdc8 100644 --- a/docs/source/getting_started/installation/ai_accelerator/hpu-gaudi.inc.md +++ b/docs/source/getting_started/installation/ai_accelerator/hpu-gaudi.inc.md @@ -2,6 +2,10 @@ This tab provides instructions on running vLLM with Intel Gaudi devices. +:::{attention} +There are no pre-built wheels or images for this device, so you must build vLLM from source. +::: + ## Requirements - OS: Ubuntu 22.04 LTS diff --git a/docs/source/getting_started/installation/ai_accelerator/index.md b/docs/source/getting_started/installation/ai_accelerator/index.md index 88352f639567b..01793572fee7c 100644 --- a/docs/source/getting_started/installation/ai_accelerator/index.md +++ b/docs/source/getting_started/installation/ai_accelerator/index.md @@ -5,7 +5,8 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU +:selected: :sync: tpu :::{include} tpu.inc.md @@ -25,7 +26,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md @@ -52,7 +53,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU :sync: tpu :::{include} tpu.inc.md @@ -72,7 +73,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md @@ -99,7 +100,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU :sync: tpu :::{include} tpu.inc.md @@ -119,7 +120,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md @@ -146,7 +147,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU :sync: tpu :::{include} tpu.inc.md @@ -166,7 +167,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md @@ -193,7 +194,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU :sync: tpu :::{include} tpu.inc.md @@ -213,7 +214,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md @@ -242,7 +243,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU :sync: tpu :::{include} tpu.inc.md @@ -262,7 +263,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md @@ -289,7 +290,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU :sync: tpu :::{include} tpu.inc.md @@ -309,7 +310,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md @@ -336,7 +337,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::::{tab-set} :sync-group: device -::::{tab-item} TPU +::::{tab-item} Google TPU :sync: tpu :::{include} tpu.inc.md @@ -354,7 +355,7 @@ vLLM is a Python library that supports the following AI accelerators. Select you :::: -::::{tab-item} Neuron +::::{tab-item} AWS Neuron :sync: neuron :::{include} neuron.inc.md diff --git a/docs/source/getting_started/installation/ai_accelerator/neuron.inc.md b/docs/source/getting_started/installation/ai_accelerator/neuron.inc.md index 145cc9d668efd..f149818acafb8 100644 --- a/docs/source/getting_started/installation/ai_accelerator/neuron.inc.md +++ b/docs/source/getting_started/installation/ai_accelerator/neuron.inc.md @@ -4,6 +4,10 @@ vLLM 0.3.3 onwards supports model inferencing and serving on AWS Trainium/Infere Paged Attention and Chunked Prefill are currently in development and will be available soon. Data types currently supported in Neuron SDK are FP16 and BF16. +:::{attention} +There are no pre-built wheels or images for this device, so you must build vLLM from source. +::: + ## Requirements - OS: Linux diff --git a/docs/source/getting_started/installation/ai_accelerator/openvino.inc.md b/docs/source/getting_started/installation/ai_accelerator/openvino.inc.md index a7867472583d6..112e8d4d9b256 100644 --- a/docs/source/getting_started/installation/ai_accelerator/openvino.inc.md +++ b/docs/source/getting_started/installation/ai_accelerator/openvino.inc.md @@ -2,6 +2,10 @@ vLLM powered by OpenVINO supports all LLM models from [vLLM supported models list](#supported-models) and can perform optimal model serving on all x86-64 CPUs with, at least, AVX2 support, as well as on both integrated and discrete Intel® GPUs ([the list of supported GPUs](https://docs.openvino.ai/2024/about-openvino/release-notes-openvino/system-requirements.html#gpu)). +:::{attention} +There are no pre-built wheels or images for this device, so you must build vLLM from source. +::: + ## Requirements - OS: Linux diff --git a/docs/source/getting_started/installation/ai_accelerator/tpu.inc.md b/docs/source/getting_started/installation/ai_accelerator/tpu.inc.md index 6827afc805fd8..c0d50feafce56 100644 --- a/docs/source/getting_started/installation/ai_accelerator/tpu.inc.md +++ b/docs/source/getting_started/installation/ai_accelerator/tpu.inc.md @@ -30,6 +30,10 @@ For TPU pricing information, see [Cloud TPU pricing](https://cloud.google.com/tp You may need additional persistent storage for your TPU VMs. For more information, see [Storage options for Cloud TPU data](https://cloud.devsite.corp.google.com/tpu/docs/storage-options). +:::{attention} +There are no pre-built wheels for this device, so you must either use the pre-built Docker image or build vLLM from source. +::: + ## Requirements - Google Cloud TPU VM diff --git a/docs/source/getting_started/installation/cpu/apple.inc.md b/docs/source/getting_started/installation/cpu/apple.inc.md index 0808b869fdb7b..3bf1d47fa0ff9 100644 --- a/docs/source/getting_started/installation/cpu/apple.inc.md +++ b/docs/source/getting_started/installation/cpu/apple.inc.md @@ -4,6 +4,10 @@ vLLM has experimental support for macOS with Apple silicon. For now, users shall Currently the CPU implementation for macOS supports FP32 and FP16 datatypes. +:::{attention} +There are no pre-built wheels or images for this device, so you must build vLLM from source. +::: + ## Requirements - OS: `macOS Sonoma` or later diff --git a/docs/source/getting_started/installation/cpu/arm.inc.md b/docs/source/getting_started/installation/cpu/arm.inc.md index 08a764e1a25f4..a661a0ca5adc7 100644 --- a/docs/source/getting_started/installation/cpu/arm.inc.md +++ b/docs/source/getting_started/installation/cpu/arm.inc.md @@ -4,6 +4,10 @@ vLLM has been adapted to work on ARM64 CPUs with NEON support, leveraging the CP ARM CPU backend currently supports Float32, FP16 and BFloat16 datatypes. +:::{attention} +There are no pre-built wheels or images for this device, so you must build vLLM from source. +::: + ## Requirements - OS: Linux diff --git a/docs/source/getting_started/installation/cpu/index.md b/docs/source/getting_started/installation/cpu/index.md index 2f549ede0cf48..d53430403583c 100644 --- a/docs/source/getting_started/installation/cpu/index.md +++ b/docs/source/getting_started/installation/cpu/index.md @@ -5,7 +5,8 @@ vLLM is a Python library that supports the following CPU variants. Select your C :::::{tab-set} :sync-group: device -::::{tab-item} x86 +::::{tab-item} Intel/AMD x86 +:selected: :sync: x86 :::{include} x86.inc.md @@ -15,7 +16,7 @@ vLLM is a Python library that supports the following CPU variants. Select your C :::: -::::{tab-item} ARM +::::{tab-item} ARM AArch64 :sync: arm :::{include} arm.inc.md @@ -44,7 +45,7 @@ vLLM is a Python library that supports the following CPU variants. Select your C :::::{tab-set} :sync-group: device -::::{tab-item} x86 +::::{tab-item} Intel/AMD x86 :sync: x86 :::{include} x86.inc.md @@ -54,7 +55,7 @@ vLLM is a Python library that supports the following CPU variants. Select your C :::: -::::{tab-item} ARM +::::{tab-item} ARM AArch64 :sync: arm :::{include} arm.inc.md @@ -92,7 +93,7 @@ Currently, there are no pre-built CPU wheels. :::::{tab-set} :sync-group: device -::::{tab-item} x86 +::::{tab-item} Intel/AMD x86 :sync: x86 :::{include} x86.inc.md @@ -102,7 +103,7 @@ Currently, there are no pre-built CPU wheels. :::: -::::{tab-item} ARM +::::{tab-item} ARM AArch64 :sync: arm :::{include} arm.inc.md diff --git a/docs/source/getting_started/installation/cpu/x86.inc.md b/docs/source/getting_started/installation/cpu/x86.inc.md index f146ae0918b44..1dafc3660060e 100644 --- a/docs/source/getting_started/installation/cpu/x86.inc.md +++ b/docs/source/getting_started/installation/cpu/x86.inc.md @@ -2,12 +2,20 @@ vLLM initially supports basic model inferencing and serving on x86 CPU platform, with data types FP32, FP16 and BF16. +:::{attention} +There are no pre-built wheels or images for this device, so you must build vLLM from source. +::: + ## Requirements - OS: Linux - Compiler: `gcc/g++ >= 12.3.0` (optional, recommended) - Instruction Set Architecture (ISA): AVX512 (optional, recommended) +:::{tip} +[Intel Extension for PyTorch (IPEX)](https://github.com/intel/intel-extension-for-pytorch) extends PyTorch with up-to-date features optimizations for an extra performance boost on Intel hardware. +::: + ## Set up using Python ### Pre-built wheels @@ -29,7 +37,3 @@ vLLM initially supports basic model inferencing and serving on x86 CPU platform, ### Build image from source ## Extra information - -## Intel Extension for PyTorch - -- [Intel Extension for PyTorch (IPEX)](https://github.com/intel/intel-extension-for-pytorch) extends PyTorch with up-to-date features optimizations for an extra performance boost on Intel hardware. diff --git a/docs/source/getting_started/installation/gpu/index.md b/docs/source/getting_started/installation/gpu/index.md index 0a61f889753a3..f82c4bda28620 100644 --- a/docs/source/getting_started/installation/gpu/index.md +++ b/docs/source/getting_started/installation/gpu/index.md @@ -5,7 +5,8 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA +:selected: :sync: cuda :::{include} cuda.inc.md @@ -15,7 +16,7 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm :::{include} rocm.inc.md @@ -25,7 +26,7 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu :::{include} xpu.inc.md @@ -45,7 +46,7 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA :sync: cuda :::{include} cuda.inc.md @@ -55,7 +56,7 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm :::{include} rocm.inc.md @@ -65,7 +66,7 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu :::{include} xpu.inc.md @@ -87,7 +88,7 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA :sync: cuda :::{include} cuda.inc.md @@ -97,14 +98,14 @@ vLLM is a Python library that supports the following GPU variants. Select your G :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm There is no extra information on creating a new Python environment for this device. :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu There is no extra information on creating a new Python environment for this device. @@ -118,7 +119,7 @@ There is no extra information on creating a new Python environment for this devi :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA :sync: cuda :::{include} cuda.inc.md @@ -128,7 +129,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm :::{include} rocm.inc.md @@ -138,7 +139,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu :::{include} xpu.inc.md @@ -157,7 +158,7 @@ There is no extra information on creating a new Python environment for this devi :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA :sync: cuda :::{include} cuda.inc.md @@ -167,7 +168,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm :::{include} rocm.inc.md @@ -177,7 +178,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu :::{include} xpu.inc.md @@ -196,7 +197,7 @@ There is no extra information on creating a new Python environment for this devi :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA :sync: cuda :::{include} cuda.inc.md @@ -206,7 +207,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm :::{include} rocm.inc.md @@ -216,7 +217,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu :::{include} xpu.inc.md @@ -233,7 +234,7 @@ There is no extra information on creating a new Python environment for this devi :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA :sync: cuda :::{include} cuda.inc.md @@ -243,7 +244,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm :::{include} rocm.inc.md @@ -253,7 +254,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu :::{include} xpu.inc.md @@ -270,7 +271,7 @@ There is no extra information on creating a new Python environment for this devi :::::{tab-set} :sync-group: device -::::{tab-item} CUDA +::::{tab-item} NVIDIA CUDA :sync: cuda :::{include} cuda.inc.md @@ -279,7 +280,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} ROCm +::::{tab-item} AMD ROCm :sync: rocm :::{include} rocm.inc.md @@ -288,7 +289,7 @@ There is no extra information on creating a new Python environment for this devi :::: -::::{tab-item} XPU +::::{tab-item} Intel XPU :sync: xpu :::{include} xpu.inc.md diff --git a/docs/source/getting_started/installation/gpu/rocm.inc.md b/docs/source/getting_started/installation/gpu/rocm.inc.md index 131ad1704ea11..c8fd11415cfda 100644 --- a/docs/source/getting_started/installation/gpu/rocm.inc.md +++ b/docs/source/getting_started/installation/gpu/rocm.inc.md @@ -2,6 +2,10 @@ vLLM supports AMD GPUs with ROCm 6.2. +:::{attention} +There are no pre-built wheels for this device, so you must either use the pre-built Docker image or build vLLM from source. +::: + ## Requirements - GPU: MI200s (gfx90a), MI300 (gfx942), Radeon RX 7900 series (gfx1100) @@ -13,14 +17,6 @@ vLLM supports AMD GPUs with ROCm 6.2. Currently, there are no pre-built ROCm wheels. -However, the [AMD Infinity hub for vLLM](https://hub.docker.com/r/rocm/vllm/tags) offers a prebuilt, optimized -docker image designed for validating inference performance on the AMD Instinct™ MI300X accelerator. - -:::{tip} -Please check [LLM inference performance validation on AMD Instinct MI300X](https://rocm.docs.amd.com/en/latest/how-to/performance-validation/mi300x/vllm-benchmark.html) -for instructions on how to use this prebuilt docker image. -::: - ### Build wheel from source 0. Install prerequisites (skip if you are already in an environment/docker with the following installed): @@ -112,7 +108,13 @@ for instructions on how to use this prebuilt docker image. ### Pre-built images -Currently, there are no pre-built ROCm images. +The [AMD Infinity hub for vLLM](https://hub.docker.com/r/rocm/vllm/tags) offers a prebuilt, optimized +docker image designed for validating inference performance on the AMD Instinct™ MI300X accelerator. + +:::{tip} +Please check [LLM inference performance validation on AMD Instinct MI300X](https://rocm.docs.amd.com/en/latest/how-to/performance-validation/mi300x/vllm-benchmark.html) +for instructions on how to use this prebuilt docker image. +::: ### Build image from source diff --git a/docs/source/getting_started/installation/gpu/xpu.inc.md b/docs/source/getting_started/installation/gpu/xpu.inc.md index bc01c6000bc07..4116826789e5c 100644 --- a/docs/source/getting_started/installation/gpu/xpu.inc.md +++ b/docs/source/getting_started/installation/gpu/xpu.inc.md @@ -2,6 +2,10 @@ vLLM initially supports basic model inferencing and serving on Intel GPU platform. +:::{attention} +There are no pre-built wheels or images for this device, so you must build vLLM from source. +::: + ## Requirements - Supported Hardware: Intel Data Center GPU, Intel ARC GPU diff --git a/docs/source/getting_started/installation/index.md b/docs/source/getting_started/installation/index.md index 0f5e013ce071a..c64c3a7208eeb 100644 --- a/docs/source/getting_started/installation/index.md +++ b/docs/source/getting_started/installation/index.md @@ -6,8 +6,23 @@ vLLM supports the following hardware platforms: :::{toctree} :maxdepth: 1 +:hidden: gpu/index cpu/index ai_accelerator/index ::: + +- + - NVIDIA CUDA + - AMD ROCm + - Intel XPU +- + - Intel/AMD x86 + - ARM AArch64 + - Apple silicon +- + - Google TPU + - Intel Gaudi + - AWS Neuron + - OpenVINO diff --git a/docs/source/index.md b/docs/source/index.md index e90e81c72860a..ee25678e2c418 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -153,6 +153,13 @@ design/automatic_prefix_caching design/multiprocessing ::: +:::{toctree} +:caption: V1 Design Documents +:maxdepth: 2 + +design/v1/prefix_caching +::: + % How to contribute to the vLLM project :::{toctree} diff --git a/format.sh b/format.sh index 4bcd0be0c96e5..3e78bf9865f0d 100755 --- a/format.sh +++ b/format.sh @@ -1,5 +1,6 @@ #!/bin/bash echo "vLLM linting system has been moved from format.sh to pre-commit hook." -echo "Please run 'pip install -r requirements-lint.txt' and 'pre-commit install' to install the pre-commit hook." +echo "Please run 'pip install -r requirements-lint.txt', followed by" +echo "'pre-commit install --hook-type pre-commit --hook-type commit-msg' to install the pre-commit hook." echo "Then linters will run automatically before each commit." diff --git a/tests/v1/core/test_kv_cache_utils.py b/tests/v1/core/test_kv_cache_utils.py index f4081766e39a2..0a5ba1f98221f 100644 --- a/tests/v1/core/test_kv_cache_utils.py +++ b/tests/v1/core/test_kv_cache_utils.py @@ -192,7 +192,7 @@ def test_hash_block_tokens(): extra_keys) assert isinstance(block_hash, BlockHashType) assert block_hash.hash_value == hash( - (parent_block_hash, *curr_block_token_ids)) + (parent_block_hash, curr_block_token_ids, extra_keys)) assert block_hash.token_ids == curr_block_token_ids assert block_hash.extra_keys == extra_keys @@ -227,6 +227,38 @@ def test_hash_request_tokens(): assert block_hashes[1].extra_keys == ("hash2", ) +def test_hash_tokens_different_mm_input(): + request1 = make_request( + request_id=0, + prompt_token_ids=[_ for _ in range(6)], + mm_positions=[{ + "offset": 0, + "length": 3 + }, { + "offset": 3, + "length": 3 + }], + mm_hashes=["hash1", "hash2"], + ) + request2 = make_request( + request_id=1, + prompt_token_ids=[_ for _ in range(6)], + mm_positions=[{ + "offset": 0, + "length": 3 + }, { + "offset": 3, + "length": 3 + }], + mm_hashes=["hash3", "hash2"], + ) + block_size = 3 + block_hashes1 = hash_request_tokens(block_size, request1) + block_hashes2 = hash_request_tokens(block_size, request2) + assert block_hashes1[0] != block_hashes2[0] + assert block_hashes1[1] != block_hashes2[1] + + def test_hash_request_tokens_no_mm_inputs(): request = make_request( request_id=0, diff --git a/vllm/_custom_ops.py b/vllm/_custom_ops.py index aa451352f979f..18bedbf728599 100644 --- a/vllm/_custom_ops.py +++ b/vllm/_custom_ops.py @@ -456,6 +456,11 @@ def cutlass_scaled_mm_supports_fp8(cuda_device_capability: int) -> bool: return torch.ops._C.cutlass_scaled_mm_supports_fp8(cuda_device_capability) +def cutlass_scaled_mm_supports_block_fp8(cuda_device_capability: int) -> bool: + return torch.ops._C.cutlass_scaled_mm_supports_block_fp8( + cuda_device_capability) + + def cutlass_scaled_mm(a: torch.Tensor, b: torch.Tensor, scale_a: torch.Tensor, diff --git a/vllm/attention/backends/mla/utils.py b/vllm/attention/backends/mla/utils.py index c6c8a6034e20f..e8fec234c0225 100644 --- a/vllm/attention/backends/mla/utils.py +++ b/vllm/attention/backends/mla/utils.py @@ -1,17 +1,29 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any, Dict, Generic, List, Optional +from typing import Any, Dict, Generic, List, Optional, Tuple import torch +from compressed_tensors.quantization import QuantizationStrategy from vllm import _custom_ops as ops from vllm import envs from vllm.attention.backends.abstract import (AttentionLayer, AttentionMetadata, MLAAttentionImpl, T) -from vllm.distributed import get_tensor_model_parallel_world_size +from vllm.distributed import (get_tensor_model_parallel_world_size, + tensor_model_parallel_all_reduce) from vllm.model_executor.layers.linear import (ColumnParallelLinear, - RowParallelLinear) + LinearBase, RowParallelLinear, + UnquantizedLinearMethod) +from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors import ( # noqa: E501 + CompressedTensorsLinearMethod) +from vllm.model_executor.layers.quantization.compressed_tensors.schemes import ( + CompressedTensorsW8A8Fp8) +from vllm.model_executor.layers.quantization.fp8 import Fp8LinearMethod +from vllm.model_executor.layers.quantization.utils.fp8_utils import ( + apply_fp8_linear_generic, current_platform_fp8_dtype, is_fp8) +from vllm.model_executor.layers.quantization.utils.quant_utils import ( + scaled_dequantize, scaled_quantize) from vllm.model_executor.layers.rotary_embedding import RotaryEmbedding from vllm.vllm_flash_attn import flash_attn_varlen_func @@ -25,11 +37,11 @@ class MLACommonMetadata(AttentionMetadata): class MLACommonImpl(MLAAttentionImpl[T], Generic[T]): """ - Common class for implementing repeated parts - + Common class for implementing repeated parts + Main reference: DeepseekV2 paper, and FlashInfer Implementation (https://arxiv.org/abs/2405.04434 and https://github.com/flashinfer-ai/flashinfer/pull/551). - + Deepseek's MLA attention works the following way: * Use a single latent vector to represent the entire KV cache. * The attention "simulates" a multi-head attention, while the compute is @@ -46,7 +58,7 @@ class MLACommonImpl(MLAAttentionImpl[T], Generic[T]): * V: V head dim. * kv_c: latent/compressed KV * q_c: latent/compressed Q - + # # Outside the MLA attention backend # @@ -55,21 +67,21 @@ class MLACommonImpl(MLAAttentionImpl[T], Generic[T]): kv_c_k_pe (B, Lkv+R). 2. The kv_c_k_pe is split into kv_c (B, Lkv) and k_pe (B, R). cq and kv_c are normalized. - + # # Inside the MLA attention backend # * if prefill: - - 3. The q_c is then projected up into the multi-head version. - * q_c goes from (B, Lq) to (B, N, (P+R)), which is split into q_nope - (B, N, P) and q_pe (B, N, R). + + 3. The q_c is then projected up into the multi-head version. + * q_c goes from (B, Lq) to (B, N, (P+R)), which is split into q_nope + (B, N, P) and q_pe (B, N, R). 4. q_pe, k_pe are then passed through rotary embeddings. 5. kv_c and k_pe are concatenated and inserted into the cache - 6. The kv_c is then projected up into the multi-head version. - * kv_c goes from (B, Lkv) to (B, N, (P+V)) which has the nope - dimensions for K and V, which is split into k_nope (B, N, P) + 6. The kv_c is then projected up into the multi-head version. + * kv_c goes from (B, Lkv) to (B, N, (P+V)) which has the nope + dimensions for K and V, which is split into k_nope (B, N, P) and v (B, N, V). 7. q (B, N, (P+R)) and k (B, N, (P+R)) matrices are assembled from q_nope, q_pe, k_nope, k_pe. @@ -112,7 +124,7 @@ class MLACommonImpl(MLAAttentionImpl[T], Generic[T]): From @tsu-bin's calculation, we only want to use the absorption technique for decode. The prefill algorithm should still use the up-projected MHA for less flops and memory usage. - + """ def __init__( @@ -162,8 +174,19 @@ def __init__( def _v_up_proj_and_o_proj(self, x): if envs.VLLM_MLA_PERFORM_MATRIX_ABSORPTION: - return self.o_proj_absorbed( - x.reshape(-1, self.num_heads * self.kv_lora_rank))[0] + if is_fp8(self.W_UV_O): + output_parallel = apply_fp8_linear_generic( + x.flatten(start_dim=1), self.W_UV_O, self.W_UV_O_scales, + self.reqaunt_input_group_shape, + self.reqaunt_weight_group_shape) + else: + output_parallel = torch.matmul(x.flatten(start_dim=1), + self.W_UV_O) + if self.tp_size > 1: + output = tensor_model_parallel_all_reduce(output_parallel) + else: + output = output_parallel + return output else: x = torch.einsum("bnl,lnv->bnv", x, self.W_UV) return self.o_proj(x.reshape(-1, @@ -171,6 +194,12 @@ def _v_up_proj_and_o_proj(self, x): def _q_proj_and_k_up_proj(self, x): if envs.VLLM_MLA_PERFORM_MATRIX_ABSORPTION: + if is_fp8(self.W_Q_UK): + return apply_fp8_linear_generic( + x, self.W_Q_UK, self.W_Q_UK_scales, + self.reqaunt_input_group_shape, + self.reqaunt_weight_group_shape).view( + -1, self.num_heads, self.kv_lora_rank) return torch.matmul(x, self.W_Q_UK)\ .view(-1, self.num_heads, self.kv_lora_rank) else: @@ -179,8 +208,91 @@ def _q_proj_and_k_up_proj(self, x): return torch.einsum("bnp,lnp->bnl", x, self.W_UK)\ .view(-1, self.num_heads, self.kv_lora_rank) - def process_weights_after_loading(self): - kv_b_proj_weight = self.kv_b_proj.weight.T + def process_weights_after_loading(self, act_dtype: torch.dtype): + + def is_layer_fp8(layer: LinearBase) -> bool: + return isinstance(layer.quant_method, Fp8LinearMethod) or\ + (isinstance(layer.quant_method, CompressedTensorsLinearMethod)\ + and isinstance(layer.scheme, CompressedTensorsW8A8Fp8)) + + def quantization_scheme_supported(layer: LinearBase) -> bool: + return isinstance(layer.quant_method, UnquantizedLinearMethod) or \ + is_layer_fp8(layer) + + # TODO(lucas) This is very gross, we need a more wide scale refactor of + # all the FP8 code with a more standard way of + # defining schemes/group-shapes, we should also potentially force + # quant_methods to support a decompress function + # + # returns input_group_shape, weight_group_shape + def get_scale_group_shapes_for_fp8(layer: LinearBase) -> \ + Tuple[Tuple[int, int], Tuple[int, int]]: + if isinstance(layer.quant_method, Fp8LinearMethod): + if layer.quant_method.block_quant is not None: + weight_block_size = \ + layer.quant_method.quant_config.weight_block_size + # per-token-group (1, X), block-quantized (X, Y) + return (1, weight_block_size[-1]), weight_block_size + else: + return (-1, -1), (-1, -1) # per-tensor, per-tensor + elif isinstance(layer.quant_method, CompressedTensorsLinearMethod)\ + and isinstance(layer.scheme, CompressedTensorsW8A8Fp8): + # this is hacky but we always assume the for + # CompressedTensorsW8A8Fp8 the input is dynamic per-token + # we ignore if it is static-per-tensor since we are going to + # requantize after later anyways + strategy = layer.scheme.strategy + if strategy == QuantizationStrategy.TENSOR: + return (1, -1), (-1, -1) # per-token, per-tensor + elif strategy == QuantizationStrategy.CHANNEL: + return (1, -1), (-1, 1) # per-token, per-channel + else: + raise NotImplementedError( + f"QuantizationStrategy.{strategy} is not supported for " + "fp8 MLA, please run with VLLM_MLA_DISABLE=1") + else: + raise NotImplementedError( + "Can't determine scale group shapes for " + f"{layer.quant_method}, please run with VLLM_MLA_DISABLE=1" + ) + + def get_scales(layer: LinearBase) -> torch.Tensor: + if hasattr(layer, "weight_scale_inv"): + return layer.weight_scale_inv + return layer.weight_scale + + def get_and_maybe_dequant_weights(layer: LinearBase): + if is_layer_fp8(layer): + if isinstance(layer.quant_method, \ + CompressedTensorsLinearMethod) and \ + isinstance(layer.scheme, CompressedTensorsW8A8Fp8): + # NOTE(lucas): note sure why but `CompressedTensorsW8A8Fp8` + # seems to store weights as (input, output) instead of + # (output, input) so we need to transpose + weight = layer.weight.T # standardize to (output, input) + else: + weight = layer.weight + _, weight_scale_group_shape = \ + get_scale_group_shapes_for_fp8(layer) + scales = get_scales(layer) + + return scaled_dequantize(weight, scales, + weight_scale_group_shape) + else: + return layer.weight + + if not (quantization_scheme_supported(self.kv_b_proj) and\ + quantization_scheme_supported(self.q_proj) and\ + quantization_scheme_supported(self.o_proj)): + raise NotImplementedError( + "Only FP8 and UnquantizedLinearMethod are supported for MLA" + ", please run with VLLM_MLA_DISABLE=1") + + weight_dtype = self.kv_b_proj.weight.dtype + assert self.o_proj.weight.dtype == weight_dtype + assert self.q_proj.weight.dtype == weight_dtype + + kv_b_proj_weight = get_and_maybe_dequant_weights(self.kv_b_proj).T assert kv_b_proj_weight.shape == ( self.kv_lora_rank, self.num_heads * (self.qk_nope_head_dim + self.v_head_dim)), ( @@ -198,18 +310,35 @@ def process_weights_after_loading(self): W_UK, W_UV = kv_b_proj_weight.split( [self.qk_nope_head_dim, self.v_head_dim], dim=-1) - q_proj = self.q_proj.weight.T\ + q_proj_weight = get_and_maybe_dequant_weights(self.q_proj).T\ .view(-1, self.num_heads, self.qk_head_dim) # can be W_Q or W_UQ depending q_lora_rank, the former if # q_lora_rank is None, the latter otherwise. From the Attention backend # perspective though we call these both W_Q and rely on the layer # to pass in the correct matrix - W_Q = q_proj[..., :self.qk_nope_head_dim] - self.W_QR = q_proj[..., self.qk_nope_head_dim:]\ + W_Q = q_proj_weight[..., :self.qk_nope_head_dim] + self.W_QR = q_proj_weight[..., self.qk_nope_head_dim:]\ .flatten(start_dim=1).contiguous() + # W_QR is small so for simplicity we dont bother requantizing it + self.W_QR = self.W_QR.to(act_dtype) + if envs.VLLM_MLA_PERFORM_MATRIX_ABSORPTION: + requantization_enabled = not envs.VLLM_MLA_DISABLE_REQUANTIZATION + if is_fp8(weight_dtype) and requantization_enabled: + # This assumes it wise to requantize using the same group shapes + # (i.e. strategy, per-tensor, per-channel, block etc.) that the + # weights were originally quantized + requant_input_group_shape, requant_weight_group_shape = \ + get_scale_group_shapes_for_fp8(self.q_proj) + assert (requant_input_group_shape, requant_weight_group_shape)\ + == get_scale_group_shapes_for_fp8(self.kv_b_proj) + assert (requant_input_group_shape, requant_weight_group_shape)\ + == get_scale_group_shapes_for_fp8(self.o_proj) + self.reqaunt_input_group_shape = requant_input_group_shape + self.reqaunt_weight_group_shape = requant_weight_group_shape + # # Perform matrix-absorption following # https://github.com/flashinfer-ai/flashinfer/pull/551 @@ -223,25 +352,44 @@ def process_weights_after_loading(self): # latter otherwise # basically if q_lora_rank is none we are absorbing into q_proj # instead of UQ - self.W_Q_UK = torch.einsum("qnd,lnd -> qnl", W_Q, W_UK)\ + W_Q_UK = torch.einsum("qnd,lnd -> qnl", W_Q, W_UK)\ .flatten(start_dim=1).contiguous() - W_O = self.o_proj.weight\ + if is_fp8(weight_dtype) and requantization_enabled: + W_Q_UK, W_Q_UK_scales = scaled_quantize( + W_Q_UK, + self.reqaunt_weight_group_shape, + quant_dtype=current_platform_fp8_dtype) + # For FP8 save the transpose so we can use + # `apply_w8a8_block_fp8_linear` directly + self.W_Q_UK = W_Q_UK.T.contiguous() + self.W_Q_UK_scales = W_Q_UK_scales.T.contiguous() + else: + self.W_Q_UK = W_Q_UK.to(act_dtype) + + W_O = get_and_maybe_dequant_weights(self.o_proj)\ .view(-1, self.num_heads, self.v_head_dim) - self.W_UV_O = torch.einsum("lnd,hnd -> nlh", W_UV, W_O)\ + W_UV_O = torch.einsum("lnd,hnd -> nlh", W_UV, W_O)\ .flatten(start_dim=0, end_dim=1).contiguous() - tp_size = get_tensor_model_parallel_world_size() - self.o_proj_absorbed = RowParallelLinear( - self.W_UV_O.shape[0] * tp_size, - self.W_UV_O.shape[1], - bias=False, - # TODO(lucas) figure out how to properly forward quant_method - #quant_config=self.o_proj.quant_method, - ) - - self.o_proj_absorbed.weight = torch.nn.Parameter(self.W_UV_O.T) + if is_fp8(weight_dtype) and requantization_enabled: + W_UV_O, W_UV_O_scales = scaled_quantize( + W_UV_O, + self.reqaunt_weight_group_shape, + quant_dtype=current_platform_fp8_dtype) + # For FP8 save the transpose so we can use + # `apply_w8a8_block_fp8_linear` directly + self.W_UV_O = W_UV_O.T.contiguous() + self.W_UV_O_scales = W_UV_O_scales.T.contiguous() + else: + self.W_UV_O = W_UV_O.to(act_dtype) + + self.tp_size = get_tensor_model_parallel_world_size() else: + if is_fp8(weight_dtype): + raise NotImplementedError( + "Currently fp8 requires matrix absorption") + self.W_UV = W_UV self.W_UK = W_UK self.W_Q = W_Q.flatten(start_dim=1) diff --git a/vllm/attention/backends/triton_mla.py b/vllm/attention/backends/triton_mla.py index da09bb70b4f1a..95dc119a47bb5 100644 --- a/vllm/attention/backends/triton_mla.py +++ b/vllm/attention/backends/triton_mla.py @@ -57,14 +57,12 @@ def get_state_cls() -> Type["TritonMLAState"]: @staticmethod def get_kv_cache_shape( - num_blocks: int, - block_size: int, - num_kv_heads: int, # assumed to be 1 for MLA - kv_lora_rank: int, # passed via head_size + num_blocks: int, + block_size: int, + num_kv_heads: int, # assumed to be 1 for MLA + head_size: int, ) -> Tuple[int, ...]: - # TODO(lucas): remove hardcoding k_pe size as 1/8th of kv_lora_rank - k_pe_size = kv_lora_rank // 8 - return (num_blocks, block_size, kv_lora_rank + k_pe_size) + return (num_blocks, block_size, head_size) @staticmethod def swap_blocks( @@ -83,7 +81,7 @@ def copy_blocks( @staticmethod def get_supported_head_sizes() -> List[int]: - return [512] + return [576] class TritonMLAState(AttentionState): @@ -624,8 +622,6 @@ def build(self, seq_lens: List[int], query_lens: List[int], self.multimodal_placeholder_maps.items() } - num_kv_splits = 8 - return TritonMLAMetadata( num_prefills=self.num_prefills, slot_mapping=slot_mapping_tensor, @@ -645,7 +641,7 @@ def build(self, seq_lens: List[int], query_lens: List[int], context_lens_tensor=context_lens_tensor, block_tables=block_tables, use_cuda_graph=use_captured_graph, - num_kv_splits=num_kv_splits, + num_kv_splits=4, # TODO(lucas) add heuristic head_dim=self.runner.model_config.get_head_size(), ) diff --git a/vllm/attention/layer.py b/vllm/attention/layer.py index a10daa351d85b..b7e8d536de9f0 100644 --- a/vllm/attention/layer.py +++ b/vllm/attention/layer.py @@ -206,9 +206,9 @@ def extra_repr(self) -> str: s += f", backend={self.impl.__class__.__name__}" return s - def process_weights_after_loading(self): + def process_weights_after_loading(self, act_dtype: torch.dtype): if hasattr(self.impl, "process_weights_after_loading"): - self.impl.process_weights_after_loading() + self.impl.process_weights_after_loading(act_dtype) class MultiHeadAttention(nn.Module): diff --git a/vllm/config.py b/vllm/config.py index 04c36fadf9323..84dbe2d5a6edd 100644 --- a/vllm/config.py +++ b/vllm/config.py @@ -744,16 +744,16 @@ def is_deepseek_mla(self) -> bool: # TODO add deepseek_v3 return hasattr(self.hf_text_config, "model_type") and (self.hf_text_config.model_type - in ('deepseek_v2')) + in ('deepseek_v2', 'deepseek_v3')) def get_head_size(self) -> int: # TODO remove hard code if self.is_deepseek_mla: + qk_rope_head_dim = getattr(self.hf_text_config, "qk_rope_head_dim", + 0) if self.use_mla: - return self.hf_text_config.kv_lora_rank + return self.hf_text_config.kv_lora_rank + qk_rope_head_dim else: - qk_rope_head_dim = getattr(self.hf_text_config, - "qk_rope_head_dim", 0) qk_nope_head_dim = getattr(self.hf_text_config, "qk_nope_head_dim", 0) if qk_rope_head_dim and qk_nope_head_dim: @@ -972,6 +972,32 @@ def is_cross_encoder(self) -> bool: @property def use_mla(self) -> bool: + if self.quantization is not None and self.quantization not in [\ + "fp8", "compressed-tensors"]: + logger.warning( + "MLA is not supported with %s quantization. " + "Disabling MLA.", self.quantization) + return False + + # If using a "compressed-tensors" checkpoint, check that all groups + # have fp8 for both weights and activations. + if self.quantization == "compressed-tensors": + quant_config = self._parse_quant_hf_config() + if self.quantization == "compressed-tensors": + quant_config = self._parse_quant_hf_config() + for group_name, cfg in quant_config.get("config_groups", + {}).items(): + act_type = cfg.get("input_activations", {}).get("type", "") + weight_type = cfg.get("weights", {}).get("type", "") + if act_type != "fp8" or weight_type != "fp8": + logger.warning( + "compressed-tensors MLA support requires fp8 " + "activations and weights in group '%s', but got " + "activations type '%s' and weights type '%s'.\n " + "Full config: %s", group_name, act_type, weight_type, + quant_config) + return False + use_mla = (self.is_deepseek_mla and not envs.VLLM_MLA_DISABLE) return use_mla diff --git a/vllm/envs.py b/vllm/envs.py index 1d773f15dd259..3fd5d8c107236 100644 --- a/vllm/envs.py +++ b/vllm/envs.py @@ -91,6 +91,7 @@ VLLM_V1_OUTPUT_PROC_CHUNK_SIZE: int = 128 VLLM_MLA_DISABLE: bool = False VLLM_MLA_PERFORM_MATRIX_ABSORPTION: bool = True + VLLM_MLA_DISABLE_REQUANTIZATION: bool = False def get_default_cache_root(): @@ -592,7 +593,16 @@ def maybe_convert_int(value: Optional[str]) -> Optional[int]: # storing more weights, W_Q_UK and W_UV_O, so can increase memory usage, # the is enabled by default "VLLM_MLA_PERFORM_MATRIX_ABSORPTION": - lambda: bool(int(os.getenv("VLLM_MLA_PERFORM_MATRIX_ABSORPTION", "1"))) + lambda: bool(int(os.getenv("VLLM_MLA_PERFORM_MATRIX_ABSORPTION", "1"))), + + # When running MLA with matrix-absorption enabled and fp8 quantized weights + # we perform the matrix-absorption in float32 precision, after the matrices + # are absorbed we requantize the weights back to fp8, this flag can be used + # to disable the requantization step, and instead convert the absorbed + # matrices to match the activation type. This can lead to higher memory and + # compute usage but better preserves the accuracy of the original model. + "VLLM_MLA_DISABLE_REQUANTIZATION": + lambda: bool(int(os.getenv("VLLM_MLA_DISABLE_REQUANTIZATION", "0"))) } # end-env-vars-definition diff --git a/vllm/model_executor/guided_decoding/xgrammar_decoding.py b/vllm/model_executor/guided_decoding/xgrammar_decoding.py index 2d8594cb8aafa..ee30ce96f0a1e 100644 --- a/vllm/model_executor/guided_decoding/xgrammar_decoding.py +++ b/vllm/model_executor/guided_decoding/xgrammar_decoding.py @@ -307,8 +307,8 @@ def __call__(self, input_ids: list[int], # Note: In this method, if the tensors have different dimensions # on CPU device fails, but on GPU it runs without error. Hence the # unsqueeze above for scores, to match the token bitmask shape - xgr.apply_token_bitmask_inplace(scores, - self.token_bitmask.to(scores.device)) + xgr.apply_token_bitmask_inplace( + scores, self.token_bitmask.to(scores.device, non_blocking=True)) if device_type != "cuda": scores = scores.to(dtype).to(device_type).squeeze() diff --git a/vllm/model_executor/layers/fused_moe/fused_moe.py b/vllm/model_executor/layers/fused_moe/fused_moe.py index 83951d614721c..a0395beddd1a4 100644 --- a/vllm/model_executor/layers/fused_moe/fused_moe.py +++ b/vllm/model_executor/layers/fused_moe/fused_moe.py @@ -661,36 +661,17 @@ def get_default_config( is_marlin: bool, block_shape: Optional[List[int]] = None, ) -> Dict[str, int]: - if dtype == "fp8_w8a8": - if block_shape is None: - config = { - "BLOCK_SIZE_M": 128, - "BLOCK_SIZE_N": 256, - "BLOCK_SIZE_K": 128, - "GROUP_SIZE_M": 32, - "num_warps": 8, - "num_stages": 4, - } - if M <= E: - config = { - "BLOCK_SIZE_M": 64, - "BLOCK_SIZE_N": 128, - "BLOCK_SIZE_K": 128, - "GROUP_SIZE_M": 1, - "num_warps": 4, - "num_stages": 4, - } - else: - # Block-wise quant: BLOCK_SIZE_N must be divisible by block_shape[0] - # BLOCK_SIZE_K must be divisible by block_shape[1] - config = { - "BLOCK_SIZE_M": 64, - "BLOCK_SIZE_N": block_shape[0], - "BLOCK_SIZE_K": block_shape[1], - "GROUP_SIZE_M": 32, - "num_warps": 4, - "num_stages": 3, - } + if dtype == "fp8_w8a8" and block_shape is not None: + # Block-wise quant: BLOCK_SIZE_N must be divisible by block_shape[0] + # BLOCK_SIZE_K must be divisible by block_shape[1] + config = { + "BLOCK_SIZE_M": 64, + "BLOCK_SIZE_N": block_shape[0], + "BLOCK_SIZE_K": block_shape[1], + "GROUP_SIZE_M": 32, + "num_warps": 4, + "num_stages": 3, + } else: config = { "BLOCK_SIZE_M": 64, diff --git a/vllm/model_executor/layers/quantization/fp8.py b/vllm/model_executor/layers/quantization/fp8.py index 5ba574d34010a..94ed6cc48a742 100644 --- a/vllm/model_executor/layers/quantization/fp8.py +++ b/vllm/model_executor/layers/quantization/fp8.py @@ -22,7 +22,8 @@ is_layer_skipped) from vllm.model_executor.layers.quantization.utils.w8a8_utils import ( all_close_1d, apply_fp8_linear, convert_to_channelwise, - cutlass_fp8_supported, normalize_e4m3fn_to_e4m3fnuz, per_tensor_dequantize, + cutlass_block_fp8_supported, cutlass_fp8_supported, + normalize_e4m3fn_to_e4m3fnuz, per_tensor_dequantize, requantize_with_max_scale) from vllm.model_executor.parameter import (BlockQuantScaleParameter, ModelWeightParameter, @@ -155,6 +156,7 @@ class Fp8LinearMethod(LinearMethodBase): def __init__(self, quant_config: Fp8Config): self.quant_config = quant_config self.cutlass_fp8_supported = cutlass_fp8_supported() + self.cutlass_block_fp8_supported = cutlass_block_fp8_supported() # For GPUs that lack FP8 hardware support, we can leverage the Marlin # kernel for fast weight-only FP8 quantization @@ -270,20 +272,24 @@ def create_weights( layer.register_parameter("input_scale", None) def process_weights_after_loading(self, layer: Module) -> None: - # Block quant doesn't need to process weights after loading + # TODO(rob): refactor block quant into separate class. if self.block_quant: + assert self.quant_config.activation_scheme == "dynamic" if current_platform.is_rocm() and not is_navi(): - weight, weight_scale, _ = \ + weight, weight_scale_inv, _ = ( normalize_e4m3fn_to_e4m3fnuz( weight=layer.weight, - weight_scale=layer.weight_scale_inv, - input_scale=layer.input_scale) - layer.weight = Parameter(weight, requires_grad=False) - layer.weight_scale_inv = Parameter(weight_scale, - requires_grad=False) + weight_scale=layer.weight_scale_inv)) + else: + weight = layer.weight.data + weight_scale_inv = layer.weight_scale_inv.data + + # Torch.compile cannot use Parameter subclasses. + layer.weight = Parameter(weight, requires_grad=False) + layer.weight_scale_inv = Parameter(weight_scale_inv, + requires_grad=False) return - layer.weight = torch.nn.Parameter(layer.weight.data, - requires_grad=False) + # If checkpoint not serialized fp8, quantize the weights. if not self.quant_config.is_checkpoint_fp8_serialized: qweight, weight_scale = ops.scaled_fp8_quant(layer.weight, @@ -392,6 +398,7 @@ def apply(self, weight_scale=layer.weight_scale_inv, input_scale=layer.input_scale, bias=bias, + cutlass_block_fp8_supported=self.cutlass_block_fp8_supported, ) return apply_fp8_linear( @@ -545,8 +552,9 @@ def create_weights(self, layer: Module, num_experts: int, hidden_size: int, layer.w2_input_scale = None def process_weights_after_loading(self, layer: Module) -> None: - # Block quant doesn't need to process weights after loading + # TODO (rob): refactor block quant into separate class. if self.block_quant: + assert self.quant_config.activation_scheme == "dynamic" if current_platform.is_rocm() and not is_navi(): w13_weight, w13_weight_scale_inv, w13_input_scale = \ normalize_e4m3fn_to_e4m3fnuz( @@ -556,22 +564,21 @@ def process_weights_after_loading(self, layer: Module) -> None: normalize_e4m3fn_to_e4m3fnuz( layer.w2_weight, layer.w2_weight_scale_inv, layer.w2_input_scale) - # Reset the parameter - layer.w13_weight = torch.nn.Parameter(w13_weight, - requires_grad=False) - layer.w13_weight_scale_inv = torch.nn.Parameter( - w13_weight_scale_inv, requires_grad=False) - if w13_input_scale is not None: - layer.w13_input_scale = torch.nn.Parameter( - w13_input_scale, requires_grad=False) - layer.w2_weight = torch.nn.Parameter(w2_weight, - requires_grad=False) - layer.w2_weight_scale_inv = torch.nn.Parameter( - w2_weight_scale_inv, requires_grad=False) - if w2_input_scale is not None: - layer.w2_input_scale = torch.nn.Parameter( - w2_input_scale, requires_grad=False) + else: + w13_weight = layer.w13_weight.data + w13_weight_scale_inv = layer.w13_weight_scale_inv.data + w2_weight = layer.w2_weight + w2_weight_scale_inv = layer.w2_weight_scale_inv + + # torch.compile() cannot use Parameter subclasses. + layer.w13_weight = Parameter(w13_weight, requires_grad=False) + layer.w13_weight_scale_inv = Parameter(w13_weight_scale_inv, + requires_grad=False) + layer.w2_weight = Parameter(w2_weight, requires_grad=False) + layer.w2_weight_scale_inv = Parameter(w2_weight_scale_inv, + requires_grad=False) return + # If checkpoint is fp16, quantize in place. if not self.quant_config.is_checkpoint_fp8_serialized: # If rocm (except Navi4x), use float8_e4m3fnuz as dtype diff --git a/vllm/model_executor/layers/quantization/utils/fp8_utils.py b/vllm/model_executor/layers/quantization/utils/fp8_utils.py index f256ecb8ee1b7..ba713a1d1974c 100644 --- a/vllm/model_executor/layers/quantization/utils/fp8_utils.py +++ b/vllm/model_executor/layers/quantization/utils/fp8_utils.py @@ -2,18 +2,33 @@ import functools import json import os -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple, Union import torch import triton import triton.language as tl +from vllm import _custom_ops as ops from vllm.logger import init_logger +from vllm.model_executor.layers.quantization.utils.quant_utils import ( + _normalize_quant_group_shape, scaled_dequantize) +from vllm.model_executor.layers.quantization.utils.w8a8_utils import ( + apply_fp8_linear) from vllm.platforms import current_platform from vllm.utils import is_navi logger = init_logger(__name__) +current_platform_fp8_dtype = (torch.float8_e4m3fnuz + if current_platform.is_rocm() else + torch.float8_e4m3fn) + + +def is_fp8(x: Union[torch.dtype, torch.Tensor]) -> bool: + if isinstance(x, torch.Tensor): + x = x.dtype + return x == torch.float8_e4m3fn or x == torch.float8_e4m3fnuz + def apply_w8a8_block_fp8_linear( input: torch.Tensor, @@ -22,25 +37,75 @@ def apply_w8a8_block_fp8_linear( weight_scale: torch.Tensor, input_scale: Optional[torch.Tensor] = None, bias: Optional[torch.Tensor] = None, + cutlass_block_fp8_supported: bool = True, ) -> torch.Tensor: assert input_scale is None # View input as 2D matrix for fp8 methods input_2d = input.view(-1, input.shape[-1]) output_shape = [*input.shape[:-1], weight.shape[0]] - q_input, x_scale = per_token_group_quant_fp8(input_2d, block_size[1]) - output = w8a8_block_fp8_matmul(q_input, - weight, - x_scale, - weight_scale, - block_size, - output_dtype=input.dtype) - + shape_supported_by_cutlass = (weight.shape[0] % 128 == 0 + and weight.shape[1] % 128 == 0) + if cutlass_block_fp8_supported and shape_supported_by_cutlass: + q_input, x_scale = per_token_group_quant_fp8(input_2d, + block_size[1], + column_major_scales=True) + output = ops.cutlass_scaled_mm(q_input, + weight.T, + out_dtype=input.dtype, + scale_a=x_scale, + scale_b=weight_scale.T) + else: + q_input, x_scale = per_token_group_quant_fp8(input_2d, + block_size[1], + column_major_scales=False) + output = w8a8_block_fp8_matmul(q_input, + weight, + x_scale, + weight_scale, + block_size, + output_dtype=input.dtype) if bias is not None: output = output + bias return output.to(dtype=input.dtype).view(*output_shape) +# Unify the interface between `apply_w8a8_block_fp8_linear` and +# `apply_fp8_linear` +# NOTE(lucas): this is quite messy, we should think through this more formally +def apply_fp8_linear_generic( + input: torch.Tensor, + weight: torch.Tensor, + weight_scale: torch.Tensor, + input_group_shape: Tuple[int, int], + weight_group_shape: Tuple[int, int], + input_scale: Optional[torch.Tensor] = None, # static scale if one +) -> torch.Tensor: + # View input as 2D matrix for fp8 methods + input = input.view(-1, input.shape[-1]) + + weight_group_shape = _normalize_quant_group_shape(\ + weight, weight_group_shape) + input_group_shape = _normalize_quant_group_shape(input, input_group_shape) + + def is_dim_blocked(dim, shape, group_shape): + return group_shape < shape[dim] and group_shape > 1 + + if is_dim_blocked(0, weight.shape, weight_group_shape[0])\ + and is_dim_blocked(1, weight.shape, weight_group_shape[1]) and\ + input_group_shape == (1, weight_group_shape[1]): + return apply_w8a8_block_fp8_linear(input, weight, + list(weight_group_shape), + weight_scale) + else: + # Despite having linear in the it doesn't conform to + # `torch.nn.functional.linear` which is defined as `input @ weight.T` + # so we explicitly transpose the weight matrix here + return apply_fp8_linear(input, weight.T, weight_scale.T, + use_per_token_if_dynamic=\ + (input_group_shape == (1, input.shape[1]))) + + def input_to_float8( x: torch.Tensor, dtype: Optional[torch.dtype] = None @@ -61,7 +126,6 @@ def input_to_float8( def block_quant_to_tensor_quant( x_q_block: torch.Tensor, x_s: torch.Tensor, - block_size: List[int], ) -> Tuple[torch.Tensor, torch.Tensor]: """This function converts block-wise quantization to tensor-wise quantization. The inputs are block-wise quantization tensor `x_q_block`, @@ -69,26 +133,7 @@ def block_quant_to_tensor_quant( The outputs are tensor-wise quantization tensor and tensor-wise quantization scale. Note only float8 is supported for now. """ - block_n, block_k = block_size[0], block_size[1] - n, k = x_q_block.shape - n_tiles = (n + block_n - 1) // block_n - k_tiles = (k + block_k - 1) // block_k - assert n_tiles == x_s.shape[0] - assert k_tiles == x_s.shape[1] - - x_dq_block = x_q_block.to(torch.float32) - - x_dq_block_tiles = [[ - x_dq_block[ - j * block_n:min((j + 1) * block_n, n), - i * block_k:min((i + 1) * block_k, k), - ] for i in range(k_tiles) - ] for j in range(n_tiles)] - - for i in range(k_tiles): - for j in range(n_tiles): - x_dq_block_tiles[j][i][:, :] = x_dq_block_tiles[j][i] * x_s[j][i] - + x_dq_block = scaled_dequantize(x_q_block, x_s) x_q_tensor, scale = input_to_float8(x_dq_block, dtype=x_q_block.dtype) return x_q_tensor, scale @@ -99,10 +144,7 @@ def _per_token_group_quant_fp8( y_ptr, y_q_ptr, y_s_ptr, - # Stride of input - y_stride, - # Columns of input - N, + group_size, # Avoid to divide zero eps, # Information for float8 @@ -117,12 +159,60 @@ def _per_token_group_quant_fp8( """ # Map the program id to the row of X and Y it should compute. g_id = tl.program_id(0) - y_ptr += g_id * y_stride - y_q_ptr += g_id * y_stride + y_ptr += g_id * group_size + y_q_ptr += g_id * group_size y_s_ptr += g_id cols = tl.arange(0, BLOCK) # N <= BLOCK - mask = cols < N + mask = cols < group_size + + y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32) + # Quant + _absmax = tl.maximum(tl.max(tl.abs(y)), eps) + y_s = _absmax / fp8_max + y_q = tl.clamp(y / y_s, fp8_min, fp8_max).to(y_q_ptr.dtype.element_ty) + + tl.store(y_q_ptr + cols, y_q, mask=mask) + tl.store(y_s_ptr, y_s) + + +@triton.jit +def _per_token_group_quant_fp8_colmajor( + # Pointers to inputs and output + y_ptr, + y_q_ptr, + y_s_ptr, + group_size, + # Num columns of y + y_num_columns, + # Stride from one column to the next of y_s + y_s_col_stride, + # Avoid to divide zero + eps, + # Information for float8 + fp8_min, + fp8_max, + # Meta-parameters + BLOCK: tl.constexpr, +): + """A Triton-accelerated function to perform per-token-group + quantization on a tensor. + This function converts the tensor values into float8 values. + """ + # Map the program id to the row of X and Y it should compute. + g_id = tl.program_id(0) + y_ptr += g_id * group_size + y_q_ptr += g_id * group_size + + # Convert g_id the flattened block coordinate to 2D so we can index + # into the output y_scales matrix + blocks_per_row = y_num_columns // group_size + scale_col = g_id % blocks_per_row + scale_row = g_id // blocks_per_row + y_s_ptr += scale_col * y_s_col_stride + scale_row + + cols = tl.arange(0, BLOCK) # group_size <= BLOCK + mask = cols < group_size y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32) # Quant @@ -139,12 +229,13 @@ def per_token_group_quant_fp8( group_size: int, eps: float = 1e-10, dtype: Optional[torch.dtype] = None, + column_major_scales: bool = False, ) -> Tuple[torch.Tensor, torch.Tensor]: """Function to perform per-token-group quantization on an input tensor `x`. It converts the tensor values into signed float8 values and returns the quantized tensor along with the scaling factor used for quantization. Args: - x: The input tenosr with ndim >= 2. + x: The input tensor with ndim >= 2. group_size: The group size used for quantization. eps: The minimum to avoid dividing zero. dtype: The dype of output tensor. Note that only `torch.float8_e4m3fn` @@ -168,29 +259,46 @@ def per_token_group_quant_fp8( x_q = torch.empty_like(x, device=x.device, dtype=dtype) M = x.numel() // group_size N = group_size - x_s = torch.empty( - x.shape[:-1] + (x.shape[-1] // group_size, ), - device=x.device, - dtype=torch.float32, - ) + if column_major_scales: + shape = (x.shape[-1] // group_size, ) + x.shape[:-1] + x_s = torch.empty(shape, device=x.device, + dtype=torch.float32).permute(-1, -2) + else: + shape = x.shape[:-1] + (x.shape[-1] // group_size, ) + x_s = torch.empty(shape, device=x.device, dtype=torch.float32) BLOCK = triton.next_power_of_2(N) # heuristics for number of warps num_warps = min(max(BLOCK // 256, 1), 8) num_stages = 1 - _per_token_group_quant_fp8[(M, )]( - x, - x_q, - x_s, - group_size, - N, - eps, - fp8_min=fp8_min, - fp8_max=fp8_max, - BLOCK=BLOCK, - num_warps=num_warps, - num_stages=num_stages, - ) + if column_major_scales: + _per_token_group_quant_fp8_colmajor[(M, )]( + x, + x_q, + x_s, + group_size, + x.shape[1], + x_s.stride(1), + eps, + fp8_min=fp8_min, + fp8_max=fp8_max, + BLOCK=BLOCK, + num_warps=num_warps, + num_stages=num_stages, + ) + else: + _per_token_group_quant_fp8[(M, )]( + x, + x_q, + x_s, + group_size, + eps, + fp8_min=fp8_min, + fp8_max=fp8_max, + BLOCK=BLOCK, + num_warps=num_warps, + num_stages=num_stages, + ) return x_q, x_s diff --git a/vllm/model_executor/layers/quantization/utils/quant_utils.py b/vllm/model_executor/layers/quantization/utils/quant_utils.py index 83055d6000d83..95e785dcc4078 100644 --- a/vllm/model_executor/layers/quantization/utils/quant_utils.py +++ b/vllm/model_executor/layers/quantization/utils/quant_utils.py @@ -1,5 +1,5 @@ """This file is used for /tests and /benchmarks""" -from typing import List, Optional +from typing import List, Optional, Tuple import numpy import torch @@ -20,6 +20,120 @@ } +# Normalize the group_shape to the full extent for any dims that are -1 +def _normalize_quant_group_shape(x: torch.Tensor, group_shape: Tuple[int, + int]): + # -1 means full extent + return (group_shape[0] if group_shape[0] > 0 else x.shape[-2], + group_shape[1] if group_shape[1] > 0 else x.shape[-1]) + + +# Useful when treating N-dimensional group scaling as extended numpy-style +# broadcasting in numpy simply stretches dimensions with an extent of 1 to match +# the target shape by repeating the data along that dimension (broadcasting) +# , we extend these semantics to say if the extent of a dimension in the +# source shape is not 1 and does not match the target shape we repeat each +# element along that dimension src_shape[dim] // target_shape[dim] times +# example if we have: +# a = [[1, 2], and target_shape = (2, 4) +# [3, 4]] +# then we would expand a to: +# a = [[1, 1, 2, 2], +# [3, 3, 4, 4]] +# NOTE this function this function does not explicitly broadcast dimensions +# with an extent of 1, since this can be done implicitly by pytorch +def group_broadcast(t, shape): + for i, s in enumerate(shape): + if t.shape[i] != s and t.shape[i] != 1: + assert s % t.shape[i] == 0 + t = t.unsqueeze(i + 1)\ + .expand(*t.shape[:i+1], s // t.shape[i], *t.shape[i+1:])\ + .flatten(i, i + 1) + return t + + +# Quantize assuming once scale per group of elements with shape group_shape, +# example group shapes: +# * (-1, -1) for per-tensor quantization +# * (1, -1) for per-row quantization +# * (-1, 1) for per-column quantization +# * (128, 128) for 128x128 deepseek style block quantization +# * (1, 128) for deepseek style activation quantization +# (i.e. per-token-per-group) +def scaled_quantize( + x: torch.Tensor, + group_shape: Tuple[int, int], + quant_dtype: torch.dtype, +) -> Tuple[torch.Tensor, torch.Tensor]: + group_shape = _normalize_quant_group_shape(x, group_shape) + assert quant_dtype.is_floating_point, \ + "currently `scaled_quantize` only supports floating point dtypes " \ + "but could be extended to support other dtypes" + + finfo = torch.finfo(quant_dtype) + + # Reshape (M, N) into (BLK_M, BLOCK_SIZE_M, BLK_N, BLOCK_SIZE_N) + assert x.ndim == 2 + assert x.shape[0] % group_shape[0] == 0 and x.shape[1] % group_shape[1] == 0 + blk_m, blk_n = x.shape[0] // group_shape[0], x.shape[1] // group_shape[1] + x_blkd = x.reshape(blk_m, group_shape[0], blk_n, group_shape[1]) + + # Permute to (BLK_M, BLK_N, BLOCK_SIZE_M, BLOCK_SIZE_N) + x_blkd_permd = x_blkd.permute(0, 2, 1, 3) + # Flatten to (BLK_M, BLK_N, BLOCK_SIZE_M * BLOCK_SIZE_N) + x_blkd_permd = x_blkd_permd.flatten(start_dim=2) + + # Compute scales + min_val, max_val = x_blkd_permd.aminmax(dim=-1) + amax = torch.maximum(min_val.abs(), max_val.abs()).clamp(min=1e-12) + scale = finfo.max / amax + + # Apply scale and convert form: + # (BLK_M, BLK_N, BLOCK_SIZE_M * BLOCK_SIZE_N) to (M, N) + x_scl_sat = (x_blkd_permd * scale.unsqueeze(-1))\ + .clamp(min=finfo.min, max=finfo.max)\ + .reshape(blk_m, blk_n, group_shape[0], group_shape[1])\ + .permute(0, 2, 1, 3)\ + .reshape(x.shape) + + return x_scl_sat.to(quant_dtype).contiguous(), scale.float().reciprocal() + + +# inverses `scaled_quantize` +def scaled_dequantize( + x_q: torch.Tensor, + x_s: torch.Tensor, + group_shape: Optional[Tuple[int, int]] = None, + out_dtype: torch.dtype = torch.float32, +) -> Tuple[torch.Tensor, torch.Tensor]: + if group_shape is not None: + group_shape = _normalize_quant_group_shape(x_q, group_shape) + + if x_s.ndim == 0: # scalar + x_s = x_s.unsqueeze(-1).unsqueeze(-1) # convert to (1, 1) tensor + if x_s.ndim == 1: + if group_shape is None: + raise AssertionError( + "if x_s is 1D tensor, group_shape must be provided otherwise " + "its ambiguous which dimension to broadcast x_s to") + # unsqueeze the scales for the dimension where we want to broadcast + # across the full extent + if group_shape[0] == x_q.shape[-2]: + x_s = x_s.unsqueeze(-2) + elif group_shape[1] == x_q.shape[-1]: + x_s = x_s.unsqueeze(-1) + else: + raise AssertionError( + "if x_s is a vector we should be broadcasting it to the full " + "extent of one of the dimensions") + + if group_shape is not None: + assert x_s.shape[-1] == x_q.shape[-1] // group_shape[1] + assert x_s.shape[-2] == x_q.shape[-2] // group_shape[0] + x_s = group_broadcast(x_s.to(torch.float32), x_q.shape) + return (x_q.to(torch.float32) * x_s).to(out_dtype) + + def pack_quantized_values_into_int32(w_q: torch.Tensor, wtype: ScalarType, packed_dim: int = 0): diff --git a/vllm/model_executor/layers/quantization/utils/w8a8_utils.py b/vllm/model_executor/layers/quantization/utils/w8a8_utils.py index c93a3951731e8..1d802f0e47dcf 100644 --- a/vllm/model_executor/layers/quantization/utils/w8a8_utils.py +++ b/vllm/model_executor/layers/quantization/utils/w8a8_utils.py @@ -30,6 +30,16 @@ def cutlass_fp8_supported() -> bool: return ops.cutlass_scaled_mm_supports_fp8(capability) +def cutlass_block_fp8_supported() -> bool: + if not current_platform.is_cuda(): + return False + + capability_tuple = current_platform.get_device_capability() + capability = -1 if capability_tuple is None else capability_tuple.to_int() + + return ops.cutlass_scaled_mm_supports_block_fp8(capability) + + def per_tensor_dequantize( tensor: torch.Tensor, inv_scale: Union[float, torch.Tensor]) -> torch.Tensor: diff --git a/vllm/model_executor/model_loader/loader.py b/vllm/model_executor/model_loader/loader.py index 62babcddd61b1..4be511d12838d 100644 --- a/vllm/model_executor/model_loader/loader.py +++ b/vllm/model_executor/model_loader/loader.py @@ -398,11 +398,13 @@ def load_model(self, vllm_config: VllmConfig) -> nn.Module: # parameters onto device for processing and back off after. with device_loading_context(module, target_device): quant_method.process_weights_after_loading(module) - elif isinstance(module, Attention) and \ + if isinstance(module, Attention) and \ hasattr(module, "process_weights_after_loading"): # When attention modules need to process weights after # currently only used by MLA - module.process_weights_after_loading() + # TODO(lucas): see if there is a way to unify the signatures + # of process_weights_after_loading + module.process_weights_after_loading(model_config.dtype) return model.eval() @@ -439,6 +441,11 @@ def load_model(self, vllm_config: VllmConfig) -> nn.Module: with device_loading_context( module, torch.device(device_config.device)): quant_method.process_weights_after_loading(module) + if isinstance(module, Attention) and \ + hasattr(module, "process_weights_after_loading"): + # When attention modules need to process weights after + # currently only used by MLA + module.process_weights_after_loading(model_config.dtype) return model.eval() @@ -633,6 +640,12 @@ def load_model(self, vllm_config: VllmConfig) -> nn.Module: quant_method = getattr(module, "quant_method", None) if quant_method is not None: quant_method.process_weights_after_loading(module) + if isinstance(module, Attention) and \ + hasattr(module, "process_weights_after_loading"): + # When attention modules need to process weights after + # currently only used by MLA + module.process_weights_after_loading( + model_config.dtype) rank = get_tensor_model_parallel_rank() pattern = os.path.join( local_model_path, @@ -1272,7 +1285,7 @@ def load_model(self, vllm_config: VllmConfig) -> nn.Module: class RunaiModelStreamerLoader(BaseModelLoader): """ - Model loader that can load safetensors + Model loader that can load safetensors files from local FS or S3 bucket. """ @@ -1369,6 +1382,11 @@ def load_model(self, vllm_config: VllmConfig) -> nn.Module: if quant_method is not None: with device_loading_context(module, target_device): quant_method.process_weights_after_loading(module) + if isinstance(module, Attention) and \ + hasattr(module, "process_weights_after_loading"): + # When attention modules need to process weights after + # currently only used by MLA + module.process_weights_after_loading(model_config.dtype) return model.eval() diff --git a/vllm/model_executor/models/deepseek_v3.py b/vllm/model_executor/models/deepseek_v3.py index 0b44f0d062c40..f6ab53c85faa3 100644 --- a/vllm/model_executor/models/deepseek_v3.py +++ b/vllm/model_executor/models/deepseek_v3.py @@ -27,7 +27,7 @@ from transformers import PretrainedConfig from vllm.attention import Attention, AttentionMetadata -from vllm.config import CacheConfig, VllmConfig +from vllm.config import CacheConfig, ModelConfig, VllmConfig from vllm.distributed import (get_pp_group, get_tensor_model_parallel_world_size, tensor_model_parallel_all_reduce) @@ -333,12 +333,156 @@ def forward( return output +class DeepseekV3MLAAttention(nn.Module): + """ + Main reference: DeepseekV2 paper, and FlashInfer Implementation + (https://arxiv.org/abs/2405.04434 and https://github.com/flashinfer-ai/flashinfer/pull/551). + + For more info see MLACommonImpl in: vllm/attention/backends/mla/utils.py + """ + + def __init__( + self, + config: PretrainedConfig, + hidden_size: int, + num_heads: int, + qk_nope_head_dim: int, + qk_rope_head_dim: int, + v_head_dim: int, + q_lora_rank: Optional[int], + kv_lora_rank: int, + rope_theta: float = 10000, + rope_scaling: Optional[Dict[str, Any]] = None, + max_position_embeddings: int = 8192, + cache_config: Optional[CacheConfig] = None, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = hidden_size + self.qk_nope_head_dim = qk_nope_head_dim + self.qk_rope_head_dim = qk_rope_head_dim + self.qk_head_dim = qk_nope_head_dim + qk_rope_head_dim + self.v_head_dim = v_head_dim + + self.q_lora_rank = q_lora_rank + self.kv_lora_rank = kv_lora_rank + + self.num_heads = num_heads + tp_size = get_tensor_model_parallel_world_size() + assert num_heads % tp_size == 0 + self.num_local_heads = num_heads // tp_size + + self.scaling = self.qk_head_dim**-0.5 + self.rope_theta = rope_theta + self.max_position_embeddings = max_position_embeddings + + if self.q_lora_rank is not None: + self.q_a_proj = ReplicatedLinear(self.hidden_size, + self.q_lora_rank, + bias=False, + quant_config=quant_config, + prefix=f"{prefix}.q_a_proj") + self.q_a_layernorm = RMSNorm(self.q_lora_rank, + eps=config.rms_norm_eps) + self.q_b_proj = ColumnParallelLinear(q_lora_rank, + self.num_heads * + self.qk_head_dim, + bias=False, + quant_config=quant_config, + prefix=f"{prefix}.q_b_proj") + else: + self.q_proj = ColumnParallelLinear(self.hidden_size, + self.num_heads * + self.qk_head_dim, + bias=False, + quant_config=quant_config, + prefix=f"{prefix}.q_proj") + + self.kv_a_proj_with_mqa = ReplicatedLinear( + self.hidden_size, + self.kv_lora_rank + self.qk_rope_head_dim, + bias=False, + quant_config=quant_config, + prefix=f"{prefix}.kv_a_proj_with_mqa") + self.kv_a_layernorm = RMSNorm(self.kv_lora_rank, + eps=config.rms_norm_eps) + self.kv_b_proj = ColumnParallelLinear( + self.kv_lora_rank, + self.num_heads * (self.qk_nope_head_dim + self.v_head_dim), + bias=False, + quant_config=quant_config, + prefix=f"{prefix}.kv_b_proj") + self.o_proj = RowParallelLinear(self.num_heads * self.v_head_dim, + self.hidden_size, + bias=False, + quant_config=quant_config, + prefix=f"{prefix}.o_proj") + + rope_scaling["rope_type"] = 'deepseek_yarn' + self.rotary_emb = get_rope(qk_rope_head_dim, + rotary_dim=qk_rope_head_dim, + max_position=max_position_embeddings, + base=rope_theta, + rope_scaling=rope_scaling, + is_neox_style=False) + if rope_scaling: + mscale_all_dim = rope_scaling.get("mscale_all_dim", False) + scaling_factor = rope_scaling["factor"] + mscale = yarn_get_mscale(scaling_factor, float(mscale_all_dim)) + self.scaling = self.scaling * mscale * mscale + + self.mla_attn = Attention( + num_heads=self.num_local_heads, + head_size=self.kv_lora_rank, + scale=self.scaling, + num_kv_heads=1, + cache_config=cache_config, + quant_config=quant_config, + prefix=f"{prefix}.attn", + use_mla=True, + # MLA Args + q_lora_rank=self.q_lora_rank, + kv_lora_rank=self.kv_lora_rank, + qk_nope_head_dim=self.qk_nope_head_dim, + qk_rope_head_dim=self.qk_rope_head_dim, + qk_head_dim=self.qk_head_dim, + v_head_dim=self.v_head_dim, + rotary_emb=self.rotary_emb, + q_proj=self.q_proj if self.q_lora_rank is None else self.q_b_proj, + kv_b_proj=self.kv_b_proj, + o_proj=self.o_proj, + ) + + self.prefix = prefix + self.debug_layer_idx = int(self.prefix.split(".")[-2]) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + kv_cache: torch.Tensor, + attn_metadata: AttentionMetadata, + ) -> torch.Tensor: + if self.q_lora_rank is not None: + ckq = self.q_a_proj(hidden_states)[0] + hidden_states_or_q_c = self.q_a_layernorm(ckq) + else: + hidden_states_or_q_c = hidden_states + kv_c, k_pe = self.kv_a_proj_with_mqa(hidden_states)[0].split( + [self.kv_lora_rank, self.qk_rope_head_dim], dim=-1) + kv_c_normed = self.kv_a_layernorm(kv_c.contiguous()) + return self.mla_attn(hidden_states_or_q_c, kv_c_normed, k_pe, kv_cache, + attn_metadata) + + class DeepseekV3DecoderLayer(nn.Module): def __init__( self, config: PretrainedConfig, prefix: str, + model_config: ModelConfig, cache_config: Optional[CacheConfig] = None, quant_config: Optional[QuantizationConfig] = None, ) -> None: @@ -351,7 +495,11 @@ def __init__( # DecoderLayers are created with `make_layers` which passes the prefix # with the layer's index. layer_idx = int(prefix.split(sep='.')[-1]) - self.self_attn = DeepseekV3Attention( + if model_config.use_mla: + attn_cls = DeepseekV3MLAAttention + else: + attn_cls = DeepseekV3Attention + self.self_attn = attn_cls( config=config, hidden_size=self.hidden_size, num_heads=config.num_attention_heads, @@ -428,6 +576,7 @@ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""): super().__init__() config = vllm_config.model_config.hf_config + model_config = vllm_config.model_config cache_config = vllm_config.cache_config quant_config = vllm_config.quant_config @@ -447,6 +596,7 @@ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""): lambda prefix: DeepseekV3DecoderLayer( config, prefix, + model_config=model_config, cache_config=cache_config, quant_config=quant_config, ), diff --git a/vllm/v1/core/kv_cache_utils.py b/vllm/v1/core/kv_cache_utils.py index dbdda51aedaa0..2b6557ad3ce66 100644 --- a/vllm/v1/core/kv_cache_utils.py +++ b/vllm/v1/core/kv_cache_utils.py @@ -262,8 +262,10 @@ def hash_block_tokens( The hash value of the block and the token ids in the block. The entire tuple is used as the hash key of the block. """ - return BlockHashType(hash((parent_block_hash, *curr_block_token_ids)), - tuple(curr_block_token_ids), extra_keys) + curr_block_token_ids_tuple = tuple(curr_block_token_ids) + return BlockHashType( + hash((parent_block_hash, curr_block_token_ids_tuple, extra_keys)), + curr_block_token_ids_tuple, extra_keys) def hash_request_tokens(block_size: int, diff --git a/vllm/worker/cache_engine.py b/vllm/worker/cache_engine.py index 08316ba74aad8..c427b759b2e97 100644 --- a/vllm/worker/cache_engine.py +++ b/vllm/worker/cache_engine.py @@ -110,7 +110,9 @@ def get_cache_block_size( parallel_config, LayerBlockType.attention) key_cache_block = cache_config.block_size * num_heads * head_size - value_cache_block = key_cache_block + # For MLA there is no value cache, since the latent vector + # is joint keys and values. + value_cache_block = key_cache_block if not model_config.use_mla else 0 total = num_attention_layers * (key_cache_block + value_cache_block) if cache_config.cache_dtype == "auto": dtype = model_config.dtype