Instructions to use devanshbatham/nyx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use devanshbatham/nyx with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf devanshbatham/nyx # Run inference directly in the terminal: llama cli -hf devanshbatham/nyx
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf devanshbatham/nyx # Run inference directly in the terminal: llama cli -hf devanshbatham/nyx
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf devanshbatham/nyx # Run inference directly in the terminal: ./llama-cli -hf devanshbatham/nyx
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf devanshbatham/nyx # Run inference directly in the terminal: ./build/bin/llama-cli -hf devanshbatham/nyx
Use Docker
docker model run hf.co/devanshbatham/nyx
- LM Studio
- Jan
- vLLM
How to use devanshbatham/nyx with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "devanshbatham/nyx" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "devanshbatham/nyx", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/devanshbatham/nyx
- Ollama
How to use devanshbatham/nyx with Ollama:
ollama run hf.co/devanshbatham/nyx
- Unsloth Desktop
- Pi
How to use devanshbatham/nyx with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf devanshbatham/nyx
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "devanshbatham/nyx" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use devanshbatham/nyx with Docker Model Runner:
docker model run hf.co/devanshbatham/nyx
- Lemonade
How to use devanshbatham/nyx with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull devanshbatham/nyx
Run and chat with the model
lemonade run user.nyx-{{QUANT_TAG}}List all available models
lemonade list
- Hermes Agent
How to use devanshbatham/nyx with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf devanshbatham/nyx
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default devanshbatham/nyx
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use devanshbatham/nyx with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf devanshbatham/nyx
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "devanshbatham/nyx" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Devansh Batham commited on
Commit ·
f80a01b
1
Parent(s): 70155f1
Publish Nyx INT4+FP8 production checkpoint
Browse files- .gitattributes +4 -28
- LICENSE +202 -0
- NOTICE +13 -0
- README.md +124 -0
- assets/nyx-vs-jev-benchmarks.png +3 -0
- benchmarks/random-six-report.md +43 -0
- benchmarks/summary.json +32 -0
- build_manifest.py +41 -0
- chat_template.jinja +154 -0
- config.json +96 -0
- generation_config.json +13 -0
- provenance.json +30 -0
- release-manifest.json +116 -0
- runtime/entrypoint.sh +8 -0
- runtime/label-token-ids.json +1 -0
- runtime/patches/qwen35-text-quark-generated-scales.patch +32 -0
- runtime/patches/sglang-batch-result-processor.py +1378 -0
- runtime/patches/sglang-logits-processor.py +1272 -0
- runtime/patches/sglang-qwen35-text.py +298 -0
- runtime/patches/sglang-qwen35.py +0 -0
- serve.sh +45 -0
- tokenizer.json +3 -0
- tokenizer_config.json +32 -0
- verify.py +41 -0
- weights/TP-1-sig-e793a9432f30cd4e/READY +1 -0
- weights/TP-1-sig-e793a9432f30cd4e/checksum.json +0 -0
- weights/TP-1-sig-e793a9432f30cd4e/model-00000-common.safetensor +3 -0
- weights/TP-1-sig-e793a9432f30cd4e/model-00001-common.safetensor +3 -0
- weights/TP-1-sig-e793a9432f30cd4e/model-00002-common.safetensor +3 -0
- weights/TP-1-sig-e793a9432f30cd4e/model-00003-common.safetensor +3 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,11 @@
|
|
| 1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.
|
| 14 |
-
*.
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 2 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 3 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
*.model filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.safetensor filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 9 |
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
| 11 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright 2026 Alibaba Cloud
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
NOTICE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Nyx
|
| 2 |
+
Copyright 2026 devanshbatham
|
| 3 |
+
|
| 4 |
+
Derived from Qwen/Qwen3.5-35B-A3B, revision
|
| 5 |
+
59d61f3ce65a6d9863b86d2e96597125219dc754.
|
| 6 |
+
|
| 7 |
+
Qwen3.5 was created by the Qwen team at Alibaba Cloud and is distributed under
|
| 8 |
+
the Apache License 2.0. Nyx adds task-specific decision adaptation, packaging,
|
| 9 |
+
runtime integration, and INT4+FP8 quantization. The original Qwen license is
|
| 10 |
+
retained in LICENSE.
|
| 11 |
+
|
| 12 |
+
Qwen and Alibaba Cloud do not endorse and are not affiliated with this release.
|
| 13 |
+
|
README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
library_name: sglang
|
| 4 |
+
pipeline_tag: text-generation
|
| 5 |
+
base_model:
|
| 6 |
+
- Qwen/Qwen3.5-35B-A3B
|
| 7 |
+
tags:
|
| 8 |
+
- nyx
|
| 9 |
+
- qwen3.5
|
| 10 |
+
- mixture-of-experts
|
| 11 |
+
- int4
|
| 12 |
+
- fp8
|
| 13 |
+
- rocm
|
| 14 |
+
- sglang
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# Nyx
|
| 18 |
+
|
| 19 |
+
Nyx is a task-specialized Qwen3.5-35B-A3B decision model published by
|
| 20 |
+
[`devanshbatham`](https://huggingface.co/devanshbatham). This repository contains the exact
|
| 21 |
+
accepted production checkpoint in its native SGLang presharded format.
|
| 22 |
+
|
| 23 |
+

|
| 24 |
+
|
| 25 |
+
## Model
|
| 26 |
+
|
| 27 |
+
| Property | Value |
|
| 28 |
+
|---|---|
|
| 29 |
+
| Base model | [`Qwen/Qwen3.5-35B-A3B`](https://huggingface.co/Qwen/Qwen3.5-35B-A3B) |
|
| 30 |
+
| Base revision | `59d61f3ce65a6d9863b86d2e96597125219dc754` |
|
| 31 |
+
| Architecture | Qwen3.5-35B-A3B text MoE |
|
| 32 |
+
| Quantization | Per-channel INT4 routed-expert storage; FP8 expert compute; dynamic FP8 dense layers |
|
| 33 |
+
| Runtime precision | BF16 activations and KV cache; FP32 SSM state |
|
| 34 |
+
| Measured model memory | 19.43 GB |
|
| 35 |
+
| Qualified hardware | AMD MI325X (`gfx942`) |
|
| 36 |
+
| Publisher | `devanshbatham` |
|
| 37 |
+
|
| 38 |
+
The original architecture, tokenizer, and base weights are from Qwen. Nyx contains subsequent
|
| 39 |
+
task-specific decision adaptation and quantization. See [NOTICE](NOTICE) and [provenance.json](provenance.json).
|
| 40 |
+
|
| 41 |
+
## Compatibility
|
| 42 |
+
|
| 43 |
+
This is **not** a standard Transformers `safetensors` checkpoint and cannot be loaded with
|
| 44 |
+
`AutoModel.from_pretrained`. The weight files use SGLang's pinned presharded loader plus the
|
| 45 |
+
included runtime patches. Only the pinned SGLang container on AMD MI325X has been qualified.
|
| 46 |
+
Other GPUs, SGLang versions, quantization backends, and conversions are unverified.
|
| 47 |
+
|
| 48 |
+
## Run
|
| 49 |
+
|
| 50 |
+
Requirements: Linux, Docker, ROCm-visible `/dev/kfd` and `/dev/dri`, and an AMD MI325X.
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
hf download devanshbatham/nyx --local-dir nyx
|
| 54 |
+
cd nyx
|
| 55 |
+
python3 verify.py
|
| 56 |
+
bash serve.sh
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
The server binds to `127.0.0.1:30000` by default. Override the port with `NYX_PORT`. Keep the
|
| 60 |
+
model backend private and place an authenticated API gateway and TLS termination in front of it
|
| 61 |
+
for networked production use.
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
curl http://127.0.0.1:30000/v1/chat/completions \
|
| 65 |
+
-H 'Content-Type: application/json' \
|
| 66 |
+
-d '{
|
| 67 |
+
"model": "/model",
|
| 68 |
+
"messages": [{"role": "user", "content": "Classify as positive or negative: I loved it."}],
|
| 69 |
+
"temperature": 0,
|
| 70 |
+
"max_tokens": 8
|
| 71 |
+
}'
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
The production System One gateway and typed clients are maintained separately at
|
| 75 |
+
[`devanshbatham/nyx`](https://github.com/devanshbatham/nyx).
|
| 76 |
+
|
| 77 |
+
## Evaluation
|
| 78 |
+
|
| 79 |
+
The frozen public comparison used 2,277 identical cases, concurrency 8, and counted every failure
|
| 80 |
+
as incorrect. All three providers completed every case with zero errors and zero retries.
|
| 81 |
+
|
| 82 |
+
| Benchmark | Nyx INT4+FP8 | Qwen base BF16 | Jev 1.13 |
|
| 83 |
+
|---|---:|---:|---:|
|
| 84 |
+
| AG News | 84.75% | 85.75% | 86.50% |
|
| 85 |
+
| TREC coarse | 92.00% | 94.00% | 93.00% |
|
| 86 |
+
| CoLA | 79.00% | 79.75% | 77.75% |
|
| 87 |
+
| RTE | 93.14% | 93.86% | 91.34% |
|
| 88 |
+
| Emotion | 43.33% | 44.67% | 47.00% |
|
| 89 |
+
| IMDb | 96.50% | 96.25% | 97.00% |
|
| 90 |
+
| **Dataset-macro accuracy** | **81.45%** | **82.38%** | **82.10%** |
|
| 91 |
+
| **Pooled accuracy** | **82.96%** | **83.93%** | **83.62%** |
|
| 92 |
+
|
| 93 |
+
Nyx versus Jev exact paired McNemar p=`0.23154`. Base versus Nyx p=`0.03896` before correction
|
| 94 |
+
for three comparisons and does not cross the Bonferroni-adjusted `0.0167` threshold. Public
|
| 95 |
+
pretraining contamination is unknown for every model. These six benchmarks do not establish
|
| 96 |
+
universal superiority. See [the evaluation record](benchmarks/random-six-report.md).
|
| 97 |
+
|
| 98 |
+
On the separately authored 2,000-case H1 development workload, Nyx INT4+FP8 scored 85.65% versus
|
| 99 |
+
86.65% for Nyx FP8. The compact model used 19.43 GB versus 34.58 GB model memory and was accepted
|
| 100 |
+
with the measured 1.00 percentage-point tradeoff. H1 is development evidence, not an independent
|
| 101 |
+
external test.
|
| 102 |
+
|
| 103 |
+
## Training disclosure
|
| 104 |
+
|
| 105 |
+
Nyx was post-trained for structured decision/classification tasks using task-specific examples,
|
| 106 |
+
including teacher probability targets. The publisher has affirmed the right to distribute the
|
| 107 |
+
resulting weights. Raw training records and private evaluation data are not included.
|
| 108 |
+
|
| 109 |
+
## Limitations
|
| 110 |
+
|
| 111 |
+
- Optimized for structured classification and decision workloads; general assistant quality was not established.
|
| 112 |
+
- Qualified only with the included SGLang/ROCm runtime on one AMD MI325X.
|
| 113 |
+
- The public evaluation is finite and may overlap model pretraining data.
|
| 114 |
+
- Outputs can be incorrect or biased. Use independent validation and human review for consequential decisions.
|
| 115 |
+
- No claim of bit-exact parity with BF16, Jev, or the original Qwen model is made.
|
| 116 |
+
- The included launcher exposes only a loopback model backend; production authentication is the operator's responsibility.
|
| 117 |
+
|
| 118 |
+
## License and attribution
|
| 119 |
+
|
| 120 |
+
Apache License 2.0. Nyx is derived from
|
| 121 |
+
[`Qwen/Qwen3.5-35B-A3B`](https://huggingface.co/Qwen/Qwen3.5-35B-A3B), also distributed under
|
| 122 |
+
Apache-2.0. Qwen and Alibaba Cloud are credited as the original model authors. This project is not
|
| 123 |
+
endorsed by or affiliated with the Qwen authors.
|
| 124 |
+
|
assets/nyx-vs-jev-benchmarks.png
ADDED
|
Git LFS Details
|
benchmarks/random-six-report.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Six public classification benchmarks: Nyx vs base vs Jev
|
| 2 |
+
|
| 3 |
+
Frozen paired cohort: **2,277 cases**; SHA-256 `046a38c24747fd724003aca7e5f1d1b6e148d599092222361c0717ecff84ecad`; concurrency 8; every failure counts wrong.
|
| 4 |
+
|
| 5 |
+
| Dataset | Cases | Nyx INT4+FP8 | Qwen base BF16 | Jev 1.13 |
|
| 6 |
+
|---|---:|---:|---:|---:|
|
| 7 |
+
| ag_news | 400 | 84.75% (339/400) | 85.75% (343/400) | 86.50% (346/400) |
|
| 8 |
+
| trec_coarse | 500 | 92.00% (460/500) | 94.00% (470/500) | 93.00% (465/500) |
|
| 9 |
+
| cola | 400 | 79.00% (316/400) | 79.75% (319/400) | 77.75% (311/400) |
|
| 10 |
+
| rte | 277 | 93.14% (258/277) | 93.86% (260/277) | 91.34% (253/277) |
|
| 11 |
+
| emotion | 300 | 43.33% (130/300) | 44.67% (134/300) | 47.00% (141/300) |
|
| 12 |
+
| imdb | 400 | 96.50% (386/400) | 96.25% (385/400) | 97.00% (388/400) |
|
| 13 |
+
|
| 14 |
+
| Aggregate | Nyx INT4+FP8 | Qwen base BF16 | Jev 1.13 |
|
| 15 |
+
|---|---:|---:|---:|
|
| 16 |
+
| Dataset-macro accuracy | 81.45% | 82.38% | 82.10% |
|
| 17 |
+
| Dataset-macro F1 | 81.06% | 82.02% | 81.85% |
|
| 18 |
+
| Pooled micro accuracy | 82.96% | 83.93% | 83.62% |
|
| 19 |
+
|
| 20 |
+
## Paired comparison
|
| 21 |
+
|
| 22 |
+
Exact two-sided McNemar tests use the identical case-level predictions. Base beat Nyx on 63 discordant cases versus 41 in the other direction (p=0.03896, nominal and not corrected for three comparisons). Jev beat Nyx 76 to 61 (p=0.23154), while base beat Jev 66 to 59 (p=0.59168). Thus only base-versus-Nyx crosses an uncorrected 0.05 threshold; none crosses a Bonferroni-adjusted 0.0167 threshold.
|
| 23 |
+
|
| 24 |
+
| Operational timing | Nyx INT4+FP8 | Qwen base BF16 | Jev 1.13 |
|
| 25 |
+
|---|---:|---:|---:|
|
| 26 |
+
| Wall time | 20.21 s | 23.82 s | 53.67 s |
|
| 27 |
+
| Throughput | 112.67 req/s | 95.58 req/s | 42.43 req/s |
|
| 28 |
+
| Median request | 66.25 ms | 78.23 ms | 182.33 ms |
|
| 29 |
+
| p95 request | 89.22 ms | 101.82 ms | 245.12 ms |
|
| 30 |
+
|
| 31 |
+
## Method and caveats
|
| 32 |
+
|
| 33 |
+
- Dataset choices and examples were frozen before provider scoring; request bodies and order are byte-equivalent across providers.
|
| 34 |
+
- AG News, CoLA, Emotion, and IMDb are fixed class-stratified samples; TREC test and RTE validation are complete.
|
| 35 |
+
- Accuracy is reported per dataset. Dataset-macro accuracy weights each benchmark equally; pooled accuracy weights individual cases.
|
| 36 |
+
- Public-benchmark pretraining contamination is unknown for all three models. This finite suite does not prove universal classification quality.
|
| 37 |
+
- Timing is operational only: Nyx and base are local on one MI325X, while Jev is remote HTTPS on unmatched infrastructure.
|
| 38 |
+
|
| 39 |
+
## Artifacts
|
| 40 |
+
|
| 41 |
+
- Manifest: `artifacts/random-six-headtohead-v2/manifest.json`
|
| 42 |
+
- Comparison JSON: `artifacts/random-six-headtohead-v2/comparison.json`
|
| 43 |
+
- Raw provider results: `artifacts/random-six-headtohead-v2/result-nyx-int4fp8.json`, `artifacts/random-six-headtohead-v2/result-qwen-base-bf16.json`, `artifacts/random-six-headtohead-v2/result-jev-1.13.json`
|
benchmarks/summary.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"format_version": 1,
|
| 3 |
+
"cases_sha256": "046a38c24747fd724003aca7e5f1d1b6e148d599092222361c0717ecff84ecad",
|
| 4 |
+
"cases": 2277,
|
| 5 |
+
"concurrency": 8,
|
| 6 |
+
"errors": {
|
| 7 |
+
"nyx-int4fp8": 0,
|
| 8 |
+
"qwen-base-bf16": 0,
|
| 9 |
+
"jev-1.13": 0
|
| 10 |
+
},
|
| 11 |
+
"dataset_macro_accuracy": {
|
| 12 |
+
"nyx-int4fp8": 0.8145402125952668,
|
| 13 |
+
"qwen-base-bf16": 0.8237991375852386,
|
| 14 |
+
"jev-1.13": 0.8209762334536702
|
| 15 |
+
},
|
| 16 |
+
"pooled_accuracy": {
|
| 17 |
+
"nyx-int4fp8": 0.8296003513394817,
|
| 18 |
+
"qwen-base-bf16": 0.839262187088274,
|
| 19 |
+
"jev-1.13": 0.8361879666227492
|
| 20 |
+
},
|
| 21 |
+
"paired_mcnemar_two_sided_p": {
|
| 22 |
+
"nyx-int4fp8_vs_qwen-base-bf16": 0.03895793340131187,
|
| 23 |
+
"nyx-int4fp8_vs_jev-1.13": 0.23153716588439505,
|
| 24 |
+
"qwen-base-bf16_vs_jev-1.13": 0.591683927094163
|
| 25 |
+
},
|
| 26 |
+
"limitations": [
|
| 27 |
+
"Public benchmark contamination is unknown.",
|
| 28 |
+
"Fixed samples are not complete official-suite scores except TREC test and RTE validation.",
|
| 29 |
+
"Jev timing used unmatched remote infrastructure."
|
| 30 |
+
]
|
| 31 |
+
}
|
| 32 |
+
|
build_manifest.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Build the deterministic release manifest for this repository."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import hashlib
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
ROOT = Path(__file__).resolve().parent
|
| 12 |
+
EXCLUDE = {"release-manifest.json", "build_manifest.py", "verify.py"}
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def digest(path: Path) -> str:
|
| 16 |
+
checksum = hashlib.sha256()
|
| 17 |
+
with path.open("rb") as stream:
|
| 18 |
+
for block in iter(lambda: stream.read(8 * 1024 * 1024), b""):
|
| 19 |
+
checksum.update(block)
|
| 20 |
+
return checksum.hexdigest()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
files = {}
|
| 24 |
+
for path in sorted(ROOT.rglob("*")):
|
| 25 |
+
if not path.is_file() or ".git" in path.parts:
|
| 26 |
+
continue
|
| 27 |
+
relative = path.relative_to(ROOT).as_posix()
|
| 28 |
+
if relative in EXCLUDE:
|
| 29 |
+
continue
|
| 30 |
+
files[relative] = {"bytes": path.stat().st_size, "sha256": digest(path)}
|
| 31 |
+
|
| 32 |
+
payload = {
|
| 33 |
+
"format_version": 1,
|
| 34 |
+
"repository": "devanshbatham/nyx",
|
| 35 |
+
"artifact": "nyx-35b-a3b-int4fp8",
|
| 36 |
+
"files": files,
|
| 37 |
+
"total_bytes": sum(item["bytes"] for item in files.values()),
|
| 38 |
+
}
|
| 39 |
+
(ROOT / "release-manifest.json").write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
|
| 40 |
+
print(json.dumps({"files": len(files), "total_bytes": payload["total_bytes"]}))
|
| 41 |
+
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- set image_count = namespace(value=0) %}
|
| 2 |
+
{%- set video_count = namespace(value=0) %}
|
| 3 |
+
{%- macro render_content(content, do_vision_count, is_system_content=false) %}
|
| 4 |
+
{%- if content is string %}
|
| 5 |
+
{{- content }}
|
| 6 |
+
{%- elif content is iterable and content is not mapping %}
|
| 7 |
+
{%- for item in content %}
|
| 8 |
+
{%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
|
| 9 |
+
{%- if is_system_content %}
|
| 10 |
+
{{- raise_exception('System message cannot contain images.') }}
|
| 11 |
+
{%- endif %}
|
| 12 |
+
{%- if do_vision_count %}
|
| 13 |
+
{%- set image_count.value = image_count.value + 1 %}
|
| 14 |
+
{%- endif %}
|
| 15 |
+
{%- if add_vision_id %}
|
| 16 |
+
{{- 'Picture ' ~ image_count.value ~ ': ' }}
|
| 17 |
+
{%- endif %}
|
| 18 |
+
{{- '<|vision_start|><|image_pad|><|vision_end|>' }}
|
| 19 |
+
{%- elif 'video' in item or item.type == 'video' %}
|
| 20 |
+
{%- if is_system_content %}
|
| 21 |
+
{{- raise_exception('System message cannot contain videos.') }}
|
| 22 |
+
{%- endif %}
|
| 23 |
+
{%- if do_vision_count %}
|
| 24 |
+
{%- set video_count.value = video_count.value + 1 %}
|
| 25 |
+
{%- endif %}
|
| 26 |
+
{%- if add_vision_id %}
|
| 27 |
+
{{- 'Video ' ~ video_count.value ~ ': ' }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{{- '<|vision_start|><|video_pad|><|vision_end|>' }}
|
| 30 |
+
{%- elif 'text' in item %}
|
| 31 |
+
{{- item.text }}
|
| 32 |
+
{%- else %}
|
| 33 |
+
{{- raise_exception('Unexpected item type in content.') }}
|
| 34 |
+
{%- endif %}
|
| 35 |
+
{%- endfor %}
|
| 36 |
+
{%- elif content is none or content is undefined %}
|
| 37 |
+
{{- '' }}
|
| 38 |
+
{%- else %}
|
| 39 |
+
{{- raise_exception('Unexpected content type.') }}
|
| 40 |
+
{%- endif %}
|
| 41 |
+
{%- endmacro %}
|
| 42 |
+
{%- if not messages %}
|
| 43 |
+
{{- raise_exception('No messages provided.') }}
|
| 44 |
+
{%- endif %}
|
| 45 |
+
{%- if tools and tools is iterable and tools is not mapping %}
|
| 46 |
+
{{- '<|im_start|>system\n' }}
|
| 47 |
+
{{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
|
| 48 |
+
{%- for tool in tools %}
|
| 49 |
+
{{- "\n" }}
|
| 50 |
+
{{- tool | tojson }}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{{- "\n</tools>" }}
|
| 53 |
+
{{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
|
| 54 |
+
{%- if messages[0].role == 'system' %}
|
| 55 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 56 |
+
{%- if content %}
|
| 57 |
+
{{- '\n\n' + content }}
|
| 58 |
+
{%- endif %}
|
| 59 |
+
{%- endif %}
|
| 60 |
+
{{- '<|im_end|>\n' }}
|
| 61 |
+
{%- else %}
|
| 62 |
+
{%- if messages[0].role == 'system' %}
|
| 63 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 64 |
+
{{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
|
| 65 |
+
{%- endif %}
|
| 66 |
+
{%- endif %}
|
| 67 |
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
| 68 |
+
{%- for message in messages[::-1] %}
|
| 69 |
+
{%- set index = (messages|length - 1) - loop.index0 %}
|
| 70 |
+
{%- if ns.multi_step_tool and message.role == "user" %}
|
| 71 |
+
{%- set content = render_content(message.content, false)|trim %}
|
| 72 |
+
{%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
|
| 73 |
+
{%- set ns.multi_step_tool = false %}
|
| 74 |
+
{%- set ns.last_query_index = index %}
|
| 75 |
+
{%- endif %}
|
| 76 |
+
{%- endif %}
|
| 77 |
+
{%- endfor %}
|
| 78 |
+
{%- if ns.multi_step_tool %}
|
| 79 |
+
{{- raise_exception('No user query found in messages.') }}
|
| 80 |
+
{%- endif %}
|
| 81 |
+
{%- for message in messages %}
|
| 82 |
+
{%- set content = render_content(message.content, true)|trim %}
|
| 83 |
+
{%- if message.role == "system" %}
|
| 84 |
+
{%- if not loop.first %}
|
| 85 |
+
{{- raise_exception('System message must be at the beginning.') }}
|
| 86 |
+
{%- endif %}
|
| 87 |
+
{%- elif message.role == "user" %}
|
| 88 |
+
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
| 89 |
+
{%- elif message.role == "assistant" %}
|
| 90 |
+
{%- set reasoning_content = '' %}
|
| 91 |
+
{%- if message.reasoning_content is string %}
|
| 92 |
+
{%- set reasoning_content = message.reasoning_content %}
|
| 93 |
+
{%- else %}
|
| 94 |
+
{%- if '</think>' in content %}
|
| 95 |
+
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
| 96 |
+
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
|
| 97 |
+
{%- endif %}
|
| 98 |
+
{%- endif %}
|
| 99 |
+
{%- set reasoning_content = reasoning_content|trim %}
|
| 100 |
+
{%- if loop.index0 > ns.last_query_index %}
|
| 101 |
+
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
|
| 102 |
+
{%- else %}
|
| 103 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 104 |
+
{%- endif %}
|
| 105 |
+
{%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
|
| 106 |
+
{%- for tool_call in message.tool_calls %}
|
| 107 |
+
{%- if tool_call.function is defined %}
|
| 108 |
+
{%- set tool_call = tool_call.function %}
|
| 109 |
+
{%- endif %}
|
| 110 |
+
{%- if loop.first %}
|
| 111 |
+
{%- if content|trim %}
|
| 112 |
+
{{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 113 |
+
{%- else %}
|
| 114 |
+
{{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 115 |
+
{%- endif %}
|
| 116 |
+
{%- else %}
|
| 117 |
+
{{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 118 |
+
{%- endif %}
|
| 119 |
+
{%- if tool_call.arguments is defined %}
|
| 120 |
+
{%- for args_name, args_value in tool_call.arguments|items %}
|
| 121 |
+
{{- '<parameter=' + args_name + '>\n' }}
|
| 122 |
+
{%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
|
| 123 |
+
{{- args_value }}
|
| 124 |
+
{{- '\n</parameter>\n' }}
|
| 125 |
+
{%- endfor %}
|
| 126 |
+
{%- endif %}
|
| 127 |
+
{{- '</function>\n</tool_call>' }}
|
| 128 |
+
{%- endfor %}
|
| 129 |
+
{%- endif %}
|
| 130 |
+
{{- '<|im_end|>\n' }}
|
| 131 |
+
{%- elif message.role == "tool" %}
|
| 132 |
+
{%- if loop.previtem and loop.previtem.role != "tool" %}
|
| 133 |
+
{{- '<|im_start|>user' }}
|
| 134 |
+
{%- endif %}
|
| 135 |
+
{{- '\n<tool_response>\n' }}
|
| 136 |
+
{{- content }}
|
| 137 |
+
{{- '\n</tool_response>' }}
|
| 138 |
+
{%- if not loop.last and loop.nextitem.role != "tool" %}
|
| 139 |
+
{{- '<|im_end|>\n' }}
|
| 140 |
+
{%- elif loop.last %}
|
| 141 |
+
{{- '<|im_end|>\n' }}
|
| 142 |
+
{%- endif %}
|
| 143 |
+
{%- else %}
|
| 144 |
+
{{- raise_exception('Unexpected message role.') }}
|
| 145 |
+
{%- endif %}
|
| 146 |
+
{%- endfor %}
|
| 147 |
+
{%- if add_generation_prompt %}
|
| 148 |
+
{{- '<|im_start|>assistant\n' }}
|
| 149 |
+
{%- if enable_thinking is defined and enable_thinking is false %}
|
| 150 |
+
{{- '<think>\n\n</think>\n\n' }}
|
| 151 |
+
{%- else %}
|
| 152 |
+
{{- '<think>\n' }}
|
| 153 |
+
{%- endif %}
|
| 154 |
+
{%- endif %}
|
config.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Qwen3_5MoeForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"attention_bias": false,
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"attn_output_gate": true,
|
| 8 |
+
"bos_token_id": null,
|
| 9 |
+
"dtype": "bfloat16",
|
| 10 |
+
"eos_token_id": 248044,
|
| 11 |
+
"full_attention_interval": 4,
|
| 12 |
+
"head_dim": 256,
|
| 13 |
+
"hidden_act": "silu",
|
| 14 |
+
"hidden_size": 2048,
|
| 15 |
+
"initializer_range": 0.02,
|
| 16 |
+
"layer_types": [
|
| 17 |
+
"linear_attention",
|
| 18 |
+
"linear_attention",
|
| 19 |
+
"linear_attention",
|
| 20 |
+
"full_attention",
|
| 21 |
+
"linear_attention",
|
| 22 |
+
"linear_attention",
|
| 23 |
+
"linear_attention",
|
| 24 |
+
"full_attention",
|
| 25 |
+
"linear_attention",
|
| 26 |
+
"linear_attention",
|
| 27 |
+
"linear_attention",
|
| 28 |
+
"full_attention",
|
| 29 |
+
"linear_attention",
|
| 30 |
+
"linear_attention",
|
| 31 |
+
"linear_attention",
|
| 32 |
+
"full_attention",
|
| 33 |
+
"linear_attention",
|
| 34 |
+
"linear_attention",
|
| 35 |
+
"linear_attention",
|
| 36 |
+
"full_attention",
|
| 37 |
+
"linear_attention",
|
| 38 |
+
"linear_attention",
|
| 39 |
+
"linear_attention",
|
| 40 |
+
"full_attention",
|
| 41 |
+
"linear_attention",
|
| 42 |
+
"linear_attention",
|
| 43 |
+
"linear_attention",
|
| 44 |
+
"full_attention",
|
| 45 |
+
"linear_attention",
|
| 46 |
+
"linear_attention",
|
| 47 |
+
"linear_attention",
|
| 48 |
+
"full_attention",
|
| 49 |
+
"linear_attention",
|
| 50 |
+
"linear_attention",
|
| 51 |
+
"linear_attention",
|
| 52 |
+
"full_attention",
|
| 53 |
+
"linear_attention",
|
| 54 |
+
"linear_attention",
|
| 55 |
+
"linear_attention",
|
| 56 |
+
"full_attention"
|
| 57 |
+
],
|
| 58 |
+
"linear_conv_kernel_dim": 4,
|
| 59 |
+
"linear_key_head_dim": 128,
|
| 60 |
+
"linear_num_key_heads": 16,
|
| 61 |
+
"linear_num_value_heads": 32,
|
| 62 |
+
"linear_value_head_dim": 128,
|
| 63 |
+
"mamba_ssm_dtype": "float32",
|
| 64 |
+
"max_position_embeddings": 262144,
|
| 65 |
+
"mlp_only_layers": [],
|
| 66 |
+
"model_type": "qwen3_5_moe_text",
|
| 67 |
+
"moe_intermediate_size": 512,
|
| 68 |
+
"mtp_num_hidden_layers": 1,
|
| 69 |
+
"mtp_use_dedicated_embeddings": false,
|
| 70 |
+
"num_attention_heads": 16,
|
| 71 |
+
"num_experts": 256,
|
| 72 |
+
"num_experts_per_tok": 8,
|
| 73 |
+
"num_hidden_layers": 40,
|
| 74 |
+
"num_key_value_heads": 2,
|
| 75 |
+
"output_router_logits": false,
|
| 76 |
+
"pad_token_id": null,
|
| 77 |
+
"partial_rotary_factor": 0.25,
|
| 78 |
+
"rms_norm_eps": 1e-06,
|
| 79 |
+
"rope_parameters": {
|
| 80 |
+
"mrope_interleaved": true,
|
| 81 |
+
"mrope_section": [
|
| 82 |
+
11,
|
| 83 |
+
11,
|
| 84 |
+
10
|
| 85 |
+
],
|
| 86 |
+
"partial_rotary_factor": 0.25,
|
| 87 |
+
"rope_theta": 10000000,
|
| 88 |
+
"rope_type": "default"
|
| 89 |
+
},
|
| 90 |
+
"router_aux_loss_coef": 0.001,
|
| 91 |
+
"shared_expert_intermediate_size": 512,
|
| 92 |
+
"tie_word_embeddings": false,
|
| 93 |
+
"transformers_version": "5.17.0",
|
| 94 |
+
"use_cache": true,
|
| 95 |
+
"vocab_size": 248320
|
| 96 |
+
}
|
generation_config.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 248044,
|
| 3 |
+
"do_sample": true,
|
| 4 |
+
"eos_token_id": [
|
| 5 |
+
248046,
|
| 6 |
+
248044
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 248044,
|
| 9 |
+
"temperature": 1.0,
|
| 10 |
+
"top_k": 20,
|
| 11 |
+
"top_p": 0.95,
|
| 12 |
+
"transformers_version": "5.17.0"
|
| 13 |
+
}
|
provenance.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"format_version": 1,
|
| 3 |
+
"model": "devanshbatham/nyx",
|
| 4 |
+
"publisher": "devanshbatham",
|
| 5 |
+
"base_model": "Qwen/Qwen3.5-35B-A3B",
|
| 6 |
+
"base_revision": "59d61f3ce65a6d9863b86d2e96597125219dc754",
|
| 7 |
+
"license": "Apache-2.0",
|
| 8 |
+
"quantization": {
|
| 9 |
+
"routed_expert_storage": "per-channel INT4",
|
| 10 |
+
"expert_compute": "FP8",
|
| 11 |
+
"dense_layers": "dynamic FP8",
|
| 12 |
+
"activations": "BF16",
|
| 13 |
+
"kv_cache": "BF16",
|
| 14 |
+
"ssm_state": "FP32"
|
| 15 |
+
},
|
| 16 |
+
"qualified_runtime": {
|
| 17 |
+
"hardware": "AMD MI325X gfx942",
|
| 18 |
+
"sglang_image": "lmsysorg/sglang@sha256:a0ffcdd013af6d79c9b7c4348c42b14a79a48f6c43dd3ca0235ec3802da45f75",
|
| 19 |
+
"tensor_parallel_size": 1
|
| 20 |
+
},
|
| 21 |
+
"accepted_evidence": {
|
| 22 |
+
"model_memory_gb": 19.43,
|
| 23 |
+
"package_bytes": 19657745446,
|
| 24 |
+
"h1_correct": 1713,
|
| 25 |
+
"h1_total": 2000,
|
| 26 |
+
"h1_result_sha256": "b10697d6a03fb19c56e7193eb85b932c92aee2a27d4b6a088f684051dff1c01e",
|
| 27 |
+
"source_checksum_manifest_sha256": "829aa54dd4f440c10d85c6253a41891a69ba1717ace2d193cbfab3c1a7546c6e"
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
release-manifest.json
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"artifact": "nyx-35b-a3b-int4fp8",
|
| 3 |
+
"files": {
|
| 4 |
+
".gitattributes": {
|
| 5 |
+
"bytes": 448,
|
| 6 |
+
"sha256": "f3b77d221a265fd892b0993f88aa613596c2f58adf13dcee9b1d3a14156a14f8"
|
| 7 |
+
},
|
| 8 |
+
"LICENSE": {
|
| 9 |
+
"bytes": 11544,
|
| 10 |
+
"sha256": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a"
|
| 11 |
+
},
|
| 12 |
+
"NOTICE": {
|
| 13 |
+
"bytes": 459,
|
| 14 |
+
"sha256": "08640c173e604546300dd76b47c842858ffcc236942b45c6cfdd894c851d8a77"
|
| 15 |
+
},
|
| 16 |
+
"README.md": {
|
| 17 |
+
"bytes": 4989,
|
| 18 |
+
"sha256": "f8965349f25854cf6818f780b0a28151150f0b78eab15064a435e695defa0316"
|
| 19 |
+
},
|
| 20 |
+
"assets/nyx-vs-jev-benchmarks.png": {
|
| 21 |
+
"bytes": 1757593,
|
| 22 |
+
"sha256": "679a775f1f891713884549dcf6c2eca849f5b4c40064bc1ffb70a507e1add9c8"
|
| 23 |
+
},
|
| 24 |
+
"benchmarks/random-six-report.md": {
|
| 25 |
+
"bytes": 2731,
|
| 26 |
+
"sha256": "1dd4c4a7cad8a4158a4e5d9f2ddd0fa01d1f8c3df629585a1591229c8b4d9a98"
|
| 27 |
+
},
|
| 28 |
+
"benchmarks/summary.json": {
|
| 29 |
+
"bytes": 955,
|
| 30 |
+
"sha256": "e3a56d1bc57b8d1837ef9abbd5852d2d45a83cbc345b2a638f1e8e5018599a3f"
|
| 31 |
+
},
|
| 32 |
+
"chat_template.jinja": {
|
| 33 |
+
"bytes": 7756,
|
| 34 |
+
"sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715"
|
| 35 |
+
},
|
| 36 |
+
"config.json": {
|
| 37 |
+
"bytes": 2336,
|
| 38 |
+
"sha256": "27e3575d049c5a0187b783aecff914135be455439134368aba8cdcc6d678ebb7"
|
| 39 |
+
},
|
| 40 |
+
"generation_config.json": {
|
| 41 |
+
"bytes": 214,
|
| 42 |
+
"sha256": "df6f86c3fdce573ecdb55cec35f502ddda70e70abe78288a78164aef120232e7"
|
| 43 |
+
},
|
| 44 |
+
"provenance.json": {
|
| 45 |
+
"bytes": 980,
|
| 46 |
+
"sha256": "85773821fad372dd98cf10b02c4103e78406f0b45d13f2875772470a9791f1d6"
|
| 47 |
+
},
|
| 48 |
+
"runtime/entrypoint.sh": {
|
| 49 |
+
"bytes": 276,
|
| 50 |
+
"sha256": "742824765cb2f5361a476674e16a5d347abb9ae905e2798a2183121ea1ee4b07"
|
| 51 |
+
},
|
| 52 |
+
"runtime/label-token-ids.json": {
|
| 53 |
+
"bytes": 761,
|
| 54 |
+
"sha256": "8cc5610b02d5ba14358ebef59cd16f567767632fb6ea03de1565b18f1c003a40"
|
| 55 |
+
},
|
| 56 |
+
"runtime/patches/qwen35-text-quark-generated-scales.patch": {
|
| 57 |
+
"bytes": 1680,
|
| 58 |
+
"sha256": "920a8cabd8d2f6565e4f0c3296b1b0e154e48c227e4fd12f019033792cbe567f"
|
| 59 |
+
},
|
| 60 |
+
"runtime/patches/sglang-batch-result-processor.py": {
|
| 61 |
+
"bytes": 57803,
|
| 62 |
+
"sha256": "f7f58ef7f377e3df111c87d1803b6e0008bdf76726d80cbf1012275839e77da7"
|
| 63 |
+
},
|
| 64 |
+
"runtime/patches/sglang-logits-processor.py": {
|
| 65 |
+
"bytes": 54217,
|
| 66 |
+
"sha256": "aed498368a4402a84a4ef71a5962f1ff78cad265d4becbe5b3b677f1cb8ad9e2"
|
| 67 |
+
},
|
| 68 |
+
"runtime/patches/sglang-qwen35-text.py": {
|
| 69 |
+
"bytes": 12308,
|
| 70 |
+
"sha256": "52cdbda541ff612353adf5d5b04a6668954040b48cfa5209f59abbd662a023e1"
|
| 71 |
+
},
|
| 72 |
+
"runtime/patches/sglang-qwen35.py": {
|
| 73 |
+
"bytes": 106934,
|
| 74 |
+
"sha256": "f6975a9b3303002c25418b0d5b32c6bd40502ba6568ab7d5c847c6983705c01e"
|
| 75 |
+
},
|
| 76 |
+
"serve.sh": {
|
| 77 |
+
"bytes": 2065,
|
| 78 |
+
"sha256": "5f308855054cd6dd86fde051cc9771e02e1e160dc0ddbad1f5eed8e7b81a4b65"
|
| 79 |
+
},
|
| 80 |
+
"tokenizer.json": {
|
| 81 |
+
"bytes": 19989325,
|
| 82 |
+
"sha256": "06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523"
|
| 83 |
+
},
|
| 84 |
+
"tokenizer_config.json": {
|
| 85 |
+
"bytes": 1123,
|
| 86 |
+
"sha256": "bee8eba30f0eb4af73c0fe2cd06d0f89b657d7819941c438157ec42f7c80ea87"
|
| 87 |
+
},
|
| 88 |
+
"weights/TP-1-sig-e793a9432f30cd4e/READY": {
|
| 89 |
+
"bytes": 70,
|
| 90 |
+
"sha256": "b20cd5cb19a0b44c0189bb79b85ee0300f0453a31bfef7b39ac825b409cbb578"
|
| 91 |
+
},
|
| 92 |
+
"weights/TP-1-sig-e793a9432f30cd4e/checksum.json": {
|
| 93 |
+
"bytes": 359616,
|
| 94 |
+
"sha256": "829aa54dd4f440c10d85c6253a41891a69ba1717ace2d193cbfab3c1a7546c6e"
|
| 95 |
+
},
|
| 96 |
+
"weights/TP-1-sig-e793a9432f30cd4e/model-00000-common.safetensor": {
|
| 97 |
+
"bytes": 5268047400,
|
| 98 |
+
"sha256": "16e3906e9a161a6337a4a0f6d06e352d09360b37a9d4c4ea341ea978192ee289"
|
| 99 |
+
},
|
| 100 |
+
"weights/TP-1-sig-e793a9432f30cd4e/model-00001-common.safetensor": {
|
| 101 |
+
"bytes": 5120198752,
|
| 102 |
+
"sha256": "7ad7affbcc8912528ff2859568b51ee9427cc17119312ea8750cf118544ea5d3"
|
| 103 |
+
},
|
| 104 |
+
"weights/TP-1-sig-e793a9432f30cd4e/model-00002-common.safetensor": {
|
| 105 |
+
"bytes": 5254941984,
|
| 106 |
+
"sha256": "fa4a08a1e0e0de30543d343a629c6710e59575cd6f98f5beae789f34a59b79b7"
|
| 107 |
+
},
|
| 108 |
+
"weights/TP-1-sig-e793a9432f30cd4e/model-00003-common.safetensor": {
|
| 109 |
+
"bytes": 4014197624,
|
| 110 |
+
"sha256": "dfb557a70a97b91208837dac145569c844971062970f41466738e61e77d53eb3"
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"format_version": 1,
|
| 114 |
+
"repository": "devanshbatham/nyx",
|
| 115 |
+
"total_bytes": 19679761943
|
| 116 |
+
}
|
runtime/entrypoint.sh
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
target=/sgl-workspace/sglang/python/sglang/srt/models/qwen3_5_text.py
|
| 5 |
+
cp /model/runtime/patches/sglang-qwen35-text.py "$target"
|
| 6 |
+
patch --batch "$target" < /model/runtime/patches/qwen35-text-quark-generated-scales.patch
|
| 7 |
+
exec python3 "$@"
|
| 8 |
+
|
runtime/label-token-ids.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 5840, 1803, 1646, 1745, 13276, 8018, 1825, 28946, 15015, 29595, 11568, 939, 1354, 1058, 18183, 2456, 88898, 905, 1846, 802, 33869, 7839, 14006, 2860, 2926, 22828, 6844, 9798, 4738, 9265, 11261, 19278, 36513, 93801, 8335, 14544, 85266, 9110, 28000, 15137, 4525, 25261, 12717, 7116, 17078, 14497, 57339, 74909, 52072, 19305, 4887, 12607, 3580, 6281, 2036, 9362, 8533, 2080, 10911, 2925, 3040, 9690, 27731, 8023, 6901, 8702, 6211, 1123, 16307, 18990, 64045, 63037, 33380, 6151, 3392, 5449, 3967, 1113, 5095, 51923, 49600, 17099, 51483, 17756, 16037, 8135, 30237, 5683, 9992, 7444, 5751, 10284, 20887, 59884, 52396, 16103, 67547, 18535, 8006, 7263, 1425, 6878]
|
runtime/patches/qwen35-text-quark-generated-scales.patch
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- qwen3_5_text.py
|
| 2 |
+
+++ qwen3_5_text.py
|
| 3 |
+
@@ -242,17 +242,24 @@
|
| 4 |
+
loaded_params.add("lm_head.weight")
|
| 5 |
+
|
| 6 |
+
missing = set(params_dict) - loaded_params
|
| 7 |
+
- if (self.quant_config is not None
|
| 8 |
+
- and self.quant_config.get_name() == "fp8"
|
| 9 |
+
+ quant_name = (
|
| 10 |
+
+ self.quant_config.get_name() if self.quant_config is not None else None
|
| 11 |
+
+ )
|
| 12 |
+
+ if (quant_name in {"fp8", "quark_int4fp8_moe"}
|
| 13 |
+
and not self.quant_config.is_checkpoint_fp8_serialized):
|
| 14 |
+
- # Online BF16->FP8 creates these scales itself. They are deliberately
|
| 15 |
+
- # absent from the source BF16 checkpoint; all model weights still
|
| 16 |
+
- # have to load. Do not apply this exception to serialized FP8.
|
| 17 |
+
+ # Online quantizers create their scales from source weights. They are
|
| 18 |
+
+ # deliberately absent from the BF16 checkpoint; all source model
|
| 19 |
+
+ # weights still have to load. Never apply this to serialized FP8.
|
| 20 |
+
generated_suffixes = (
|
| 21 |
+
".mlp.experts.w13_weight_scale", ".mlp.experts.w2_weight_scale",
|
| 22 |
+
".mlp.experts.w13_weight_scale1", ".mlp.experts.w2_weight_scale1",
|
| 23 |
+
".attn.k_scale", ".attn.v_scale",
|
| 24 |
+
)
|
| 25 |
+
+ if quant_name == "quark_int4fp8_moe":
|
| 26 |
+
+ generated_suffixes += (
|
| 27 |
+
+ ".mlp.experts.w13_fp8_scale", ".mlp.experts.w2_fp8_scale",
|
| 28 |
+
+ ".mlp.experts.w13_int4_scale", ".mlp.experts.w2_int4_scale",
|
| 29 |
+
+ )
|
| 30 |
+
generated = {name for name in missing if name.endswith(generated_suffixes)}
|
| 31 |
+
missing -= generated
|
| 32 |
+
if missing:
|
runtime/patches/sglang-batch-result-processor.py
ADDED
|
@@ -0,0 +1,1378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import (
|
| 6 |
+
TYPE_CHECKING,
|
| 7 |
+
Callable,
|
| 8 |
+
List,
|
| 9 |
+
Optional,
|
| 10 |
+
Tuple,
|
| 11 |
+
Union,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
from sglang.srt.disaggregation.utils import DisaggregationMode
|
| 17 |
+
from sglang.srt.environ import envs
|
| 18 |
+
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
| 19 |
+
from sglang.srt.managers.schedule_batch import (
|
| 20 |
+
FINISH_ABORT,
|
| 21 |
+
FINISH_MATCHED_TOKEN,
|
| 22 |
+
Req,
|
| 23 |
+
ScheduleBatch,
|
| 24 |
+
mamba_lazy_spec_in_window,
|
| 25 |
+
)
|
| 26 |
+
from sglang.srt.mem_cache.common import (
|
| 27 |
+
maybe_cache_unfinished_req,
|
| 28 |
+
release_kv_cache,
|
| 29 |
+
)
|
| 30 |
+
from sglang.srt.model_executor.forward_batch_info import (
|
| 31 |
+
CaptureHiddenMode,
|
| 32 |
+
get_required_capture_hidden_mode,
|
| 33 |
+
get_server_return_hidden_states_mode,
|
| 34 |
+
)
|
| 35 |
+
from sglang.srt.runtime_context import (
|
| 36 |
+
get_disagg,
|
| 37 |
+
get_memory,
|
| 38 |
+
get_observability,
|
| 39 |
+
mamba_extra_buffer_lazy_enabled,
|
| 40 |
+
mamba_track_grid,
|
| 41 |
+
max_speculative_num_draft_tokens,
|
| 42 |
+
)
|
| 43 |
+
from sglang.srt.sampling.sampling_observer import CommittedTokens
|
| 44 |
+
from sglang.srt.speculative.base_spec_worker import BaseSpecWorker
|
| 45 |
+
from sglang.srt.state_capturer.indexer_topk import get_global_indexer_capturer
|
| 46 |
+
from sglang.srt.state_capturer.routed_experts import get_global_experts_capturer
|
| 47 |
+
|
| 48 |
+
if TYPE_CHECKING:
|
| 49 |
+
from sglang.srt.beam_search.coordinator import BeamCoordinator
|
| 50 |
+
from sglang.srt.configs.model_config import ModelConfig
|
| 51 |
+
from sglang.srt.disaggregation.decode_kvcache_offload_manager import (
|
| 52 |
+
DecodeKVCacheOffloadManager,
|
| 53 |
+
)
|
| 54 |
+
from sglang.srt.managers.hisparse_coordinator import HiSparseCoordinator
|
| 55 |
+
from sglang.srt.managers.scheduler_components.logprob_result_processor import (
|
| 56 |
+
SchedulerLogprobResultProcessor,
|
| 57 |
+
)
|
| 58 |
+
from sglang.srt.managers.scheduler_components.metrics_reporter import (
|
| 59 |
+
SchedulerMetricsReporter,
|
| 60 |
+
)
|
| 61 |
+
from sglang.srt.managers.scheduler_components.output_streamer import (
|
| 62 |
+
SchedulerOutputStreamer,
|
| 63 |
+
)
|
| 64 |
+
from sglang.srt.managers.tp_worker import BaseTpWorker
|
| 65 |
+
from sglang.srt.managers.utils import (
|
| 66 |
+
EmbeddingBatchResult,
|
| 67 |
+
GenerationBatchResult,
|
| 68 |
+
)
|
| 69 |
+
from sglang.srt.mem_cache.allocator import BaseTokenToKVPoolAllocator
|
| 70 |
+
from sglang.srt.mem_cache.base_prefix_cache import BasePrefixCache
|
| 71 |
+
from sglang.srt.mem_cache.memory_pool import ReqToTokenPool
|
| 72 |
+
from sglang.srt.observability.metrics_collector import SchedulerMetricsCollector
|
| 73 |
+
from sglang.srt.sampling.sampling_observer import HostAuxiliaryOutput
|
| 74 |
+
|
| 75 |
+
logger = logging.getLogger(__name__)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@dataclass(kw_only=True, slots=True, frozen=True)
|
| 79 |
+
class SchedulerBatchResultProcessor:
|
| 80 |
+
is_generation: bool
|
| 81 |
+
disaggregation_mode: DisaggregationMode
|
| 82 |
+
enable_overlap: bool
|
| 83 |
+
enable_overlap_mlx: bool
|
| 84 |
+
model_config: ModelConfig
|
| 85 |
+
token_to_kv_pool_allocator: BaseTokenToKVPoolAllocator
|
| 86 |
+
tree_cache: BasePrefixCache
|
| 87 |
+
hisparse_coordinator: Optional[HiSparseCoordinator]
|
| 88 |
+
req_to_token_pool: ReqToTokenPool
|
| 89 |
+
decode_offload_manager: Optional[DecodeKVCacheOffloadManager]
|
| 90 |
+
metrics_collector: SchedulerMetricsCollector
|
| 91 |
+
metrics_reporter: SchedulerMetricsReporter
|
| 92 |
+
draft_worker: BaseTpWorker
|
| 93 |
+
model_worker: BaseTpWorker
|
| 94 |
+
logprob_result_processor: SchedulerLogprobResultProcessor
|
| 95 |
+
output_streamer: SchedulerOutputStreamer
|
| 96 |
+
beam_coordinator: BeamCoordinator
|
| 97 |
+
abort_request: Callable
|
| 98 |
+
|
| 99 |
+
def process_batch_result_prebuilt(self, batch: ScheduleBatch):
|
| 100 |
+
assert self.disaggregation_mode == DisaggregationMode.DECODE
|
| 101 |
+
use_free_group = get_disagg().disaggregation_decode_enable_radix_cache
|
| 102 |
+
if use_free_group:
|
| 103 |
+
self.token_to_kv_pool_allocator.free_group_begin()
|
| 104 |
+
for req in batch.reqs:
|
| 105 |
+
req.time_stats.set_decode_prebuilt_finish_time()
|
| 106 |
+
req.update_finish_state()
|
| 107 |
+
if req.finished():
|
| 108 |
+
req.time_stats.set_quick_finish_time()
|
| 109 |
+
if get_memory().enable_hisparse:
|
| 110 |
+
self.hisparse_coordinator.request_finished(req)
|
| 111 |
+
release_kv_cache(req, self.tree_cache)
|
| 112 |
+
|
| 113 |
+
# Note: Logprobs should be handled on the prefill engine.
|
| 114 |
+
self.output_streamer.stream_output(batch.reqs, batch.return_logprob)
|
| 115 |
+
if use_free_group:
|
| 116 |
+
self.token_to_kv_pool_allocator.free_group_end()
|
| 117 |
+
|
| 118 |
+
def _maybe_collect_routed_experts(self, req: Req):
|
| 119 |
+
"""Collect routed experts for a finished request.
|
| 120 |
+
|
| 121 |
+
Returns immediately if `return_routed_experts` was not set on the
|
| 122 |
+
request, so non-opted-in reqs don't pay the host-gather cost.
|
| 123 |
+
|
| 124 |
+
Honors the caller's absolute start so the response covers
|
| 125 |
+
`[start_len, seqlen - 1)`. The default start_len is 0, which returns
|
| 126 |
+
the full sequence.
|
| 127 |
+
|
| 128 |
+
Logs a soft warning if the resulting tensor's row count differs from
|
| 129 |
+
the expected `seqlen - 1 - start_len`, to catch silent regressions.
|
| 130 |
+
"""
|
| 131 |
+
if not req.return_routed_experts:
|
| 132 |
+
return
|
| 133 |
+
capturer = get_global_experts_capturer()
|
| 134 |
+
if capturer is None:
|
| 135 |
+
return
|
| 136 |
+
start_len = req.routed_experts_start_len
|
| 137 |
+
seqlen = len(req.origin_input_ids) + len(req.output_ids_through_stop)
|
| 138 |
+
req.routed_experts = capturer.get_topk(
|
| 139 |
+
req_pool_idx=req.kv.req_pool_idx,
|
| 140 |
+
seqlen=seqlen,
|
| 141 |
+
req_to_token_pool=self.req_to_token_pool,
|
| 142 |
+
start_len=start_len,
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
expected_rows = max(0, seqlen - 1 - start_len)
|
| 146 |
+
if (
|
| 147 |
+
req.routed_experts is not None
|
| 148 |
+
and req.routed_experts.shape[0] != expected_rows
|
| 149 |
+
):
|
| 150 |
+
logger.warning(
|
| 151 |
+
"routed_experts row-count mismatch for req %s: got %d, expected %d "
|
| 152 |
+
"(seqlen=%d, raw_seqlen=%d, cached_tokens=%d, start_len=%s). "
|
| 153 |
+
"This indicates a silent bug.",
|
| 154 |
+
req.rid,
|
| 155 |
+
req.routed_experts.shape[0],
|
| 156 |
+
expected_rows,
|
| 157 |
+
seqlen,
|
| 158 |
+
req.seqlen,
|
| 159 |
+
req.cached_tokens,
|
| 160 |
+
req.routed_experts_start_len,
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
def _maybe_collect_indexer_topk(self, req: Req):
|
| 164 |
+
capturer = get_global_indexer_capturer()
|
| 165 |
+
if capturer is None:
|
| 166 |
+
return
|
| 167 |
+
seqlen = len(req.origin_input_ids) + len(req.output_ids_through_stop)
|
| 168 |
+
req.indexer_topk = capturer.get_topk(
|
| 169 |
+
req_pool_idx=req.kv.req_pool_idx,
|
| 170 |
+
seqlen=seqlen,
|
| 171 |
+
req_to_token_pool=self.req_to_token_pool,
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
def _maybe_collect_customized_info(
|
| 175 |
+
self,
|
| 176 |
+
i: int,
|
| 177 |
+
req: Req,
|
| 178 |
+
logits_output: LogitsProcessorOutput,
|
| 179 |
+
):
|
| 180 |
+
if logits_output is not None and logits_output.customized_info is not None:
|
| 181 |
+
if req.customized_info is None:
|
| 182 |
+
req.customized_info = {}
|
| 183 |
+
for k, v in logits_output.customized_info.items():
|
| 184 |
+
if k not in req.customized_info:
|
| 185 |
+
req.customized_info[k] = []
|
| 186 |
+
# Copy the element so it doesn't retain the entire batch
|
| 187 |
+
# tensor/array via a view reference.
|
| 188 |
+
elem = v[i]
|
| 189 |
+
if isinstance(elem, torch.Tensor):
|
| 190 |
+
elem = elem.clone()
|
| 191 |
+
elif hasattr(elem, "copy") and callable(elem.copy):
|
| 192 |
+
elem = elem.copy()
|
| 193 |
+
req.customized_info[k].append(elem)
|
| 194 |
+
|
| 195 |
+
@staticmethod
|
| 196 |
+
def _visible_output_len(req: Req) -> int:
|
| 197 |
+
return req.finished_len if req.finished_len is not None else len(req.output_ids)
|
| 198 |
+
|
| 199 |
+
@classmethod
|
| 200 |
+
def snapshot_auxiliary_output_starts(
|
| 201 |
+
cls,
|
| 202 |
+
batch: ScheduleBatch,
|
| 203 |
+
result: GenerationBatchResult,
|
| 204 |
+
) -> Optional[List[int]]:
|
| 205 |
+
if result.auxiliary_host_output is None:
|
| 206 |
+
return None
|
| 207 |
+
return [cls._visible_output_len(req) for req in batch.reqs]
|
| 208 |
+
|
| 209 |
+
@classmethod
|
| 210 |
+
def _build_auxiliary_commits(
|
| 211 |
+
cls,
|
| 212 |
+
batch: ScheduleBatch,
|
| 213 |
+
output_starts: List[int],
|
| 214 |
+
) -> List[Optional[CommittedTokens]]:
|
| 215 |
+
commits: List[Optional[CommittedTokens]] = []
|
| 216 |
+
for req, output_start in zip(batch.reqs, output_starts, strict=True):
|
| 217 |
+
output_end = cls._visible_output_len(req)
|
| 218 |
+
if output_end < output_start:
|
| 219 |
+
raise RuntimeError("committed output length moved backwards")
|
| 220 |
+
if output_end == output_start:
|
| 221 |
+
commits.append(None)
|
| 222 |
+
continue
|
| 223 |
+
commits.append(
|
| 224 |
+
CommittedTokens(
|
| 225 |
+
output_index=output_start,
|
| 226 |
+
token_ids=tuple(req.output_ids[output_start:output_end]),
|
| 227 |
+
)
|
| 228 |
+
)
|
| 229 |
+
return commits
|
| 230 |
+
|
| 231 |
+
@classmethod
|
| 232 |
+
def consume_auxiliary_output(
|
| 233 |
+
cls,
|
| 234 |
+
batch: ScheduleBatch,
|
| 235 |
+
output: HostAuxiliaryOutput,
|
| 236 |
+
output_starts: List[int],
|
| 237 |
+
) -> None:
|
| 238 |
+
output.consume(batch, cls._build_auxiliary_commits(batch, output_starts))
|
| 239 |
+
|
| 240 |
+
def process_batch_result_prefill(
|
| 241 |
+
self,
|
| 242 |
+
batch: ScheduleBatch,
|
| 243 |
+
result: Union[GenerationBatchResult, EmbeddingBatchResult],
|
| 244 |
+
):
|
| 245 |
+
skip_stream_req = None
|
| 246 |
+
self.token_to_kv_pool_allocator.free_group_begin()
|
| 247 |
+
|
| 248 |
+
if self.is_generation:
|
| 249 |
+
if result.copy_done is not None:
|
| 250 |
+
result.copy_done.synchronize()
|
| 251 |
+
auxiliary_output_starts = self.snapshot_auxiliary_output_starts(
|
| 252 |
+
batch, result
|
| 253 |
+
)
|
| 254 |
+
auxiliary_output = result.auxiliary_host_output
|
| 255 |
+
if result.routed_experts_output is not None:
|
| 256 |
+
result.routed_experts_output.finalize()
|
| 257 |
+
result.routed_experts_output = None
|
| 258 |
+
if result.indexer_topk_output is not None:
|
| 259 |
+
result.indexer_topk_output.finalize()
|
| 260 |
+
result.indexer_topk_output = None
|
| 261 |
+
|
| 262 |
+
(
|
| 263 |
+
logits_output,
|
| 264 |
+
next_token_ids,
|
| 265 |
+
extend_input_len_per_req,
|
| 266 |
+
extend_logprob_start_len_per_req,
|
| 267 |
+
) = (
|
| 268 |
+
result.logits_output,
|
| 269 |
+
result.next_token_ids,
|
| 270 |
+
result.extend_input_len_per_req,
|
| 271 |
+
result.extend_logprob_start_len_per_req,
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
# Move next_token_ids and logprobs to cpu
|
| 275 |
+
next_token_ids = next_token_ids.tolist()
|
| 276 |
+
self.move_logprobs_to_cpu(batch=batch, logits_output=logits_output)
|
| 277 |
+
|
| 278 |
+
self._validate_pp_skip_output_comm(batch, result)
|
| 279 |
+
|
| 280 |
+
hidden_state_offset = 0
|
| 281 |
+
prefill_hidden_capture_mode = self._get_prefill_hidden_capture_mode(
|
| 282 |
+
batch,
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
# Check finish conditions
|
| 286 |
+
logprob_pt = 0
|
| 287 |
+
|
| 288 |
+
for i, (req, next_token_id) in enumerate(zip(batch.reqs, next_token_ids)):
|
| 289 |
+
if (
|
| 290 |
+
batch.return_hidden_states
|
| 291 |
+
and logits_output.hidden_states is not None
|
| 292 |
+
):
|
| 293 |
+
assert extend_input_len_per_req is not None
|
| 294 |
+
hidden_state_offset = self._append_prefill_hidden_states(
|
| 295 |
+
req=req,
|
| 296 |
+
logits_output=logits_output,
|
| 297 |
+
hidden_state_offset=hidden_state_offset,
|
| 298 |
+
capture_hidden_mode=prefill_hidden_capture_mode,
|
| 299 |
+
extend_input_len=extend_input_len_per_req[i],
|
| 300 |
+
store=(
|
| 301 |
+
not req.finished()
|
| 302 |
+
and not req.is_retracted
|
| 303 |
+
and req.inflight_middle_chunks <= 0
|
| 304 |
+
),
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
if (
|
| 308 |
+
req.finished() and req.inflight_middle_chunks <= 0
|
| 309 |
+
) or req.is_retracted:
|
| 310 |
+
# Decode req in a mixed batch, or a retracted req. Keep an
|
| 311 |
+
# aborted middle chunk in the chunked branch long enough to
|
| 312 |
+
# drain its accounting without streaming it.
|
| 313 |
+
continue
|
| 314 |
+
|
| 315 |
+
if req.inflight_middle_chunks <= 0:
|
| 316 |
+
req.time_stats.set_prefill_finished_time()
|
| 317 |
+
|
| 318 |
+
if req.beam_group is not None:
|
| 319 |
+
# The relay point already replaced the sampled-token
|
| 320 |
+
# append; the group owns all finish semantics.
|
| 321 |
+
self.beam_coordinator.commit_prefill(
|
| 322 |
+
req, up_to_tick=batch.forward_iter
|
| 323 |
+
)
|
| 324 |
+
else:
|
| 325 |
+
# req output_ids are set here
|
| 326 |
+
req.output_ids.append(next_token_id)
|
| 327 |
+
|
| 328 |
+
self._maybe_update_reasoning_tokens(req, next_token_id)
|
| 329 |
+
|
| 330 |
+
req.update_finish_state()
|
| 331 |
+
# A mixed spec tail committed its pending bonus token; advance
|
| 332 |
+
# so the next spec prepare_for_decode reserves from the right base.
|
| 333 |
+
if (
|
| 334 |
+
not req.finished()
|
| 335 |
+
and batch.decoding_reqs
|
| 336 |
+
and req in batch.decoding_reqs
|
| 337 |
+
and not batch.spec_algorithm.is_none()
|
| 338 |
+
):
|
| 339 |
+
req.kv.kv_committed_len += 1
|
| 340 |
+
if req.finished():
|
| 341 |
+
self._maybe_collect_routed_experts(req)
|
| 342 |
+
self._maybe_collect_indexer_topk(req)
|
| 343 |
+
release_kv_cache(req, self.tree_cache)
|
| 344 |
+
req.time_stats.set_completion_time()
|
| 345 |
+
elif not batch.decoding_reqs or req not in batch.decoding_reqs:
|
| 346 |
+
maybe_cache_unfinished_req(req, self.tree_cache)
|
| 347 |
+
if get_memory().enable_hisparse:
|
| 348 |
+
self.hisparse_coordinator.admit_request_into_staging(req)
|
| 349 |
+
|
| 350 |
+
self._maybe_collect_customized_info(i, req, logits_output)
|
| 351 |
+
|
| 352 |
+
if batch.return_logprob:
|
| 353 |
+
logprob_pt = self._apply_prefill_logprobs(
|
| 354 |
+
req=req,
|
| 355 |
+
i=i,
|
| 356 |
+
logits_output=logits_output,
|
| 357 |
+
extend_input_len_per_req=extend_input_len_per_req,
|
| 358 |
+
extend_logprob_start_len_per_req=extend_logprob_start_len_per_req,
|
| 359 |
+
next_token_ids=next_token_ids,
|
| 360 |
+
logprob_pt=logprob_pt,
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
if req.return_sampling_mask:
|
| 364 |
+
self.add_sampling_mask_return_values(i, req, logits_output)
|
| 365 |
+
|
| 366 |
+
if req.grammar is not None:
|
| 367 |
+
self._apply_prefill_grammar(
|
| 368 |
+
req=req,
|
| 369 |
+
next_token_id=next_token_id,
|
| 370 |
+
already_advanced=result.grammar_advanced,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
else:
|
| 374 |
+
# being chunked reqs' prefill is not finished
|
| 375 |
+
req.inflight_middle_chunks -= 1
|
| 376 |
+
# There is only at most one request being currently chunked.
|
| 377 |
+
# Because this request does not finish prefill,
|
| 378 |
+
# we don't want to stream the request currently being chunked.
|
| 379 |
+
skip_stream_req = req
|
| 380 |
+
|
| 381 |
+
# Incrementally update input logprobs.
|
| 382 |
+
if batch.return_logprob:
|
| 383 |
+
logprob_pt = self._apply_chunked_prefill_logprobs(
|
| 384 |
+
req=req,
|
| 385 |
+
i=i,
|
| 386 |
+
logits_output=logits_output,
|
| 387 |
+
extend_input_len_per_req=extend_input_len_per_req,
|
| 388 |
+
extend_logprob_start_len_per_req=extend_logprob_start_len_per_req,
|
| 389 |
+
logprob_pt=logprob_pt,
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
req.time_stats.set_last_chunked_prefill_finish_time()
|
| 393 |
+
|
| 394 |
+
if auxiliary_output is not None:
|
| 395 |
+
self.consume_auxiliary_output(
|
| 396 |
+
batch,
|
| 397 |
+
auxiliary_output,
|
| 398 |
+
auxiliary_output_starts,
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
else: # embedding or reward model
|
| 402 |
+
if result.copy_done is not None:
|
| 403 |
+
result.copy_done.synchronize()
|
| 404 |
+
|
| 405 |
+
embeddings = self._convert_embeddings(result=result)
|
| 406 |
+
phs = result.pooled_hidden_states
|
| 407 |
+
|
| 408 |
+
if phs is not None:
|
| 409 |
+
if isinstance(phs, list):
|
| 410 |
+
phs = [t.cpu().detach() for t in phs]
|
| 411 |
+
else:
|
| 412 |
+
phs = phs.cpu().detach()
|
| 413 |
+
|
| 414 |
+
# Check finish conditions
|
| 415 |
+
for i, req in enumerate(batch.reqs):
|
| 416 |
+
if req.is_retracted:
|
| 417 |
+
continue
|
| 418 |
+
|
| 419 |
+
req.embedding = embeddings[i]
|
| 420 |
+
if req.return_pooled_hidden_states and phs is not None:
|
| 421 |
+
req.pooled_hidden_state = phs[i]
|
| 422 |
+
if req.inflight_middle_chunks <= 0:
|
| 423 |
+
req.time_stats.set_prefill_finished_time()
|
| 424 |
+
# Dummy output token for embedding models
|
| 425 |
+
req.output_ids.append(0)
|
| 426 |
+
req.update_finish_state()
|
| 427 |
+
|
| 428 |
+
if req.finished():
|
| 429 |
+
release_kv_cache(req, self.tree_cache)
|
| 430 |
+
req.time_stats.set_completion_time()
|
| 431 |
+
else:
|
| 432 |
+
maybe_cache_unfinished_req(req, self.tree_cache)
|
| 433 |
+
else:
|
| 434 |
+
# being chunked reqs' prefill is not finished
|
| 435 |
+
req.inflight_middle_chunks -= 1
|
| 436 |
+
req.time_stats.set_last_chunked_prefill_finish_time()
|
| 437 |
+
|
| 438 |
+
self.token_to_kv_pool_allocator.free_group_end()
|
| 439 |
+
self.output_streamer.stream_output(
|
| 440 |
+
batch.reqs, batch.return_logprob, skip_stream_req
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
can_run_cuda_graph = result.can_run_cuda_graph
|
| 444 |
+
# None on decode->extend converted batches; they are decode work and
|
| 445 |
+
# have no prefill stats to report.
|
| 446 |
+
if batch.prefill_stats is not None:
|
| 447 |
+
self.metrics_reporter.report_prefill_stats(
|
| 448 |
+
batch=batch,
|
| 449 |
+
prefill_stats=batch.prefill_stats,
|
| 450 |
+
can_run_cuda_graph=can_run_cuda_graph,
|
| 451 |
+
dp_cooperation_info=batch.dp_cooperation_info,
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
def _convert_embeddings(self, *, result: EmbeddingBatchResult) -> list:
|
| 455 |
+
is_sparse = envs.SGLANG_EMBEDDINGS_SPARSE_HEAD.is_set()
|
| 456 |
+
|
| 457 |
+
embeddings = result.embeddings
|
| 458 |
+
|
| 459 |
+
if is_sparse:
|
| 460 |
+
batch_ids, token_ids = embeddings.indices()
|
| 461 |
+
values = embeddings.values()
|
| 462 |
+
|
| 463 |
+
embeddings = [{} for _ in range(embeddings.size(0))]
|
| 464 |
+
for i in range(batch_ids.shape[0]):
|
| 465 |
+
embeddings[batch_ids[i].item()][token_ids[i].item()] = values[i].item()
|
| 466 |
+
else:
|
| 467 |
+
if isinstance(embeddings, torch.Tensor):
|
| 468 |
+
embeddings = embeddings.tolist()
|
| 469 |
+
else:
|
| 470 |
+
embeddings = [tensor.tolist() for tensor in embeddings]
|
| 471 |
+
return embeddings
|
| 472 |
+
|
| 473 |
+
def move_logprobs_to_cpu(
|
| 474 |
+
self,
|
| 475 |
+
*,
|
| 476 |
+
batch: ScheduleBatch,
|
| 477 |
+
logits_output: LogitsProcessorOutput,
|
| 478 |
+
) -> None:
|
| 479 |
+
if batch.return_logprob:
|
| 480 |
+
if logits_output.next_token_logprobs is not None:
|
| 481 |
+
logits_output.next_token_logprobs = (
|
| 482 |
+
logits_output.next_token_logprobs.tolist()
|
| 483 |
+
)
|
| 484 |
+
if logits_output.input_token_logprobs is not None:
|
| 485 |
+
logits_output.input_token_logprobs = tuple(
|
| 486 |
+
logits_output.input_token_logprobs.tolist()
|
| 487 |
+
)
|
| 488 |
+
if logits_output.next_token_top_logprobs_val:
|
| 489 |
+
logits_output.next_token_top_logprobs_val = [
|
| 490 |
+
v.tolist() for v in logits_output.next_token_top_logprobs_val
|
| 491 |
+
]
|
| 492 |
+
logits_output.next_token_top_logprobs_idx = [
|
| 493 |
+
x.tolist() for x in logits_output.next_token_top_logprobs_idx
|
| 494 |
+
]
|
| 495 |
+
if logits_output.next_token_token_ids_logprobs_val:
|
| 496 |
+
logits_output.next_token_token_ids_logprobs_val = [
|
| 497 |
+
v.tolist() if isinstance(v, torch.Tensor) else v
|
| 498 |
+
for v in logits_output.next_token_token_ids_logprobs_val
|
| 499 |
+
]
|
| 500 |
+
|
| 501 |
+
def _apply_prefill_logprobs(
|
| 502 |
+
self,
|
| 503 |
+
*,
|
| 504 |
+
req: Req,
|
| 505 |
+
i: int,
|
| 506 |
+
logits_output: LogitsProcessorOutput,
|
| 507 |
+
extend_input_len_per_req: Optional[List[int]],
|
| 508 |
+
extend_logprob_start_len_per_req: Optional[List[int]],
|
| 509 |
+
next_token_ids: List[int],
|
| 510 |
+
logprob_pt: int,
|
| 511 |
+
) -> int:
|
| 512 |
+
assert extend_logprob_start_len_per_req is not None
|
| 513 |
+
assert extend_input_len_per_req is not None
|
| 514 |
+
extend_logprob_start_len = extend_logprob_start_len_per_req[i]
|
| 515 |
+
extend_input_len = extend_input_len_per_req[i]
|
| 516 |
+
|
| 517 |
+
num_input_logprobs = self.logprob_result_processor.calculate_num_input_logprobs(
|
| 518 |
+
req,
|
| 519 |
+
extend_input_len,
|
| 520 |
+
extend_logprob_start_len,
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
if req.return_logprob:
|
| 524 |
+
self.logprob_result_processor.add_logprob_return_values(
|
| 525 |
+
i,
|
| 526 |
+
req,
|
| 527 |
+
logprob_pt,
|
| 528 |
+
next_token_ids,
|
| 529 |
+
num_input_logprobs,
|
| 530 |
+
logits_output,
|
| 531 |
+
)
|
| 532 |
+
logprob_pt += num_input_logprobs
|
| 533 |
+
return logprob_pt
|
| 534 |
+
|
| 535 |
+
@staticmethod
|
| 536 |
+
def _validate_pp_skip_output_comm(
|
| 537 |
+
batch: ScheduleBatch,
|
| 538 |
+
result: Union[GenerationBatchResult, EmbeddingBatchResult],
|
| 539 |
+
):
|
| 540 |
+
"""Validate PP skip output comm correctness.
|
| 541 |
+
|
| 542 |
+
- When skip=True: all reqs must be middle chunks (inflight_middle_chunks > 0)
|
| 543 |
+
so placeholder zeros are never consumed via req.output_ids.append().
|
| 544 |
+
- When skip=False: at least one req should consume next_token_ids
|
| 545 |
+
(inflight_middle_chunks <= 0), otherwise warn.
|
| 546 |
+
"""
|
| 547 |
+
if not envs.SGLANG_PP_SKIP_PURE_CHUNKED_OUTPUT_COMM.get():
|
| 548 |
+
return
|
| 549 |
+
|
| 550 |
+
if not getattr(result, "skipped_output_comm", False):
|
| 551 |
+
if batch.forward_mode.is_extend() and not batch.forward_mode.is_prebuilt():
|
| 552 |
+
has_consumed_output = any(
|
| 553 |
+
req.inflight_middle_chunks <= 0
|
| 554 |
+
for req in batch.reqs
|
| 555 |
+
if not req.finished() and not req.is_retracted
|
| 556 |
+
)
|
| 557 |
+
if not has_consumed_output and len(batch.reqs) > 0:
|
| 558 |
+
chunks = list([r.inflight_middle_chunks for r in batch.reqs])
|
| 559 |
+
logger.warning(
|
| 560 |
+
f"PP non-skip output comm: no req consumed next_token_ids. "
|
| 561 |
+
f"contains_last_prefill_chunk={batch.contains_last_prefill_chunk}, "
|
| 562 |
+
f"num_reqs={len(batch.reqs)}, all inflight_middle_chunks={chunks}"
|
| 563 |
+
)
|
| 564 |
+
return
|
| 565 |
+
|
| 566 |
+
for req in batch.reqs:
|
| 567 |
+
if not req.finished() and not req.is_retracted:
|
| 568 |
+
assert req.inflight_middle_chunks > 0, (
|
| 569 |
+
f"PP skip output comm invariant violated: req {req.rid} "
|
| 570 |
+
f"has inflight_middle_chunks={req.inflight_middle_chunks} "
|
| 571 |
+
f"but output was skipped (contains_last_prefill_chunk="
|
| 572 |
+
f"{batch.contains_last_prefill_chunk}). "
|
| 573 |
+
f"Placeholder zeros would be appended to output_ids."
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
def _append_prefill_hidden_states(
|
| 577 |
+
self,
|
| 578 |
+
*,
|
| 579 |
+
req: Req,
|
| 580 |
+
logits_output: LogitsProcessorOutput,
|
| 581 |
+
hidden_state_offset: int,
|
| 582 |
+
capture_hidden_mode: CaptureHiddenMode,
|
| 583 |
+
extend_input_len: int,
|
| 584 |
+
store: bool = True,
|
| 585 |
+
) -> int:
|
| 586 |
+
if capture_hidden_mode.is_full():
|
| 587 |
+
start = hidden_state_offset
|
| 588 |
+
hidden_state_offset += extend_input_len
|
| 589 |
+
if not store or not req.return_hidden_states:
|
| 590 |
+
return hidden_state_offset
|
| 591 |
+
|
| 592 |
+
req_hidden_states = logits_output.hidden_states[start:hidden_state_offset]
|
| 593 |
+
if req.return_hidden_states is True:
|
| 594 |
+
req.hidden_states.append(req_hidden_states.cpu().clone().tolist())
|
| 595 |
+
elif req.return_hidden_states == "last":
|
| 596 |
+
req.hidden_states.append(req_hidden_states[-1].cpu().tolist())
|
| 597 |
+
elif capture_hidden_mode.is_last():
|
| 598 |
+
index = hidden_state_offset
|
| 599 |
+
hidden_state_offset += 1
|
| 600 |
+
if store and req.return_hidden_states:
|
| 601 |
+
req.hidden_states.append(
|
| 602 |
+
logits_output.hidden_states[index].cpu().tolist()
|
| 603 |
+
)
|
| 604 |
+
else:
|
| 605 |
+
raise ValueError(
|
| 606 |
+
f"Unexpected hidden states capture mode: {capture_hidden_mode}"
|
| 607 |
+
)
|
| 608 |
+
return hidden_state_offset
|
| 609 |
+
|
| 610 |
+
@staticmethod
|
| 611 |
+
def _append_decode_hidden_states(
|
| 612 |
+
*,
|
| 613 |
+
req: Req,
|
| 614 |
+
hidden_states: torch.Tensor,
|
| 615 |
+
start: int,
|
| 616 |
+
accept_len: int,
|
| 617 |
+
) -> None:
|
| 618 |
+
if accept_len <= 0:
|
| 619 |
+
return
|
| 620 |
+
|
| 621 |
+
if req.return_hidden_states == "last":
|
| 622 |
+
valid_accept_len = accept_len
|
| 623 |
+
if req.finished_len is not None:
|
| 624 |
+
step_start = len(req.output_ids) - accept_len
|
| 625 |
+
valid_accept_len = max(
|
| 626 |
+
0,
|
| 627 |
+
min(accept_len, req.finished_len - step_start),
|
| 628 |
+
)
|
| 629 |
+
if valid_accept_len > 0:
|
| 630 |
+
req.hidden_states[:] = [
|
| 631 |
+
hidden_states[start + valid_accept_len - 1].cpu().tolist()
|
| 632 |
+
]
|
| 633 |
+
else:
|
| 634 |
+
req.hidden_states.extend(
|
| 635 |
+
hidden_states[start : start + accept_len].cpu().tolist()
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
+
@staticmethod
|
| 639 |
+
def _get_prefill_hidden_capture_mode(batch: ScheduleBatch) -> CaptureHiddenMode:
|
| 640 |
+
return get_required_capture_hidden_mode(
|
| 641 |
+
max(
|
| 642 |
+
batch.return_hidden_states_mode,
|
| 643 |
+
get_server_return_hidden_states_mode(),
|
| 644 |
+
),
|
| 645 |
+
batch.spec_info,
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
def _apply_prefill_grammar(
|
| 649 |
+
self, *, req: Req, next_token_id: int, already_advanced: bool = False
|
| 650 |
+
) -> None:
|
| 651 |
+
# The grammar barrier may have already advanced the FSM over this prefilled
|
| 652 |
+
# token (spec overlap path); only advance if not, but always sync
|
| 653 |
+
# grammar.finished.
|
| 654 |
+
if not already_advanced:
|
| 655 |
+
# FIXME: this try-except block is for handling unexpected xgrammar issue.
|
| 656 |
+
try:
|
| 657 |
+
req.grammar.accept_token(next_token_id)
|
| 658 |
+
except ValueError as e:
|
| 659 |
+
# Grammar accept_token can raise ValueError if the token is not in the
|
| 660 |
+
# grammar. This can happen if the grammar is not set correctly or the
|
| 661 |
+
# token is invalid.
|
| 662 |
+
logger.error(
|
| 663 |
+
f"Grammar accept_token failed for req {req.rid} with token {next_token_id}: {e}"
|
| 664 |
+
)
|
| 665 |
+
req.to_finish = FINISH_ABORT()
|
| 666 |
+
req.grammar.finished = req.finished()
|
| 667 |
+
|
| 668 |
+
def _apply_chunked_prefill_logprobs(
|
| 669 |
+
self,
|
| 670 |
+
*,
|
| 671 |
+
req: Req,
|
| 672 |
+
i: int,
|
| 673 |
+
logits_output: LogitsProcessorOutput,
|
| 674 |
+
extend_input_len_per_req: Optional[List[int]],
|
| 675 |
+
extend_logprob_start_len_per_req: Optional[List[int]],
|
| 676 |
+
logprob_pt: int,
|
| 677 |
+
) -> int:
|
| 678 |
+
extend_logprob_start_len = extend_logprob_start_len_per_req[i]
|
| 679 |
+
extend_input_len = extend_input_len_per_req[i]
|
| 680 |
+
if extend_logprob_start_len < extend_input_len:
|
| 681 |
+
# Update input logprobs.
|
| 682 |
+
num_input_logprobs = (
|
| 683 |
+
self.logprob_result_processor.calculate_num_input_logprobs(
|
| 684 |
+
req,
|
| 685 |
+
extend_input_len,
|
| 686 |
+
extend_logprob_start_len,
|
| 687 |
+
)
|
| 688 |
+
)
|
| 689 |
+
if req.return_logprob:
|
| 690 |
+
self.logprob_result_processor.add_input_logprob_return_values(
|
| 691 |
+
i,
|
| 692 |
+
req,
|
| 693 |
+
logits_output,
|
| 694 |
+
logprob_pt,
|
| 695 |
+
num_input_logprobs,
|
| 696 |
+
last_prefill_chunk=False,
|
| 697 |
+
)
|
| 698 |
+
logprob_pt += num_input_logprobs
|
| 699 |
+
return logprob_pt
|
| 700 |
+
|
| 701 |
+
def _resolve_spec_v2_tokens(
|
| 702 |
+
self,
|
| 703 |
+
result: GenerationBatchResult,
|
| 704 |
+
batch: ScheduleBatch,
|
| 705 |
+
) -> List[List[int]]:
|
| 706 |
+
"""Resolve the padded next token ids for spec-v2 (overlap and non-overlap)."""
|
| 707 |
+
assert result.next_token_ids.is_cpu
|
| 708 |
+
assert result.accept_lens.is_cpu
|
| 709 |
+
|
| 710 |
+
next_token_ids = result.next_token_ids.tolist()
|
| 711 |
+
accept_lens = result.accept_lens.tolist()
|
| 712 |
+
result.num_correct_drafts = sum(accept_lens) - len(batch.reqs)
|
| 713 |
+
result.num_correct_drafts_per_req_cpu = [x - 1 for x in accept_lens]
|
| 714 |
+
|
| 715 |
+
block_accept_lens = (
|
| 716 |
+
result.block_accept_lens.tolist()
|
| 717 |
+
if result.block_accept_lens is not None
|
| 718 |
+
else None
|
| 719 |
+
)
|
| 720 |
+
result.num_block_accept_tokens = (
|
| 721 |
+
sum(block_accept_lens) if block_accept_lens else 0
|
| 722 |
+
)
|
| 723 |
+
cap_lens = result.cap_lens.tolist() if result.cap_lens is not None else None
|
| 724 |
+
result.num_cap_tokens = sum(cap_lens) if cap_lens else 0
|
| 725 |
+
|
| 726 |
+
# Feed the adaptive controller now that accept_lens is on CPU,
|
| 727 |
+
# instead of doing a synchronous GPU→CPU copy in the worker hot path.
|
| 728 |
+
# BaseSpecWorker provides a no-op default for non-adaptive workers.
|
| 729 |
+
self.model_worker.on_verify_complete_cpu(
|
| 730 |
+
result.num_correct_drafts_per_req_cpu, batch_size=len(batch.reqs)
|
| 731 |
+
)
|
| 732 |
+
|
| 733 |
+
# Advance the grammar FSM over this batch's committed tokens (idempotent):
|
| 734 |
+
# the EAGLE overlap path already did this inside verify() via the grammar
|
| 735 |
+
# barrier; otherwise advance now. advance_grammar_fsm self-gates on per-req
|
| 736 |
+
# grammar (the queued batch.copy() does not carry has_grammar) and consumes
|
| 737 |
+
# result.grammar_retained_tokens below instead of re-advancing.
|
| 738 |
+
self.advance_grammar_fsm(result, batch)
|
| 739 |
+
|
| 740 |
+
predict_tokens = []
|
| 741 |
+
# In adaptive spec-v2, the worker state may already have switched when this
|
| 742 |
+
# delayed result is processed. Use the draft token count recorded on result.
|
| 743 |
+
stride = result.speculative_num_draft_tokens
|
| 744 |
+
assert stride is not None, "spec-v2 result missing speculative_num_draft_tokens"
|
| 745 |
+
|
| 746 |
+
for i, req in enumerate(batch.reqs):
|
| 747 |
+
accept_tokens = next_token_ids[i * stride : i * stride + accept_lens[i]]
|
| 748 |
+
|
| 749 |
+
if req.is_retracted or req.finished():
|
| 750 |
+
# Nothing to settle: no worker pre-claims the bonus, so
|
| 751 |
+
# kv_committed_len already holds the committed prefix.
|
| 752 |
+
pass
|
| 753 |
+
else:
|
| 754 |
+
if req.grammar is not None:
|
| 755 |
+
# FSM already advanced + truncated by advance_grammar_fsm; reuse
|
| 756 |
+
# the retained (grammar-legal) run instead of advancing again.
|
| 757 |
+
accept_tokens = result.grammar_retained_tokens[i]
|
| 758 |
+
|
| 759 |
+
# Commit the full accepted run (drafts + bonus).
|
| 760 |
+
num_accept_tokens = len(accept_tokens)
|
| 761 |
+
req.kv.kv_committed_len += num_accept_tokens
|
| 762 |
+
req.spec_verify_ct += 1
|
| 763 |
+
|
| 764 |
+
num_correct_drafts = result.num_correct_drafts_per_req_cpu[i]
|
| 765 |
+
req.spec_num_correct_drafts += num_correct_drafts
|
| 766 |
+
req.update_spec_correct_drafts_histogram(num_correct_drafts)
|
| 767 |
+
|
| 768 |
+
if block_accept_lens is not None:
|
| 769 |
+
req.spec_num_block_accept_tokens += block_accept_lens[i]
|
| 770 |
+
if cap_lens is not None:
|
| 771 |
+
req.spec_num_cap_tokens += cap_lens[i]
|
| 772 |
+
req.update_spec_cap_lens_histogram(cap_lens[i])
|
| 773 |
+
|
| 774 |
+
predict_tokens.append(accept_tokens)
|
| 775 |
+
|
| 776 |
+
return predict_tokens
|
| 777 |
+
|
| 778 |
+
def _accept_grammar_tokens(
|
| 779 |
+
self, req: Req, tokens: Union[int, List[int]]
|
| 780 |
+
) -> List[int]:
|
| 781 |
+
"""Advance the grammar over the accepted token(s), stopping at the token
|
| 782 |
+
that terminates it.
|
| 783 |
+
|
| 784 |
+
``tokens`` is a single sampled token (normal decode) or the whole
|
| 785 |
+
verified run (spec decode). Returns the retained prefix; for spec the
|
| 786 |
+
suffix past grammar completion is dropped so it is never committed to KV
|
| 787 |
+
nor emitted. Advances the grammar FSM only -- ``grammar.finished`` is
|
| 788 |
+
synced by the caller once the finish state is updated.
|
| 789 |
+
"""
|
| 790 |
+
if isinstance(tokens, int):
|
| 791 |
+
tokens = [tokens]
|
| 792 |
+
retained = []
|
| 793 |
+
try:
|
| 794 |
+
for token_id in tokens:
|
| 795 |
+
req.grammar.accept_token(token_id)
|
| 796 |
+
retained.append(token_id)
|
| 797 |
+
if req.grammar.is_terminated():
|
| 798 |
+
break
|
| 799 |
+
except ValueError as e:
|
| 800 |
+
# accept_token raises ValueError if the token is not in the grammar
|
| 801 |
+
# (misconfigured grammar or invalid token); abort the request.
|
| 802 |
+
logger.error(
|
| 803 |
+
f"Grammar accept_token failed for req {req.rid} with token "
|
| 804 |
+
f"{tokens}: {e}"
|
| 805 |
+
)
|
| 806 |
+
req.to_finish = FINISH_ABORT()
|
| 807 |
+
return retained
|
| 808 |
+
|
| 809 |
+
def advance_grammar_fsm(
|
| 810 |
+
self, result: GenerationBatchResult, batch: ScheduleBatch
|
| 811 |
+
) -> None:
|
| 812 |
+
"""Advance each req's grammar FSM over the tokens THIS batch committed, and
|
| 813 |
+
(for decode) memoize the grammar-truncated run on ``result``.
|
| 814 |
+
|
| 815 |
+
This is the single place the spec-v2 FSM advances. It is idempotent
|
| 816 |
+
(``result.grammar_advanced``) so it runs either eagerly — inside ``verify(N)``
|
| 817 |
+
via the scheduler's grammar barrier, so the advance overlaps the target-verify
|
| 818 |
+
forward — or lazily from the result processors on the non-overlap / non-EAGLE
|
| 819 |
+
paths. It handles both decode (the accepted spec run) and extend (the single
|
| 820 |
+
prefilled token) results, so the barrier can resolve whatever the previous
|
| 821 |
+
batch was — e.g. the extend->decode boundary.
|
| 822 |
+
"""
|
| 823 |
+
if result.grammar_advanced or not batch.has_grammar:
|
| 824 |
+
return
|
| 825 |
+
is_decode = batch.forward_mode.is_decode()
|
| 826 |
+
if not (is_decode or batch.forward_mode.is_extend()):
|
| 827 |
+
return
|
| 828 |
+
if result.copy_done is not None:
|
| 829 |
+
result.copy_done.synchronize()
|
| 830 |
+
next_token_ids = result.next_token_ids.tolist()
|
| 831 |
+
|
| 832 |
+
if not is_decode:
|
| 833 |
+
# Extend: advance over the single token each completed-prefill req emitted
|
| 834 |
+
# (mirrors process_batch_result_prefill's per-req token indexing).
|
| 835 |
+
for i, req in enumerate(batch.reqs):
|
| 836 |
+
if (
|
| 837 |
+
req.grammar is None
|
| 838 |
+
or req.is_retracted
|
| 839 |
+
or req.finished()
|
| 840 |
+
or req.inflight_middle_chunks > 0
|
| 841 |
+
):
|
| 842 |
+
continue
|
| 843 |
+
self._accept_grammar_tokens(req, next_token_ids[i])
|
| 844 |
+
result.grammar_advanced = True
|
| 845 |
+
return
|
| 846 |
+
|
| 847 |
+
# Decode: only the spec-v2 path reaches here (the grammar barrier for
|
| 848 |
+
# spec-overlap workers and _resolve_spec_v2_tokens). Non-spec grammar decode
|
| 849 |
+
# advances its FSM in process_batch_result_decode and has no accept_lens, so
|
| 850 |
+
# bail out defensively.
|
| 851 |
+
if result.accept_lens is None:
|
| 852 |
+
return
|
| 853 |
+
accept_lens = result.accept_lens.tolist()
|
| 854 |
+
stride = result.speculative_num_draft_tokens
|
| 855 |
+
assert stride is not None, "spec-v2 result missing speculative_num_draft_tokens"
|
| 856 |
+
retained = [None] * len(batch.reqs)
|
| 857 |
+
for i, req in enumerate(batch.reqs):
|
| 858 |
+
if req.grammar is None or req.is_retracted or req.finished():
|
| 859 |
+
continue
|
| 860 |
+
accept_tokens = next_token_ids[i * stride : i * stride + accept_lens[i]]
|
| 861 |
+
# Stop accepting once the grammar terminates so the over-drafted suffix
|
| 862 |
+
# is never committed to KV nor emitted; this advances the FSM.
|
| 863 |
+
retained[i] = self._accept_grammar_tokens(req, accept_tokens)
|
| 864 |
+
result.grammar_retained_tokens = retained
|
| 865 |
+
result.grammar_advanced = True
|
| 866 |
+
|
| 867 |
+
def process_batch_result_idle(
|
| 868 |
+
self,
|
| 869 |
+
batch: ScheduleBatch,
|
| 870 |
+
result: GenerationBatchResult,
|
| 871 |
+
):
|
| 872 |
+
if result.copy_done is not None:
|
| 873 |
+
result.copy_done.synchronize()
|
| 874 |
+
|
| 875 |
+
self.output_streamer._stream_output_generation(
|
| 876 |
+
batch.reqs, batch.return_logprob, is_idle_batch=True
|
| 877 |
+
)
|
| 878 |
+
|
| 879 |
+
def process_batch_result_decode(
|
| 880 |
+
self,
|
| 881 |
+
batch: ScheduleBatch,
|
| 882 |
+
result: GenerationBatchResult,
|
| 883 |
+
):
|
| 884 |
+
if result.copy_done is not None:
|
| 885 |
+
result.copy_done.synchronize()
|
| 886 |
+
auxiliary_output_starts = self.snapshot_auxiliary_output_starts(batch, result)
|
| 887 |
+
auxiliary_output = result.auxiliary_host_output
|
| 888 |
+
if result.routed_experts_output is not None:
|
| 889 |
+
result.routed_experts_output.finalize()
|
| 890 |
+
result.routed_experts_output = None
|
| 891 |
+
if result.indexer_topk_output is not None:
|
| 892 |
+
result.indexer_topk_output.finalize()
|
| 893 |
+
result.indexer_topk_output = None
|
| 894 |
+
|
| 895 |
+
logits_output, next_token_ids, can_run_cuda_graph = (
|
| 896 |
+
result.logits_output,
|
| 897 |
+
result.next_token_ids,
|
| 898 |
+
result.can_run_cuda_graph,
|
| 899 |
+
)
|
| 900 |
+
|
| 901 |
+
next_token_ids, next_token_logprobs = self._normalize_decode_outputs(
|
| 902 |
+
batch=batch,
|
| 903 |
+
result=result,
|
| 904 |
+
logits_output=logits_output,
|
| 905 |
+
next_token_ids=next_token_ids,
|
| 906 |
+
)
|
| 907 |
+
|
| 908 |
+
self.metrics_reporter.num_generated_tokens += len(batch.reqs)
|
| 909 |
+
if not batch.spec_algorithm.is_none():
|
| 910 |
+
self.metrics_reporter.update_spec_metrics(
|
| 911 |
+
batch.batch_size(),
|
| 912 |
+
result.num_correct_drafts,
|
| 913 |
+
num_block_accept_tokens=result.num_block_accept_tokens,
|
| 914 |
+
num_cap_tokens=result.num_cap_tokens,
|
| 915 |
+
)
|
| 916 |
+
if get_observability().enable_metrics:
|
| 917 |
+
self.metrics_collector.increment_decode_cuda_graph_pass(
|
| 918 |
+
value=can_run_cuda_graph
|
| 919 |
+
)
|
| 920 |
+
|
| 921 |
+
self.token_to_kv_pool_allocator.free_group_begin()
|
| 922 |
+
|
| 923 |
+
# Folds the relay point's selection into the DAG and sets the finish
|
| 924 |
+
# states the loop below observes. Beam + spec is rejected at admission.
|
| 925 |
+
newly_finished_beam_groups = set()
|
| 926 |
+
if batch.spec_algorithm.is_none() and logits_output is not None:
|
| 927 |
+
newly_finished_beam_groups = self.beam_coordinator.commit_decode(batch)
|
| 928 |
+
|
| 929 |
+
for i, req in enumerate(batch.reqs):
|
| 930 |
+
req: Req
|
| 931 |
+
|
| 932 |
+
if req.beam_group is not None:
|
| 933 |
+
# Under overlap a finished row reappears for one overshoot tick;
|
| 934 |
+
# gate on the committing tick so this runs exactly once.
|
| 935 |
+
if req.finished() and (
|
| 936 |
+
id(req.beam_group) not in newly_finished_beam_groups
|
| 937 |
+
):
|
| 938 |
+
continue
|
| 939 |
+
req.time_stats.set_last_decode_finish_time()
|
| 940 |
+
self._handle_finish_state_updated_req(
|
| 941 |
+
req, batch, result, i, logits_output
|
| 942 |
+
)
|
| 943 |
+
continue
|
| 944 |
+
|
| 945 |
+
if (self.enable_overlap or self.enable_overlap_mlx) and (
|
| 946 |
+
req.finished() or req.is_retracted
|
| 947 |
+
):
|
| 948 |
+
# NOTE: This (req.finished() or req.is_retracted) should only happen when overlap scheduling is enabled.
|
| 949 |
+
# And all the over-allocated tokens will be freed in `release_kv_cache`.
|
| 950 |
+
continue
|
| 951 |
+
|
| 952 |
+
# next_token_id is a per-req list: 1 token for non-spec, the verified
|
| 953 |
+
# run for spec (already grammar-truncated in _resolve_spec_v2_tokens).
|
| 954 |
+
next_token_id = next_token_ids[i]
|
| 955 |
+
is_spec = not batch.spec_algorithm.is_none()
|
| 956 |
+
|
| 957 |
+
req.output_ids.extend(next_token_id)
|
| 958 |
+
new_accept_len = len(next_token_id)
|
| 959 |
+
|
| 960 |
+
self._maybe_update_reasoning_tokens(req, next_token_id)
|
| 961 |
+
req.time_stats.set_last_decode_finish_time()
|
| 962 |
+
req.update_finish_state(new_accept_len)
|
| 963 |
+
|
| 964 |
+
self._handle_finish_state_updated_req(req, batch, result, i, logits_output)
|
| 965 |
+
|
| 966 |
+
if req.return_logprob:
|
| 967 |
+
self._apply_decode_logprobs(
|
| 968 |
+
req=req,
|
| 969 |
+
i=i,
|
| 970 |
+
batch=batch,
|
| 971 |
+
next_token_id=next_token_id,
|
| 972 |
+
next_token_logprobs=next_token_logprobs,
|
| 973 |
+
logits_output=logits_output,
|
| 974 |
+
)
|
| 975 |
+
|
| 976 |
+
if req.return_sampling_mask:
|
| 977 |
+
# return_sampling_mask + speculative decoding is rejected at
|
| 978 |
+
# request entry, so this remains one support mask per token.
|
| 979 |
+
self.add_sampling_mask_return_values(i, req, logits_output)
|
| 980 |
+
|
| 981 |
+
if req.return_hidden_states and logits_output.hidden_states is not None:
|
| 982 |
+
# hidden_states is [bs * stride, hidden_dim], one row per emitted
|
| 983 |
+
# token; stride = speculative_num_draft_tokens for spec, 1 for non-spec.
|
| 984 |
+
stride = result.speculative_num_draft_tokens or 1
|
| 985 |
+
accept_len = len(next_token_id)
|
| 986 |
+
start = i * stride
|
| 987 |
+
self._append_decode_hidden_states(
|
| 988 |
+
req=req,
|
| 989 |
+
hidden_states=logits_output.hidden_states,
|
| 990 |
+
start=start,
|
| 991 |
+
accept_len=accept_len,
|
| 992 |
+
)
|
| 993 |
+
|
| 994 |
+
if req.grammar is not None:
|
| 995 |
+
if not is_spec:
|
| 996 |
+
# Normal decode advances the grammar for its single token
|
| 997 |
+
# here; spec already advanced it in _resolve_spec_v2_tokens.
|
| 998 |
+
self._accept_grammar_tokens(req, next_token_id)
|
| 999 |
+
req.grammar.finished = req.finished()
|
| 1000 |
+
|
| 1001 |
+
if auxiliary_output is not None:
|
| 1002 |
+
self.consume_auxiliary_output(
|
| 1003 |
+
batch,
|
| 1004 |
+
auxiliary_output,
|
| 1005 |
+
auxiliary_output_starts,
|
| 1006 |
+
)
|
| 1007 |
+
|
| 1008 |
+
self.output_streamer.stream_output(batch.reqs, batch.return_logprob)
|
| 1009 |
+
self.token_to_kv_pool_allocator.free_group_end()
|
| 1010 |
+
|
| 1011 |
+
self.metrics_reporter.forward_ct_decode = (
|
| 1012 |
+
self.metrics_reporter.forward_ct_decode + 1
|
| 1013 |
+
) % (1 << 30)
|
| 1014 |
+
self.metrics_reporter.report_decode_stats(
|
| 1015 |
+
can_run_cuda_graph,
|
| 1016 |
+
running_batch=batch,
|
| 1017 |
+
num_correct_drafts=result.num_correct_drafts,
|
| 1018 |
+
)
|
| 1019 |
+
|
| 1020 |
+
def _normalize_decode_outputs(
|
| 1021 |
+
self,
|
| 1022 |
+
*,
|
| 1023 |
+
batch: ScheduleBatch,
|
| 1024 |
+
result: GenerationBatchResult,
|
| 1025 |
+
logits_output: LogitsProcessorOutput,
|
| 1026 |
+
next_token_ids: Union[torch.Tensor, List[int]],
|
| 1027 |
+
) -> Tuple[Union[List[int], List[List[int]]], Optional[List[float]]]:
|
| 1028 |
+
next_token_logprobs = None
|
| 1029 |
+
# Normalize to a uniform per-req list of accepted tokens (List[List[int]]):
|
| 1030 |
+
# spec unpacks the padded verify output; non-spec wraps its single token.
|
| 1031 |
+
if not batch.spec_algorithm.is_none():
|
| 1032 |
+
next_token_ids = self._resolve_spec_v2_tokens(result, batch)
|
| 1033 |
+
else:
|
| 1034 |
+
# CUDA workers return a device tensor, MLX a host list[int]; both -> list.
|
| 1035 |
+
ids = (
|
| 1036 |
+
next_token_ids.tolist()
|
| 1037 |
+
if torch.is_tensor(next_token_ids)
|
| 1038 |
+
else next_token_ids
|
| 1039 |
+
)
|
| 1040 |
+
next_token_ids = [[t] for t in ids]
|
| 1041 |
+
|
| 1042 |
+
if batch.return_logprob:
|
| 1043 |
+
next_token_logprobs = logits_output.next_token_logprobs.tolist()
|
| 1044 |
+
if logits_output.next_token_top_logprobs_val:
|
| 1045 |
+
logits_output.next_token_top_logprobs_val = [
|
| 1046 |
+
v.tolist() for v in logits_output.next_token_top_logprobs_val
|
| 1047 |
+
]
|
| 1048 |
+
logits_output.next_token_top_logprobs_idx = [
|
| 1049 |
+
x.tolist() for x in logits_output.next_token_top_logprobs_idx
|
| 1050 |
+
]
|
| 1051 |
+
|
| 1052 |
+
if logits_output.next_token_token_ids_logprobs_val:
|
| 1053 |
+
logits_output.next_token_token_ids_logprobs_val = [
|
| 1054 |
+
v.tolist() if isinstance(v, torch.Tensor) else v
|
| 1055 |
+
for v in logits_output.next_token_token_ids_logprobs_val
|
| 1056 |
+
]
|
| 1057 |
+
return next_token_ids, next_token_logprobs
|
| 1058 |
+
|
| 1059 |
+
def _apply_decode_logprobs(
|
| 1060 |
+
self,
|
| 1061 |
+
*,
|
| 1062 |
+
req: Req,
|
| 1063 |
+
i: int,
|
| 1064 |
+
batch: ScheduleBatch,
|
| 1065 |
+
next_token_id: Union[int, List[int]],
|
| 1066 |
+
next_token_logprobs: list,
|
| 1067 |
+
logits_output: LogitsProcessorOutput,
|
| 1068 |
+
) -> None:
|
| 1069 |
+
# accepted_ids is already a per-req list; non-spec logprobs are flat, so
|
| 1070 |
+
# the scalar logprob still needs wrapping.
|
| 1071 |
+
if not batch.spec_algorithm.is_none():
|
| 1072 |
+
accepted_logprobs = next_token_logprobs[i]
|
| 1073 |
+
accepted_ids = next_token_id
|
| 1074 |
+
max_accept = len(accepted_logprobs)
|
| 1075 |
+
else:
|
| 1076 |
+
accepted_logprobs = [next_token_logprobs[i]]
|
| 1077 |
+
accepted_ids = next_token_id
|
| 1078 |
+
max_accept = 1
|
| 1079 |
+
|
| 1080 |
+
for j, tok_id in enumerate(accepted_ids):
|
| 1081 |
+
req.logprob.output_token_logprobs_val.append(accepted_logprobs[j])
|
| 1082 |
+
req.logprob.output_token_logprobs_idx.append(tok_id)
|
| 1083 |
+
if req.logprob.top_logprobs_num > 0:
|
| 1084 |
+
flat_idx = i * max_accept + j
|
| 1085 |
+
req.logprob.output_top_logprobs_val.append(
|
| 1086 |
+
logits_output.next_token_top_logprobs_val[flat_idx]
|
| 1087 |
+
)
|
| 1088 |
+
req.logprob.output_top_logprobs_idx.append(
|
| 1089 |
+
logits_output.next_token_top_logprobs_idx[flat_idx]
|
| 1090 |
+
)
|
| 1091 |
+
if req.logprob.token_ids_logprob is not None:
|
| 1092 |
+
flat_idx = i * max_accept + j
|
| 1093 |
+
req.logprob.output_token_ids_logprobs_val.append(
|
| 1094 |
+
logits_output.next_token_token_ids_logprobs_val[flat_idx]
|
| 1095 |
+
)
|
| 1096 |
+
req.logprob.output_token_ids_logprobs_idx.append(
|
| 1097 |
+
logits_output.next_token_token_ids_logprobs_idx[flat_idx]
|
| 1098 |
+
)
|
| 1099 |
+
|
| 1100 |
+
def add_sampling_mask_return_values(
|
| 1101 |
+
self,
|
| 1102 |
+
i: int,
|
| 1103 |
+
req: Req,
|
| 1104 |
+
output: LogitsProcessorOutput,
|
| 1105 |
+
) -> None:
|
| 1106 |
+
"""Attach sparse sampling support metadata to the return values."""
|
| 1107 |
+
mask = output.next_token_sampling_mask_idx
|
| 1108 |
+
logprobs = output.next_token_sampling_logprobs
|
| 1109 |
+
req.output_token_sampling_mask.append(None if mask is None else mask[i])
|
| 1110 |
+
req.output_token_sampling_logprobs.append(
|
| 1111 |
+
None if logprobs is None else logprobs[i]
|
| 1112 |
+
)
|
| 1113 |
+
|
| 1114 |
+
def _handle_finish_state_updated_req(
|
| 1115 |
+
self,
|
| 1116 |
+
req: Req,
|
| 1117 |
+
batch: ScheduleBatch,
|
| 1118 |
+
result: GenerationBatchResult,
|
| 1119 |
+
i: int,
|
| 1120 |
+
logits_output: LogitsProcessorOutput,
|
| 1121 |
+
):
|
| 1122 |
+
lazy = mamba_extra_buffer_lazy_enabled()
|
| 1123 |
+
known_mamba_boundary = None
|
| 1124 |
+
completed_mamba_boundary = None
|
| 1125 |
+
lookahead = 0
|
| 1126 |
+
if batch.mamba_track_mask_cpu is not None:
|
| 1127 |
+
completed_mamba_boundary = bool(batch.mamba_track_mask_cpu[i])
|
| 1128 |
+
lookahead = req.decode_batch_idx - batch.mamba_decode_batch_idx_cpu[i]
|
| 1129 |
+
assert lookahead in (0, 1), (
|
| 1130 |
+
f"mamba result lookahead={lookahead} for req {req.rid}; "
|
| 1131 |
+
"overlap advanced more than one decode batch"
|
| 1132 |
+
)
|
| 1133 |
+
if lookahead == 0:
|
| 1134 |
+
known_mamba_boundary = bool(batch.mamba_track_mask_cpu[i])
|
| 1135 |
+
else:
|
| 1136 |
+
known_mamba_boundary = bool(batch.mamba_track_mask_next_cpu[i])
|
| 1137 |
+
|
| 1138 |
+
if completed_mamba_boundary and not lazy:
|
| 1139 |
+
req.kv.mamba_last_track_idx = batch.mamba_track_buffer_indices[i]
|
| 1140 |
+
req.kv.mamba_last_track_seqlen = req.kv.kv_committed_len - lookahead
|
| 1141 |
+
elif (
|
| 1142 |
+
req.finished()
|
| 1143 |
+
and lazy
|
| 1144 |
+
and lookahead == 1
|
| 1145 |
+
and known_mamba_boundary
|
| 1146 |
+
and req.kv.mamba_next_track_idx == req.kv.mamba_last_track_idx
|
| 1147 |
+
):
|
| 1148 |
+
req.mamba_lazy_is_insert = False
|
| 1149 |
+
|
| 1150 |
+
# Called here (after update_finish_state) so req.finished() is valid
|
| 1151 |
+
# for mamba_lazy_post_decode_at_boundary inside.
|
| 1152 |
+
should_update = completed_mamba_boundary if lazy else known_mamba_boundary
|
| 1153 |
+
if should_update is None or should_update:
|
| 1154 |
+
self._mamba_prefix_cache_update(
|
| 1155 |
+
req,
|
| 1156 |
+
batch,
|
| 1157 |
+
result,
|
| 1158 |
+
i,
|
| 1159 |
+
known_boundary=not lazy and known_mamba_boundary is True,
|
| 1160 |
+
)
|
| 1161 |
+
|
| 1162 |
+
if (
|
| 1163 |
+
get_disagg().disaggregation_decode_enable_offload_kvcache
|
| 1164 |
+
and not req.finished()
|
| 1165 |
+
):
|
| 1166 |
+
self.decode_offload_manager.offload_kv_cache(req)
|
| 1167 |
+
|
| 1168 |
+
if req.finished():
|
| 1169 |
+
# isinstance narrowing: create_worker may also return plain
|
| 1170 |
+
# TpModelWorker-based drafts, which carry no spec-worker hooks.
|
| 1171 |
+
if isinstance(self.draft_worker, BaseSpecWorker):
|
| 1172 |
+
self.draft_worker.note_request_finished(
|
| 1173 |
+
rid=req.rid,
|
| 1174 |
+
natural_stop=isinstance(req.finished_reason, FINISH_MATCHED_TOKEN),
|
| 1175 |
+
)
|
| 1176 |
+
|
| 1177 |
+
# delete feature to save memory
|
| 1178 |
+
if req.multimodal_inputs is not None and req.session is None:
|
| 1179 |
+
req.multimodal_inputs.release_features()
|
| 1180 |
+
self._maybe_collect_routed_experts(req)
|
| 1181 |
+
self._maybe_collect_indexer_topk(req)
|
| 1182 |
+
|
| 1183 |
+
if get_disagg().disaggregation_decode_enable_offload_kvcache:
|
| 1184 |
+
# Asynchronously offload KV cache; release_kv_cache will be called after Device->Host transfer completes
|
| 1185 |
+
if not self.decode_offload_manager.offload_kv_cache(req):
|
| 1186 |
+
self.decode_offload_manager.finalize_release_on_finish(req)
|
| 1187 |
+
else:
|
| 1188 |
+
if get_memory().enable_hisparse:
|
| 1189 |
+
self.hisparse_coordinator.request_finished(req)
|
| 1190 |
+
prepare_release = getattr(
|
| 1191 |
+
self.model_worker, "prepare_for_kv_cache_release", None
|
| 1192 |
+
)
|
| 1193 |
+
if callable(prepare_release):
|
| 1194 |
+
prepare_release(req)
|
| 1195 |
+
is_insert = (
|
| 1196 |
+
req.mamba_lazy_is_insert
|
| 1197 |
+
if mamba_extra_buffer_lazy_enabled()
|
| 1198 |
+
else True
|
| 1199 |
+
)
|
| 1200 |
+
release_kv_cache(req, self.tree_cache, is_insert=is_insert)
|
| 1201 |
+
|
| 1202 |
+
req.time_stats.set_completion_time()
|
| 1203 |
+
|
| 1204 |
+
self._maybe_collect_customized_info(i, req, logits_output)
|
| 1205 |
+
|
| 1206 |
+
def _maybe_update_reasoning_tokens(
|
| 1207 |
+
self,
|
| 1208 |
+
req: Req,
|
| 1209 |
+
next_token_id: Union[int, List[int]],
|
| 1210 |
+
):
|
| 1211 |
+
think_end_ids = self.model_config.think_end_ids
|
| 1212 |
+
if req.require_reasoning and think_end_ids:
|
| 1213 |
+
req.update_reasoning_tokens(next_token_id, think_end_ids)
|
| 1214 |
+
|
| 1215 |
+
def _mamba_prefix_cache_update(
|
| 1216 |
+
self,
|
| 1217 |
+
req: Req,
|
| 1218 |
+
batch: ScheduleBatch,
|
| 1219 |
+
result: GenerationBatchResult,
|
| 1220 |
+
i: int,
|
| 1221 |
+
known_boundary: bool = False,
|
| 1222 |
+
) -> None:
|
| 1223 |
+
"""Update mamba track state at ping-pong boundaries.
|
| 1224 |
+
|
| 1225 |
+
Non-lazy: swap the ping-pong index so the next forward writes to
|
| 1226 |
+
the alternate slot.
|
| 1227 |
+
Lazy: keep the same index (prealloc handles the swap) and run
|
| 1228 |
+
post-decode cleanup to free the temporary second slot.
|
| 1229 |
+
"""
|
| 1230 |
+
if req.kv.mamba_ping_pong_track_buffer is None:
|
| 1231 |
+
return
|
| 1232 |
+
|
| 1233 |
+
lazy = mamba_extra_buffer_lazy_enabled()
|
| 1234 |
+
if known_boundary:
|
| 1235 |
+
self._mamba_assert_committed_len_lookahead(req)
|
| 1236 |
+
track_seqlen = req.kv.kv_committed_len
|
| 1237 |
+
assert track_seqlen % mamba_track_grid(self.tree_cache.page_size) == 0
|
| 1238 |
+
at_boundary = True
|
| 1239 |
+
else:
|
| 1240 |
+
at_boundary, track_seqlen = self._mamba_check_track_boundary(
|
| 1241 |
+
req, batch, result, i
|
| 1242 |
+
)
|
| 1243 |
+
|
| 1244 |
+
if lazy and not batch.spec_algorithm.is_none():
|
| 1245 |
+
# For spec, at_boundary means this step actually crossed an interval.
|
| 1246 |
+
self._mamba_lazy_spec_update(req, batch, i, at_boundary, track_seqlen)
|
| 1247 |
+
return
|
| 1248 |
+
|
| 1249 |
+
if not at_boundary:
|
| 1250 |
+
return
|
| 1251 |
+
|
| 1252 |
+
track_idx = req.kv.mamba_next_track_idx
|
| 1253 |
+
if not known_boundary and batch.mamba_track_buffer_indices is not None:
|
| 1254 |
+
track_idx = batch.mamba_track_buffer_indices[i]
|
| 1255 |
+
if not known_boundary:
|
| 1256 |
+
req.kv.mamba_last_track_seqlen = track_seqlen
|
| 1257 |
+
if lazy:
|
| 1258 |
+
self.mamba_lazy_post_decode_at_boundary(req, batch, track_idx)
|
| 1259 |
+
else:
|
| 1260 |
+
if not known_boundary:
|
| 1261 |
+
req.kv.mamba_last_track_idx = track_idx
|
| 1262 |
+
req.kv.mamba_next_track_idx = (
|
| 1263 |
+
batch.req_to_token_pool.get_mamba_ping_pong_other_idx(track_idx)
|
| 1264 |
+
)
|
| 1265 |
+
|
| 1266 |
+
def _mamba_lazy_spec_update(
|
| 1267 |
+
self, req: Req, batch: ScheduleBatch, i: int, crossed: bool, track_seqlen: int
|
| 1268 |
+
) -> None:
|
| 1269 |
+
"""Lazy + spec post-processing.
|
| 1270 |
+
|
| 1271 |
+
Running req with a confirmed crossing: the commit scattered the
|
| 1272 |
+
crossing state into the planned position; promote it to keep unless
|
| 1273 |
+
the plan already pointed at the keep slot (in-place fallback, or
|
| 1274 |
+
promoted by an earlier overlapped confirmation).
|
| 1275 |
+
Finishing req: donate the keep slot only if no scatter wrote it
|
| 1276 |
+
(this step) or may still write it (the in-flight next step).
|
| 1277 |
+
"""
|
| 1278 |
+
positions = batch.mamba_lazy_spec_track_positions_cpu
|
| 1279 |
+
planned_pos = (
|
| 1280 |
+
positions[i]
|
| 1281 |
+
if positions is not None and i < len(positions)
|
| 1282 |
+
else None # filtered/merged snapshot without a plan: be conservative
|
| 1283 |
+
)
|
| 1284 |
+
|
| 1285 |
+
if req.finished():
|
| 1286 |
+
# Skip the donation if a scatter wrote or may still write the keep slot.
|
| 1287 |
+
keep_written_by_this_step = (
|
| 1288 |
+
crossed and planned_pos == req.kv.mamba_next_track_idx
|
| 1289 |
+
)
|
| 1290 |
+
other_idx = 1 - req.kv.mamba_next_track_idx
|
| 1291 |
+
# Recompute the in-flight verify's plan (kv_committed_len is
|
| 1292 |
+
# frozen since its prepare, so the recompute is exact).
|
| 1293 |
+
keep_may_be_written_in_flight = req.kv.mamba_ping_pong_track_buffer[
|
| 1294 |
+
other_idx
|
| 1295 |
+
].item() == -1 and mamba_lazy_spec_in_window(
|
| 1296 |
+
req,
|
| 1297 |
+
mamba_track_grid(self.tree_cache.page_size),
|
| 1298 |
+
max_speculative_num_draft_tokens(),
|
| 1299 |
+
)
|
| 1300 |
+
if (
|
| 1301 |
+
planned_pos is None
|
| 1302 |
+
or keep_written_by_this_step
|
| 1303 |
+
or keep_may_be_written_in_flight
|
| 1304 |
+
):
|
| 1305 |
+
req.mamba_lazy_is_insert = False
|
| 1306 |
+
return
|
| 1307 |
+
|
| 1308 |
+
if not crossed or planned_pos is None:
|
| 1309 |
+
return
|
| 1310 |
+
if planned_pos != req.kv.mamba_next_track_idx:
|
| 1311 |
+
# Promote pending -> keep: free the old checkpoint, repoint.
|
| 1312 |
+
pool = batch.req_to_token_pool
|
| 1313 |
+
keep_idx = req.kv.mamba_next_track_idx
|
| 1314 |
+
keep_val = req.kv.mamba_ping_pong_track_buffer[keep_idx]
|
| 1315 |
+
pool.mamba_allocator.free(keep_val.unsqueeze(0))
|
| 1316 |
+
pool.set_mamba_ping_pong_slot(req, keep_idx, -1)
|
| 1317 |
+
req.kv.mamba_next_track_idx = planned_pos
|
| 1318 |
+
# else: in-place fallback, or promoted by an earlier confirmation —
|
| 1319 |
+
# keep holds the track_seqlen state either way.
|
| 1320 |
+
req.kv.mamba_last_track_idx = planned_pos
|
| 1321 |
+
req.kv.mamba_last_track_seqlen = track_seqlen
|
| 1322 |
+
|
| 1323 |
+
@staticmethod
|
| 1324 |
+
def _mamba_assert_committed_len_lookahead(req: Req) -> None:
|
| 1325 |
+
"""Alarm if overlap advances beyond the scheduler's modeled window."""
|
| 1326 |
+
assert req.output_ids, (
|
| 1327 |
+
"mamba track boundary reached before a decode token was appended "
|
| 1328 |
+
f"(req {req.rid}); output_ids is empty"
|
| 1329 |
+
)
|
| 1330 |
+
token_seq_len = len(req.origin_input_ids) + len(req.output_ids) - 1
|
| 1331 |
+
assert (req.kv.kv_committed_len - token_seq_len) in (0, 1), (
|
| 1332 |
+
f"mamba track boundary: kv_committed_len={req.kv.kv_committed_len} "
|
| 1333 |
+
f"leads seq_len={token_seq_len} by more than one (req {req.rid}); "
|
| 1334 |
+
"overlap lookahead wider than assumed"
|
| 1335 |
+
)
|
| 1336 |
+
|
| 1337 |
+
def _mamba_check_track_boundary(self, req, batch, result, i):
|
| 1338 |
+
"""Check if this decode step crosses a mamba track interval boundary.
|
| 1339 |
+
|
| 1340 |
+
Returns (at_boundary, track_seqlen). The boundary condition
|
| 1341 |
+
matches what the forward's tracking mask used:
|
| 1342 |
+
``prepare_for_decode`` increments both ``seq_lens_cpu`` and
|
| 1343 |
+
``kv_committed_len`` by 1, then checks
|
| 1344 |
+
``seq_lens_cpu % interval == 0``. Subtracting the overlap
|
| 1345 |
+
lookahead from ``kv_committed_len`` reproduces that check.
|
| 1346 |
+
|
| 1347 |
+
For spec decode, the boundary is detected by comparing the
|
| 1348 |
+
accepted seq_len range against interval boundaries.
|
| 1349 |
+
"""
|
| 1350 |
+
interval = mamba_track_grid(self.tree_cache.page_size)
|
| 1351 |
+
|
| 1352 |
+
if batch.spec_algorithm.is_none():
|
| 1353 |
+
lookahead = req.decode_batch_idx - batch.mamba_decode_batch_idx_cpu[i]
|
| 1354 |
+
committed_len = req.kv.kv_committed_len - lookahead
|
| 1355 |
+
if committed_len % interval == 0:
|
| 1356 |
+
return True, committed_len
|
| 1357 |
+
elif result.num_correct_drafts_per_req_cpu is not None:
|
| 1358 |
+
cur = req.seqlen - 1
|
| 1359 |
+
prev = cur - result.num_correct_drafts_per_req_cpu[i] - 1
|
| 1360 |
+
if cur // interval != prev // interval:
|
| 1361 |
+
return True, cur // interval * interval
|
| 1362 |
+
|
| 1363 |
+
return False, 0
|
| 1364 |
+
|
| 1365 |
+
def mamba_lazy_post_decode_at_boundary(
|
| 1366 |
+
self, req: Req, batch: ScheduleBatch, track_idx: int
|
| 1367 |
+
):
|
| 1368 |
+
"""Commit a completed lazy-mode boundary and free its old slot."""
|
| 1369 |
+
req.kv.mamba_last_track_idx = track_idx
|
| 1370 |
+
req.kv.mamba_next_track_idx = track_idx
|
| 1371 |
+
other_idx = 1 - track_idx
|
| 1372 |
+
other_val = req.kv.mamba_ping_pong_track_buffer[other_idx].item()
|
| 1373 |
+
if other_val != -1:
|
| 1374 |
+
pool = batch.req_to_token_pool
|
| 1375 |
+
pool.mamba_allocator.free(
|
| 1376 |
+
req.kv.mamba_ping_pong_track_buffer[other_idx].unsqueeze(0)
|
| 1377 |
+
)
|
| 1378 |
+
pool.set_mamba_ping_pong_slot(req, other_idx, -1)
|
runtime/patches/sglang-logits-processor.py
ADDED
|
@@ -0,0 +1,1272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
"""Logits processing."""
|
| 15 |
+
|
| 16 |
+
import dataclasses
|
| 17 |
+
import logging
|
| 18 |
+
from contextlib import contextmanager
|
| 19 |
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from torch import nn
|
| 23 |
+
|
| 24 |
+
from sglang.kernels.ops.activation.softcap import (
|
| 25 |
+
softcap_inplace_logits as fused_softcap,
|
| 26 |
+
)
|
| 27 |
+
from sglang.srt.beam_search.logits_capture import BeamLogitsCapture
|
| 28 |
+
from sglang.srt.distributed import get_tp_group
|
| 29 |
+
from sglang.srt.distributed.device_communicators import triton_symm_mem_ag
|
| 30 |
+
from sglang.srt.environ import envs
|
| 31 |
+
from sglang.srt.layers import layernorm_sp
|
| 32 |
+
from sglang.srt.layers.aux_hidden_states import (
|
| 33 |
+
AuxHiddenStates,
|
| 34 |
+
pack_aux_hidden_states,
|
| 35 |
+
)
|
| 36 |
+
from sglang.srt.layers.dp_attention import (
|
| 37 |
+
DpPaddingMode,
|
| 38 |
+
attn_tp_all_gather,
|
| 39 |
+
attn_tp_all_gather_into_tensor,
|
| 40 |
+
dp_gather_replicate,
|
| 41 |
+
dp_scatter,
|
| 42 |
+
get_dp_device,
|
| 43 |
+
get_dp_dtype,
|
| 44 |
+
get_dp_hidden_size,
|
| 45 |
+
)
|
| 46 |
+
from sglang.srt.layers.logprob_processor import (
|
| 47 |
+
InputLogprobProcessor,
|
| 48 |
+
LogprobStage,
|
| 49 |
+
get_token_ids_logprobs_raw,
|
| 50 |
+
get_top_logprobs_raw,
|
| 51 |
+
)
|
| 52 |
+
from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding
|
| 53 |
+
from sglang.srt.model_executor.forward_batch_info import (
|
| 54 |
+
CaptureHiddenMode,
|
| 55 |
+
ForwardBatch,
|
| 56 |
+
ForwardMode,
|
| 57 |
+
)
|
| 58 |
+
from sglang.srt.runtime_context import get_exec, get_parallel
|
| 59 |
+
from sglang.srt.sampling.sampling_observer import DeviceAuxiliaryOutput
|
| 60 |
+
from sglang.srt.utils.common import (
|
| 61 |
+
is_cpu,
|
| 62 |
+
is_npu,
|
| 63 |
+
is_pin_memory_available,
|
| 64 |
+
use_intel_amx_backend,
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
logger = logging.getLogger(__name__)
|
| 68 |
+
|
| 69 |
+
_is_npu = is_npu()
|
| 70 |
+
_is_cpu = is_cpu()
|
| 71 |
+
|
| 72 |
+
_UNQUANTIZED_LM_HEAD_METHODS = {
|
| 73 |
+
"UnquantizedEmbeddingMethod",
|
| 74 |
+
"UnquantizedLinearMethod",
|
| 75 |
+
"PackWeightMethod",
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
# None outside a FlashInfer autotune pass; inside one, whether that pass runs the
|
| 79 |
+
# LM head. Not-None means the forward's output is discarded -- attention backends
|
| 80 |
+
# read that via get_in_autotune_dummy_run() to skip a cross-node exchange.
|
| 81 |
+
# Skipping the LM head skips its [batch * dp_size, vocab] all-gather, which OOMs
|
| 82 |
+
# under DP attention with a tight mem_fraction_static.
|
| 83 |
+
_autotune_run_lm_head: Optional[bool] = None
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _trace_e2e_logits(stage: str, **fields) -> None:
|
| 87 |
+
if not envs.SGLANG_TRACE_LOGITS_E2E.get():
|
| 88 |
+
return
|
| 89 |
+
try:
|
| 90 |
+
parallel = get_parallel()
|
| 91 |
+
rank = f"dp={parallel.attn_dp_rank} " f"tp={parallel.tp_rank}"
|
| 92 |
+
except Exception:
|
| 93 |
+
rank = "rank=unknown"
|
| 94 |
+
details = " ".join(f"{key}={value}" for key, value in fields.items())
|
| 95 |
+
print(f"SGLANG_TRACE_LOGITS_E2E {rank} stage={stage} {details}", flush=True)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _has_lm_head_runtime_attrs(lm_head, attr_names: Tuple[str, ...]) -> bool:
|
| 99 |
+
return all(hasattr(lm_head, attr_name) for attr_name in attr_names)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def should_apply_lm_head_quant_method(lm_head, quant_method) -> bool:
|
| 103 |
+
if (
|
| 104 |
+
quant_method is None
|
| 105 |
+
or not hasattr(lm_head, "weight")
|
| 106 |
+
or not callable(getattr(quant_method, "apply", None))
|
| 107 |
+
):
|
| 108 |
+
return False
|
| 109 |
+
|
| 110 |
+
method_name = type(quant_method).__name__
|
| 111 |
+
if method_name in _UNQUANTIZED_LM_HEAD_METHODS:
|
| 112 |
+
return False
|
| 113 |
+
|
| 114 |
+
# A shared target lm_head can retain the draft's stale ModelOpt method; use it
|
| 115 |
+
# only when the runtime tensor layout matches that method.
|
| 116 |
+
if method_name == "ModelOptFp4LinearMethod":
|
| 117 |
+
if lm_head.weight.dtype == torch.int32 and _has_lm_head_runtime_attrs(
|
| 118 |
+
lm_head,
|
| 119 |
+
(
|
| 120 |
+
"weight_scale",
|
| 121 |
+
"weight_global_scale",
|
| 122 |
+
"workspace",
|
| 123 |
+
"input_size_per_partition",
|
| 124 |
+
"output_size_per_partition",
|
| 125 |
+
),
|
| 126 |
+
):
|
| 127 |
+
return True
|
| 128 |
+
return lm_head.weight.dtype == torch.uint8 and _has_lm_head_runtime_attrs(
|
| 129 |
+
lm_head,
|
| 130 |
+
(
|
| 131 |
+
"weight_scale_interleaved",
|
| 132 |
+
"alpha",
|
| 133 |
+
"input_scale_inv",
|
| 134 |
+
"input_size_per_partition",
|
| 135 |
+
"output_size_per_partition",
|
| 136 |
+
),
|
| 137 |
+
)
|
| 138 |
+
if method_name == "ModelOptNvFp4A16LinearMethod":
|
| 139 |
+
return lm_head.weight.dtype == torch.int32 and _has_lm_head_runtime_attrs(
|
| 140 |
+
lm_head,
|
| 141 |
+
(
|
| 142 |
+
"weight_scale",
|
| 143 |
+
"weight_global_scale",
|
| 144 |
+
"workspace",
|
| 145 |
+
"input_size_per_partition",
|
| 146 |
+
"output_size_per_partition",
|
| 147 |
+
),
|
| 148 |
+
)
|
| 149 |
+
if method_name == "ModelOptFp8LinearMethod":
|
| 150 |
+
return (
|
| 151 |
+
lm_head.weight.dtype == torch.float8_e4m3fn
|
| 152 |
+
and _has_lm_head_runtime_attrs(lm_head, ("weight_scale", "input_scale"))
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
return True
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
# FlashInfer autotune skips the unprofiled LM-head all-gather; its
|
| 159 |
+
# [batch * dp_size, vocab] output can OOM under tight DP-attention memory.
|
| 160 |
+
_in_autotune_dummy_run = False
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def get_in_autotune_dummy_run() -> bool:
|
| 164 |
+
return _autotune_run_lm_head is not None
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
@contextmanager
|
| 168 |
+
def autotune_dummy_run_mode(*, run_lm_head: bool):
|
| 169 |
+
global _autotune_run_lm_head
|
| 170 |
+
_autotune_run_lm_head = run_lm_head
|
| 171 |
+
try:
|
| 172 |
+
yield
|
| 173 |
+
finally:
|
| 174 |
+
_autotune_run_lm_head = None
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@dataclasses.dataclass
|
| 178 |
+
class LogitsProcessorOutput:
|
| 179 |
+
## Part 1: This part will be assigned in python/sglang/srt/layers/logits_processor.py::LogitsProcessor
|
| 180 |
+
# The logits of the next tokens. shape: [#seq, vocab_size]
|
| 181 |
+
# Can be None for certain prefill-only requests (e.g., multi-item scoring) that don't need next token generation
|
| 182 |
+
next_token_logits: Optional[torch.Tensor]
|
| 183 |
+
# Used by speculative decoding (EAGLE)
|
| 184 |
+
# The last hidden layers
|
| 185 |
+
hidden_states: Optional[torch.Tensor] = None
|
| 186 |
+
|
| 187 |
+
## Part 2: This part will be assigned in python/sglang/srt/layers/sampler.py::Sampler
|
| 188 |
+
# he log probs of output tokens, if SGLANG_RETURN_ORIGINAL_LOGPROB = True, will get the log probs before applying temperature. If False, will get the log probs before applying temperature.
|
| 189 |
+
next_token_logprobs: Optional[torch.Tensor] = None
|
| 190 |
+
# The logprobs and ids of the top-k tokens in output positions. shape: [#seq, k]
|
| 191 |
+
next_token_top_logprobs_val: Optional[List] = None
|
| 192 |
+
next_token_top_logprobs_idx: Optional[List] = None
|
| 193 |
+
# The logprobs and ids of the requested token ids in output positions. shape: [#seq, n] (n is the number of requested token ids)
|
| 194 |
+
# Can contain either lists or GPU tensors (for delayed copy optimization in prefill-only requests)
|
| 195 |
+
next_token_token_ids_logprobs_val: Optional[
|
| 196 |
+
List[Union[List[float], torch.Tensor]]
|
| 197 |
+
] = None
|
| 198 |
+
next_token_token_ids_logprobs_idx: Optional[List] = None
|
| 199 |
+
# Sparse top-k/top-p/min-p support ids and selected-token logprob after
|
| 200 |
+
# truncation/renormalization. Only populated when requested.
|
| 201 |
+
next_token_sampling_mask_idx: Optional[List[Optional[List[int]]]] = None
|
| 202 |
+
next_token_sampling_logprobs: Optional[List[Optional[float]]] = None
|
| 203 |
+
|
| 204 |
+
## Part 3: Prefill-only. This part will be assigned in python/sglang/srt/layers/logits_processor.py::LogitsProcessor
|
| 205 |
+
# The logprobs of input tokens. shape: [#token]
|
| 206 |
+
input_token_logprobs: Optional[torch.Tensor] = None
|
| 207 |
+
# The logprobs and ids of the top-k tokens in input positions. shape: [#seq, #token, k]
|
| 208 |
+
input_top_logprobs_val: Optional[List] = None
|
| 209 |
+
input_top_logprobs_idx: Optional[List] = None
|
| 210 |
+
# The logprobs and ids of the requested token ids in input positions. shape: [#seq, n] (n is the number of requested token ids)
|
| 211 |
+
# Can contain either lists or GPU tensors (for delayed GPU-to-CPU transfer optimization)
|
| 212 |
+
input_token_ids_logprobs_val: Optional[List[Union[List[float], torch.Tensor]]] = (
|
| 213 |
+
None
|
| 214 |
+
)
|
| 215 |
+
input_token_ids_logprobs_idx: Optional[List] = None
|
| 216 |
+
|
| 217 |
+
## Part 4: Diffusion LLM only.
|
| 218 |
+
full_logits: Optional[torch.Tensor] = None
|
| 219 |
+
|
| 220 |
+
# Beam search only: raw pre-sample logits for the scheduler-side joint
|
| 221 |
+
# selection; see beam_search.logits_capture.
|
| 222 |
+
beam: Optional[BeamLogitsCapture] = None
|
| 223 |
+
|
| 224 |
+
## Part 5: Customized Info
|
| 225 |
+
customized_info: Optional[Dict[str, List[Any]]] = None
|
| 226 |
+
|
| 227 |
+
## Part 6: Temporary variables
|
| 228 |
+
# FIXME: These fields are not logits-related but are passed through here as a
|
| 229 |
+
# workaround since ForwardBatch is local to forward_batch_generation().
|
| 230 |
+
# They should be moved to GenerationBatchResult to keep this class clean.
|
| 231 |
+
mm_input_embeds: Optional[torch.Tensor] = None
|
| 232 |
+
|
| 233 |
+
# Scheduler-local output copied alongside the ordinary generation result.
|
| 234 |
+
auxiliary_device_output: Optional[DeviceAuxiliaryOutput] = None
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@dataclasses.dataclass
|
| 238 |
+
class LogitsMetadata:
|
| 239 |
+
forward_mode: ForwardMode
|
| 240 |
+
capture_hidden_mode: CaptureHiddenMode = CaptureHiddenMode.NULL
|
| 241 |
+
next_token_logits_buffer: Optional[torch.Tensor] = None
|
| 242 |
+
|
| 243 |
+
extend_return_logprob: bool = False
|
| 244 |
+
extend_return_top_logprob: bool = False
|
| 245 |
+
extend_token_ids_logprob: bool = False
|
| 246 |
+
extend_seq_lens: Optional[torch.Tensor] = None
|
| 247 |
+
extend_seq_lens_cpu: Optional[List[int]] = None
|
| 248 |
+
extend_logprob_start_lens_cpu: Optional[List[int]] = None
|
| 249 |
+
extend_logprob_pruned_lens_cpu: Optional[List[int]] = None
|
| 250 |
+
top_logprobs_nums: Optional[List[int]] = None
|
| 251 |
+
extend_input_logprob_token_ids_gpu: Optional[torch.Tensor] = None
|
| 252 |
+
token_ids_logprobs: Optional[List[List[int]]] = None
|
| 253 |
+
|
| 254 |
+
# logits and logprobs post processing
|
| 255 |
+
temperature: torch.Tensor = None
|
| 256 |
+
top_p: torch.Tensor = None
|
| 257 |
+
|
| 258 |
+
# DP attention metadata. Not needed when DP attention is not used.
|
| 259 |
+
# Number of tokens in the request.
|
| 260 |
+
global_num_tokens_gpu: Optional[torch.Tensor] = None
|
| 261 |
+
# The start position of local hidden states.
|
| 262 |
+
dp_local_start_pos: Optional[torch.Tensor] = None
|
| 263 |
+
dp_local_num_tokens: Optional[torch.Tensor] = None
|
| 264 |
+
global_dp_buffer_len: Optional[int] = None
|
| 265 |
+
# Number of tokens to sample per DP rank
|
| 266 |
+
global_num_tokens_for_logprob_cpu: Optional[torch.Tensor] = None
|
| 267 |
+
global_num_tokens_for_logprob_gpu: Optional[torch.Tensor] = None
|
| 268 |
+
# The gather mode for DP attention
|
| 269 |
+
dp_padding_mode: Optional[DpPaddingMode] = None
|
| 270 |
+
|
| 271 |
+
# Whether this batch is prefill-only (no token generation needed)
|
| 272 |
+
is_prefill_only: bool = False
|
| 273 |
+
|
| 274 |
+
mm_input_embeds: Optional[torch.Tensor] = None
|
| 275 |
+
|
| 276 |
+
# DRAFT_EXTEND_V2: when set, lm_head runs only on these rows (see
|
| 277 |
+
# EagleDraftExtendInput.select_index).
|
| 278 |
+
draft_extend_select_index: Optional[torch.Tensor] = None
|
| 279 |
+
|
| 280 |
+
@classmethod
|
| 281 |
+
def from_forward_batch(cls, forward_batch: ForwardBatch):
|
| 282 |
+
if (
|
| 283 |
+
forward_batch.forward_mode.is_extend()
|
| 284 |
+
and forward_batch.return_logprob
|
| 285 |
+
and not forward_batch.forward_mode.is_target_verify()
|
| 286 |
+
):
|
| 287 |
+
extend_return_top_logprob = any(
|
| 288 |
+
x > 0 for x in forward_batch.top_logprobs_nums
|
| 289 |
+
)
|
| 290 |
+
extend_token_ids_logprob = any(
|
| 291 |
+
x is not None for x in forward_batch.token_ids_logprobs
|
| 292 |
+
)
|
| 293 |
+
extend_return_logprob = False
|
| 294 |
+
extend_logprob_pruned_lens_cpu = []
|
| 295 |
+
for extend_len, start_len in zip(
|
| 296 |
+
forward_batch.extend_seq_lens_cpu,
|
| 297 |
+
forward_batch.extend_logprob_start_lens_cpu,
|
| 298 |
+
):
|
| 299 |
+
if extend_len - start_len > 0:
|
| 300 |
+
extend_return_logprob = True
|
| 301 |
+
extend_logprob_pruned_lens_cpu.append(extend_len - start_len)
|
| 302 |
+
else:
|
| 303 |
+
extend_return_logprob = extend_return_top_logprob = (
|
| 304 |
+
extend_token_ids_logprob
|
| 305 |
+
) = extend_logprob_pruned_lens_cpu = False
|
| 306 |
+
|
| 307 |
+
if forward_batch.forward_mode.is_draft_extend_v2():
|
| 308 |
+
draft_extend_select_index = forward_batch.spec_info.select_index
|
| 309 |
+
else:
|
| 310 |
+
draft_extend_select_index = None
|
| 311 |
+
|
| 312 |
+
return cls(
|
| 313 |
+
forward_mode=forward_batch.forward_mode,
|
| 314 |
+
capture_hidden_mode=forward_batch.capture_hidden_mode,
|
| 315 |
+
next_token_logits_buffer=forward_batch.next_token_logits_buffer,
|
| 316 |
+
extend_return_logprob=extend_return_logprob,
|
| 317 |
+
extend_return_top_logprob=extend_return_top_logprob,
|
| 318 |
+
extend_token_ids_logprob=extend_token_ids_logprob,
|
| 319 |
+
extend_seq_lens=forward_batch.extend_seq_lens,
|
| 320 |
+
extend_seq_lens_cpu=forward_batch.extend_seq_lens_cpu,
|
| 321 |
+
extend_logprob_start_lens_cpu=forward_batch.extend_logprob_start_lens_cpu,
|
| 322 |
+
extend_logprob_pruned_lens_cpu=extend_logprob_pruned_lens_cpu,
|
| 323 |
+
top_logprobs_nums=forward_batch.top_logprobs_nums,
|
| 324 |
+
token_ids_logprobs=forward_batch.token_ids_logprobs,
|
| 325 |
+
extend_input_logprob_token_ids_gpu=forward_batch.extend_input_logprob_token_ids_gpu,
|
| 326 |
+
is_prefill_only=forward_batch.is_prefill_only,
|
| 327 |
+
global_num_tokens_gpu=forward_batch.global_num_tokens_gpu,
|
| 328 |
+
dp_local_start_pos=forward_batch.dp_local_start_pos,
|
| 329 |
+
dp_local_num_tokens=forward_batch.dp_local_num_tokens,
|
| 330 |
+
global_dp_buffer_len=forward_batch.global_dp_buffer_len,
|
| 331 |
+
global_num_tokens_for_logprob_cpu=forward_batch.global_num_tokens_for_logprob_cpu,
|
| 332 |
+
global_num_tokens_for_logprob_gpu=forward_batch.global_num_tokens_for_logprob_gpu,
|
| 333 |
+
dp_padding_mode=DpPaddingMode.SUM_LEN,
|
| 334 |
+
mm_input_embeds=forward_batch.mm_input_embeds,
|
| 335 |
+
draft_extend_select_index=draft_extend_select_index,
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
def compute_dp_attention_metadata(self):
|
| 339 |
+
cumtokens = torch.cumsum(self.global_num_tokens_for_logprob_gpu, dim=0)
|
| 340 |
+
dp_rank = get_parallel().attn_dp_rank
|
| 341 |
+
if dp_rank == 0:
|
| 342 |
+
dp_local_start_pos = torch.zeros_like(
|
| 343 |
+
self.global_num_tokens_for_logprob_gpu[0]
|
| 344 |
+
)
|
| 345 |
+
else:
|
| 346 |
+
dp_local_start_pos = cumtokens[dp_rank - 1]
|
| 347 |
+
|
| 348 |
+
self.dp_local_start_pos = dp_local_start_pos
|
| 349 |
+
self.dp_local_num_tokens = self.global_num_tokens_for_logprob_gpu[dp_rank]
|
| 350 |
+
|
| 351 |
+
hidden_size = get_dp_hidden_size()
|
| 352 |
+
dtype = get_dp_dtype()
|
| 353 |
+
device = get_dp_device()
|
| 354 |
+
|
| 355 |
+
if self.global_num_tokens_for_logprob_cpu is not None:
|
| 356 |
+
# create a smaller buffer to reduce peak memory usage
|
| 357 |
+
self.global_dp_buffer_len = sum(self.global_num_tokens_for_logprob_cpu)
|
| 358 |
+
else:
|
| 359 |
+
self.global_dp_buffer_len = self.global_dp_buffer_len
|
| 360 |
+
|
| 361 |
+
self.gathered_buffer = torch.empty(
|
| 362 |
+
(
|
| 363 |
+
self.global_dp_buffer_len,
|
| 364 |
+
hidden_size,
|
| 365 |
+
),
|
| 366 |
+
dtype=dtype,
|
| 367 |
+
device=device,
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
class LogitsProcessor(nn.Module):
|
| 372 |
+
def __init__(
|
| 373 |
+
self,
|
| 374 |
+
config,
|
| 375 |
+
skip_all_gather: bool = False,
|
| 376 |
+
logit_scale: Optional[float] = None,
|
| 377 |
+
return_full_logits: bool = False,
|
| 378 |
+
):
|
| 379 |
+
super().__init__()
|
| 380 |
+
self.config = config
|
| 381 |
+
# Decision-only optional projection. Public API renormalizes selected labels.
|
| 382 |
+
import os, json
|
| 383 |
+
self.jev_selected_ids = None
|
| 384 |
+
if os.getenv("JEV_SELECTED_HEAD", "0") == "1":
|
| 385 |
+
if get_parallel().tp_size != 1:
|
| 386 |
+
raise ValueError("Selected decision head requires TP1")
|
| 387 |
+
with open("/jev-label-token-ids.json") as label_file:
|
| 388 |
+
self.jev_selected_ids = json.load(label_file)
|
| 389 |
+
self.jev_head_cache = None
|
| 390 |
+
self.vocab_size = config.vocab_size
|
| 391 |
+
self.logit_scale = logit_scale
|
| 392 |
+
self.use_attn_tp_group = get_parallel().enable_dp_lm_head
|
| 393 |
+
self.use_tp_lm_head_all_to_all = get_parallel().enable_tp_lm_head_all_to_all
|
| 394 |
+
self.use_fp32_lm_head = get_exec().features.enable_fp32_lm_head
|
| 395 |
+
if self.use_attn_tp_group:
|
| 396 |
+
self.attn_tp_size = get_parallel().attn_tp_size
|
| 397 |
+
self.do_tensor_parallel_all_gather = (
|
| 398 |
+
not skip_all_gather and self.attn_tp_size > 1
|
| 399 |
+
)
|
| 400 |
+
self.do_tensor_parallel_all_gather_dp_attn = False
|
| 401 |
+
else:
|
| 402 |
+
self.do_tensor_parallel_all_gather = (
|
| 403 |
+
not skip_all_gather and get_parallel().tp_size > 1
|
| 404 |
+
)
|
| 405 |
+
self.do_tensor_parallel_all_gather_dp_attn = (
|
| 406 |
+
self.do_tensor_parallel_all_gather and get_parallel().attn_dp_size != 1
|
| 407 |
+
)
|
| 408 |
+
self.final_logit_softcapping = getattr(
|
| 409 |
+
self.config, "final_logit_softcapping", None
|
| 410 |
+
)
|
| 411 |
+
if (
|
| 412 |
+
self.final_logit_softcapping is not None
|
| 413 |
+
and self.final_logit_softcapping < 0
|
| 414 |
+
):
|
| 415 |
+
self.final_logit_softcapping = None
|
| 416 |
+
|
| 417 |
+
self.return_full_logits = return_full_logits
|
| 418 |
+
self.enable_mis = get_exec().features.enable_mis
|
| 419 |
+
self.rl_on_policy_target = get_exec().deterministic.rl_on_policy_target
|
| 420 |
+
|
| 421 |
+
self._logits_gatherer = triton_symm_mem_ag.MultimemAllGatherer(
|
| 422 |
+
max_tokens=triton_symm_mem_ag.recommended_max_tokens(
|
| 423 |
+
include_prefill=False, floor=128
|
| 424 |
+
),
|
| 425 |
+
enabled=self.do_tensor_parallel_all_gather and not self.use_attn_tp_group,
|
| 426 |
+
skip_entry_sync=True,
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
self.input_logprob_processor = InputLogprobProcessor()
|
| 430 |
+
|
| 431 |
+
def forward(
|
| 432 |
+
self,
|
| 433 |
+
input_ids,
|
| 434 |
+
hidden_states,
|
| 435 |
+
lm_head: VocabParallelEmbedding,
|
| 436 |
+
logits_metadata: Union[LogitsMetadata, ForwardBatch],
|
| 437 |
+
aux_hidden_states: Optional[AuxHiddenStates] = None,
|
| 438 |
+
hidden_states_before_norm: Optional[torch.Tensor] = None,
|
| 439 |
+
) -> LogitsProcessorOutput:
|
| 440 |
+
# Extract MIS indices before ForwardBatch → LogitsMetadata conversion
|
| 441 |
+
multi_item_delimiter_indices = None
|
| 442 |
+
if isinstance(logits_metadata, ForwardBatch):
|
| 443 |
+
multi_item_delimiter_indices = logits_metadata.multi_item_delimiter_indices
|
| 444 |
+
logits_metadata = LogitsMetadata.from_forward_batch(logits_metadata)
|
| 445 |
+
|
| 446 |
+
# Autotune dummy run discards this output. `is False` not `not`: None
|
| 447 |
+
# means no autotune pass, which must not skip. Placed before the MIS /
|
| 448 |
+
# DLLM / common dispatch so all three LM-head paths are skipped.
|
| 449 |
+
if _autotune_run_lm_head is False:
|
| 450 |
+
return LogitsProcessorOutput(next_token_logits=None)
|
| 451 |
+
|
| 452 |
+
# Under LayerNorm SP the decoder loop leaves these sequence-sharded; undo
|
| 453 |
+
# that before the LM head, which must not participate.
|
| 454 |
+
hidden_states, hidden_states_before_norm = layernorm_sp.maybe_exit_gather(
|
| 455 |
+
hidden_states=hidden_states,
|
| 456 |
+
hidden_states_before_norm=hidden_states_before_norm,
|
| 457 |
+
input_ids=input_ids,
|
| 458 |
+
forward_mode=logits_metadata.forward_mode,
|
| 459 |
+
)
|
| 460 |
+
|
| 461 |
+
# Multi-item scoring only for prefill-only requests with pre-computed indices.
|
| 462 |
+
if multi_item_delimiter_indices is not None and logits_metadata.is_prefill_only:
|
| 463 |
+
return self.compute_logprobs_for_multi_item_scoring(
|
| 464 |
+
input_ids,
|
| 465 |
+
hidden_states,
|
| 466 |
+
lm_head,
|
| 467 |
+
logits_metadata,
|
| 468 |
+
multi_item_delimiter_indices,
|
| 469 |
+
)
|
| 470 |
+
|
| 471 |
+
# Diffusion LLM only.
|
| 472 |
+
if logits_metadata.forward_mode.is_dllm_extend():
|
| 473 |
+
return self._get_dllm_logits(hidden_states, lm_head, logits_metadata)
|
| 474 |
+
|
| 475 |
+
# Get the last hidden states and last logits for the next token prediction
|
| 476 |
+
(
|
| 477 |
+
pruned_states,
|
| 478 |
+
pruned_states_before_norm,
|
| 479 |
+
aux_pruned_states,
|
| 480 |
+
sample_indices,
|
| 481 |
+
input_logprob_indices,
|
| 482 |
+
token_to_seq_idx,
|
| 483 |
+
) = self._get_pruned_states(
|
| 484 |
+
hidden_states,
|
| 485 |
+
hidden_states_before_norm,
|
| 486 |
+
aux_hidden_states,
|
| 487 |
+
logits_metadata,
|
| 488 |
+
)
|
| 489 |
+
|
| 490 |
+
hidden_states_to_store = self._get_hidden_states_to_store(
|
| 491 |
+
hidden_states,
|
| 492 |
+
hidden_states_before_norm,
|
| 493 |
+
aux_hidden_states,
|
| 494 |
+
pruned_states,
|
| 495 |
+
pruned_states_before_norm,
|
| 496 |
+
aux_pruned_states,
|
| 497 |
+
sample_indices,
|
| 498 |
+
logits_metadata,
|
| 499 |
+
)
|
| 500 |
+
del hidden_states
|
| 501 |
+
|
| 502 |
+
if not logits_metadata.extend_return_logprob:
|
| 503 |
+
# Compute logits for both input and sampled tokens.
|
| 504 |
+
logits = self._get_logits(pruned_states, lm_head, logits_metadata)
|
| 505 |
+
sampled_logits = (
|
| 506 |
+
logits[sample_indices] if sample_indices is not None else logits
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
# Decode mode or extend mode without return_logprob.
|
| 510 |
+
return LogitsProcessorOutput(
|
| 511 |
+
next_token_logits=sampled_logits,
|
| 512 |
+
hidden_states=hidden_states_to_store,
|
| 513 |
+
mm_input_embeds=logits_metadata.mm_input_embeds,
|
| 514 |
+
)
|
| 515 |
+
|
| 516 |
+
logprobs_result, sampled_logits = self.input_logprob_processor.forward(
|
| 517 |
+
pruned_states=pruned_states,
|
| 518 |
+
sample_indices=sample_indices,
|
| 519 |
+
input_logprob_indices=input_logprob_indices,
|
| 520 |
+
token_to_seq_idx=token_to_seq_idx,
|
| 521 |
+
lm_head=lm_head,
|
| 522 |
+
get_logits_fn=self._get_logits,
|
| 523 |
+
logits_metadata=logits_metadata,
|
| 524 |
+
skip_chunking_for_dp_attn=self.do_tensor_parallel_all_gather_dp_attn,
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
logits_output = LogitsProcessorOutput(
|
| 528 |
+
next_token_logits=sampled_logits,
|
| 529 |
+
hidden_states=hidden_states_to_store,
|
| 530 |
+
mm_input_embeds=logits_metadata.mm_input_embeds,
|
| 531 |
+
)
|
| 532 |
+
logprobs_result.write_input_to(logits_output)
|
| 533 |
+
return logits_output
|
| 534 |
+
|
| 535 |
+
def _get_pruned_states(
|
| 536 |
+
self,
|
| 537 |
+
hidden_states: torch.Tensor,
|
| 538 |
+
hidden_states_before_norm: Optional[torch.Tensor],
|
| 539 |
+
aux_hidden_states: Optional[AuxHiddenStates],
|
| 540 |
+
logits_metadata: LogitsMetadata,
|
| 541 |
+
):
|
| 542 |
+
pruned_states_before_norm: Optional[torch.Tensor] = None
|
| 543 |
+
aux_pruned_states = None
|
| 544 |
+
token_to_seq_idx = []
|
| 545 |
+
|
| 546 |
+
if (
|
| 547 |
+
logits_metadata.forward_mode.is_decode_or_idle()
|
| 548 |
+
or logits_metadata.forward_mode.is_target_verify()
|
| 549 |
+
or logits_metadata.forward_mode.is_draft_extend_v2()
|
| 550 |
+
):
|
| 551 |
+
if logits_metadata.draft_extend_select_index is not None:
|
| 552 |
+
# Only next_token_logits narrows to [bs, vocab]; the
|
| 553 |
+
# FULL-capture hidden stays unpruned.
|
| 554 |
+
pruned_states = hidden_states[logits_metadata.draft_extend_select_index]
|
| 555 |
+
else:
|
| 556 |
+
pruned_states = hidden_states
|
| 557 |
+
pruned_states_before_norm = hidden_states_before_norm
|
| 558 |
+
if aux_hidden_states is not None:
|
| 559 |
+
aux_pruned_states = (
|
| 560 |
+
aux_hidden_states
|
| 561 |
+
if isinstance(aux_hidden_states, torch.Tensor)
|
| 562 |
+
else [hidden for hidden in aux_hidden_states]
|
| 563 |
+
)
|
| 564 |
+
sample_indices = None
|
| 565 |
+
input_logprob_indices = None
|
| 566 |
+
|
| 567 |
+
elif (
|
| 568 |
+
logits_metadata.forward_mode.is_extend()
|
| 569 |
+
and not logits_metadata.extend_return_logprob
|
| 570 |
+
):
|
| 571 |
+
# Prefill without input logprobs.
|
| 572 |
+
last_index = torch.cumsum(logits_metadata.extend_seq_lens, dim=0) - 1
|
| 573 |
+
pruned_states = hidden_states[last_index]
|
| 574 |
+
if hidden_states_before_norm is not None:
|
| 575 |
+
pruned_states_before_norm = hidden_states_before_norm[last_index]
|
| 576 |
+
if aux_hidden_states is not None:
|
| 577 |
+
aux_pruned_states = (
|
| 578 |
+
aux_hidden_states[last_index]
|
| 579 |
+
if isinstance(aux_hidden_states, torch.Tensor)
|
| 580 |
+
else [hidden[last_index] for hidden in aux_hidden_states]
|
| 581 |
+
)
|
| 582 |
+
sample_indices = None
|
| 583 |
+
input_logprob_indices = None
|
| 584 |
+
else:
|
| 585 |
+
# Prefill with input logprobs.
|
| 586 |
+
# Find 4 different indices.
|
| 587 |
+
# 1. pruned_states: hidden states that we want logprobs from.
|
| 588 |
+
# 2. sample_indices: Indices that have sampled tokens.
|
| 589 |
+
# 3. input_logprob_indices: Indices that have input logprob tokens.
|
| 590 |
+
# 4. token_to_seq_idx: map each token to its sequence index
|
| 591 |
+
#
|
| 592 |
+
# Example
|
| 593 |
+
# -------
|
| 594 |
+
# Suppose a batch (flattened by sequence):
|
| 595 |
+
# [t00, t01, t02, t03, t10, t11, t12, t13, t14, t20, t21, t22, t23, t24, t25]
|
| 596 |
+
# extend_seq_lens_cpu = [4, 5, 6]
|
| 597 |
+
# extend_logprob_start_lens_cpu = [0, 5, 3]
|
| 598 |
+
#
|
| 599 |
+
# Then, the indices are:
|
| 600 |
+
# pruned_states -> [t00, t01, t02, t03, t14, t23, t24, t25]
|
| 601 |
+
# sample_indices -> [3, 4, 7]
|
| 602 |
+
# input_logprob_indices -> [0, 1, 2, 3, 5, 6, 7]
|
| 603 |
+
# token_to_seq_idx -> [0, 0, 0, 0, 1, 2, 2, 2]
|
| 604 |
+
#
|
| 605 |
+
# If chunk is enabled and chunk_size = 3, the chunks will be computed in a chunked manner:
|
| 606 |
+
# [t00, t01, t02], [t03, t14, t23], [t24, t25]
|
| 607 |
+
|
| 608 |
+
sample_index_pt = -1
|
| 609 |
+
sample_indices = []
|
| 610 |
+
input_logprob_indices_pt = 0
|
| 611 |
+
input_logprob_indices = []
|
| 612 |
+
pt, pruned_states_list, pruned_states_before_norm_list = 0, [], []
|
| 613 |
+
is_packed_aux_hidden_states = isinstance(aux_hidden_states, torch.Tensor)
|
| 614 |
+
aux_pruned_states_lists = None
|
| 615 |
+
if aux_hidden_states is not None:
|
| 616 |
+
aux_pruned_states_lists = (
|
| 617 |
+
[]
|
| 618 |
+
if is_packed_aux_hidden_states
|
| 619 |
+
else [[] for _ in aux_hidden_states]
|
| 620 |
+
)
|
| 621 |
+
|
| 622 |
+
for idx, (extend_logprob_start_len, extend_len) in enumerate(
|
| 623 |
+
zip(
|
| 624 |
+
logits_metadata.extend_logprob_start_lens_cpu,
|
| 625 |
+
logits_metadata.extend_seq_lens_cpu,
|
| 626 |
+
)
|
| 627 |
+
):
|
| 628 |
+
# It can happen in chunked prefill. We still need to sample 1 token,
|
| 629 |
+
# But we don't want to include it in input logprob.
|
| 630 |
+
if extend_len == extend_logprob_start_len:
|
| 631 |
+
start_len = extend_logprob_start_len - 1
|
| 632 |
+
else:
|
| 633 |
+
start_len = extend_logprob_start_len
|
| 634 |
+
|
| 635 |
+
# We always need at least 1 token to sample because that's required
|
| 636 |
+
# by a caller.
|
| 637 |
+
assert extend_len > start_len
|
| 638 |
+
pruned_states_list.append(
|
| 639 |
+
hidden_states[pt + start_len : pt + extend_len]
|
| 640 |
+
)
|
| 641 |
+
if hidden_states_before_norm is not None:
|
| 642 |
+
pruned_states_before_norm_list.append(
|
| 643 |
+
hidden_states_before_norm[pt + start_len : pt + extend_len]
|
| 644 |
+
)
|
| 645 |
+
if aux_pruned_states_lists is not None:
|
| 646 |
+
if is_packed_aux_hidden_states:
|
| 647 |
+
aux_pruned_states_lists.append(
|
| 648 |
+
aux_hidden_states[pt + start_len : pt + extend_len]
|
| 649 |
+
)
|
| 650 |
+
else:
|
| 651 |
+
for j, hidden in enumerate(aux_hidden_states):
|
| 652 |
+
aux_pruned_states_lists[j].append(
|
| 653 |
+
hidden[pt + start_len : pt + extend_len]
|
| 654 |
+
)
|
| 655 |
+
# Map each token to its sequence index, for chunked computation
|
| 656 |
+
# of input logprobs
|
| 657 |
+
token_to_seq_idx.extend([idx] * (extend_len - start_len))
|
| 658 |
+
pt += extend_len
|
| 659 |
+
sample_index_pt += extend_len - start_len
|
| 660 |
+
sample_indices.append(sample_index_pt)
|
| 661 |
+
input_logprob_indices.extend(
|
| 662 |
+
[
|
| 663 |
+
input_logprob_indices_pt + i
|
| 664 |
+
for i in range(extend_len - extend_logprob_start_len)
|
| 665 |
+
]
|
| 666 |
+
)
|
| 667 |
+
input_logprob_indices_pt += extend_len - start_len
|
| 668 |
+
|
| 669 |
+
pruned_states = torch.cat(pruned_states_list)
|
| 670 |
+
if hidden_states_before_norm is not None:
|
| 671 |
+
pruned_states_before_norm = torch.cat(pruned_states_before_norm_list)
|
| 672 |
+
if aux_pruned_states_lists is not None:
|
| 673 |
+
aux_pruned_states = (
|
| 674 |
+
torch.cat(aux_pruned_states_lists)
|
| 675 |
+
if is_packed_aux_hidden_states
|
| 676 |
+
else [torch.cat(lst) for lst in aux_pruned_states_lists]
|
| 677 |
+
)
|
| 678 |
+
|
| 679 |
+
# Build the index tensors via pinned host memory + non-blocking H2D
|
| 680 |
+
# so the small copy doesn't drain the stream.
|
| 681 |
+
sample_indices = torch.tensor(
|
| 682 |
+
sample_indices,
|
| 683 |
+
dtype=torch.int64,
|
| 684 |
+
pin_memory=is_pin_memory_available(),
|
| 685 |
+
).to(pruned_states.device, non_blocking=True)
|
| 686 |
+
input_logprob_indices = torch.tensor(
|
| 687 |
+
input_logprob_indices,
|
| 688 |
+
dtype=torch.int64,
|
| 689 |
+
pin_memory=is_pin_memory_available(),
|
| 690 |
+
).to(pruned_states.device, non_blocking=True)
|
| 691 |
+
|
| 692 |
+
return (
|
| 693 |
+
pruned_states,
|
| 694 |
+
pruned_states_before_norm,
|
| 695 |
+
aux_pruned_states,
|
| 696 |
+
sample_indices,
|
| 697 |
+
input_logprob_indices,
|
| 698 |
+
token_to_seq_idx,
|
| 699 |
+
)
|
| 700 |
+
|
| 701 |
+
def _get_hidden_states_to_store(
|
| 702 |
+
self,
|
| 703 |
+
hidden_states: torch.Tensor,
|
| 704 |
+
hidden_states_before_norm: Optional[torch.Tensor],
|
| 705 |
+
aux_hidden_states: Optional[AuxHiddenStates],
|
| 706 |
+
pruned_states: torch.Tensor,
|
| 707 |
+
pruned_states_before_norm: Optional[torch.Tensor],
|
| 708 |
+
aux_pruned_states: Optional[AuxHiddenStates],
|
| 709 |
+
sample_indices: Optional[torch.Tensor],
|
| 710 |
+
logits_metadata: LogitsMetadata,
|
| 711 |
+
) -> Optional[torch.Tensor]:
|
| 712 |
+
hidden_states_to_store: Optional[torch.Tensor] = None
|
| 713 |
+
hidden_states_to_store_before_norm: Optional[torch.Tensor] = None
|
| 714 |
+
if logits_metadata.capture_hidden_mode.need_capture():
|
| 715 |
+
if logits_metadata.capture_hidden_mode.is_full():
|
| 716 |
+
if aux_hidden_states is not None:
|
| 717 |
+
hidden_states_to_store = pack_aux_hidden_states(aux_hidden_states)
|
| 718 |
+
else:
|
| 719 |
+
hidden_states_to_store = hidden_states
|
| 720 |
+
hidden_states_to_store_before_norm = hidden_states_before_norm
|
| 721 |
+
elif logits_metadata.capture_hidden_mode.is_last():
|
| 722 |
+
# Get the last token hidden states. If sample_indices is None,
|
| 723 |
+
# pruned states only contain the last tokens already.
|
| 724 |
+
if aux_hidden_states is not None:
|
| 725 |
+
assert aux_pruned_states is not None
|
| 726 |
+
aux_pruned_states = pack_aux_hidden_states(aux_pruned_states)
|
| 727 |
+
hidden_states_to_store = (
|
| 728 |
+
aux_pruned_states[sample_indices]
|
| 729 |
+
if sample_indices is not None
|
| 730 |
+
else aux_pruned_states
|
| 731 |
+
)
|
| 732 |
+
else:
|
| 733 |
+
hidden_states_to_store = (
|
| 734 |
+
pruned_states[sample_indices]
|
| 735 |
+
if sample_indices is not None
|
| 736 |
+
else pruned_states
|
| 737 |
+
)
|
| 738 |
+
if hidden_states_before_norm is not None:
|
| 739 |
+
hidden_states_to_store_before_norm = (
|
| 740 |
+
pruned_states_before_norm[sample_indices]
|
| 741 |
+
if sample_indices is not None
|
| 742 |
+
else pruned_states_before_norm
|
| 743 |
+
)
|
| 744 |
+
else:
|
| 745 |
+
assert False, "Should never reach"
|
| 746 |
+
|
| 747 |
+
if hidden_states_to_store_before_norm is not None:
|
| 748 |
+
# NOTE: when hidden_states_before_norm is provided, we always
|
| 749 |
+
# prefer to return it.
|
| 750 |
+
hidden_states_to_store = hidden_states_to_store_before_norm
|
| 751 |
+
|
| 752 |
+
return hidden_states_to_store
|
| 753 |
+
|
| 754 |
+
def _get_logits(
|
| 755 |
+
self,
|
| 756 |
+
hidden_states: torch.Tensor,
|
| 757 |
+
lm_head: VocabParallelEmbedding,
|
| 758 |
+
logits_metadata: LogitsMetadata,
|
| 759 |
+
embedding_bias: Optional[torch.Tensor] = None,
|
| 760 |
+
use_logits_buffer: bool = True,
|
| 761 |
+
) -> torch.Tensor:
|
| 762 |
+
"""Get logits from hidden_states.
|
| 763 |
+
|
| 764 |
+
If sampled_logits_only is True, it means hidden_states only contain the
|
| 765 |
+
last position (e.g., extend without input logprobs). The caller should
|
| 766 |
+
guarantee the given hidden_states follow this constraint.
|
| 767 |
+
"""
|
| 768 |
+
_trace_e2e_logits(
|
| 769 |
+
"get_logits_enter",
|
| 770 |
+
hidden_shape=tuple(hidden_states.shape),
|
| 771 |
+
dp_gather=self.do_tensor_parallel_all_gather_dp_attn,
|
| 772 |
+
tp_gather=self.do_tensor_parallel_all_gather,
|
| 773 |
+
)
|
| 774 |
+
hidden_states, local_hidden_states = self._gather_dp_attn_hidden_states(
|
| 775 |
+
hidden_states, logits_metadata
|
| 776 |
+
)
|
| 777 |
+
_trace_e2e_logits(
|
| 778 |
+
"dp_hidden_gather_returned",
|
| 779 |
+
global_shape=tuple(hidden_states.shape),
|
| 780 |
+
local_shape=tuple(local_hidden_states.shape),
|
| 781 |
+
)
|
| 782 |
+
|
| 783 |
+
if envs.SGLANG_TRACE_LOGITS_E2E_SYNC.get():
|
| 784 |
+
_trace_e2e_logits("pre_lm_head_sync_enter")
|
| 785 |
+
torch.cuda.synchronize()
|
| 786 |
+
_trace_e2e_logits("pre_lm_head_sync_returned")
|
| 787 |
+
|
| 788 |
+
_trace_e2e_logits("lm_head_enter", hidden_shape=tuple(hidden_states.shape))
|
| 789 |
+
logits = self._compute_lm_head(hidden_states, lm_head, embedding_bias)
|
| 790 |
+
_trace_e2e_logits("lm_head_returned", logits_shape=tuple(logits.shape))
|
| 791 |
+
if envs.SGLANG_TRACE_LOGITS_E2E_SYNC.get():
|
| 792 |
+
_trace_e2e_logits("post_lm_head_sync_enter")
|
| 793 |
+
torch.cuda.synchronize()
|
| 794 |
+
_trace_e2e_logits("post_lm_head_sync_returned")
|
| 795 |
+
|
| 796 |
+
if self.logit_scale is not None:
|
| 797 |
+
logits.mul_(self.logit_scale)
|
| 798 |
+
|
| 799 |
+
used_tp_lm_head_all_to_all = False
|
| 800 |
+
if self.do_tensor_parallel_all_gather:
|
| 801 |
+
_trace_e2e_logits(
|
| 802 |
+
"tp_logits_gather_enter", logits_shape=tuple(logits.shape)
|
| 803 |
+
)
|
| 804 |
+
if self.use_attn_tp_group:
|
| 805 |
+
logits = self._gather_attn_tp_logits(logits)
|
| 806 |
+
elif self._can_use_tp_lm_head_all_to_all(
|
| 807 |
+
logits, local_hidden_states, lm_head, logits_metadata
|
| 808 |
+
):
|
| 809 |
+
logits = self._tp_lm_head_all_to_all(logits)
|
| 810 |
+
used_tp_lm_head_all_to_all = True
|
| 811 |
+
else:
|
| 812 |
+
logits = self._logits_gatherer(logits)
|
| 813 |
+
_trace_e2e_logits(
|
| 814 |
+
"tp_logits_gather_returned", logits_shape=tuple(logits.shape)
|
| 815 |
+
)
|
| 816 |
+
|
| 817 |
+
if not used_tp_lm_head_all_to_all:
|
| 818 |
+
_trace_e2e_logits(
|
| 819 |
+
"dp_logits_scatter_enter", logits_shape=tuple(logits.shape)
|
| 820 |
+
)
|
| 821 |
+
logits = self._scatter_dp_attn_logits(
|
| 822 |
+
logits, local_hidden_states, logits_metadata
|
| 823 |
+
)
|
| 824 |
+
_trace_e2e_logits(
|
| 825 |
+
"dp_logits_scatter_returned", logits_shape=tuple(logits.shape)
|
| 826 |
+
)
|
| 827 |
+
|
| 828 |
+
logits = self._copy_logits_to_buffer(
|
| 829 |
+
logits, logits_metadata, use_buffer=use_logits_buffer
|
| 830 |
+
)
|
| 831 |
+
|
| 832 |
+
if self.final_logit_softcapping:
|
| 833 |
+
if not (_is_npu or _is_cpu):
|
| 834 |
+
fused_softcap(logits, self.final_logit_softcapping)
|
| 835 |
+
else:
|
| 836 |
+
logits = self.final_logit_softcapping * torch.tanh(
|
| 837 |
+
logits / self.final_logit_softcapping
|
| 838 |
+
)
|
| 839 |
+
|
| 840 |
+
return logits
|
| 841 |
+
|
| 842 |
+
def _compute_lm_head(
|
| 843 |
+
self,
|
| 844 |
+
hidden_states: torch.Tensor,
|
| 845 |
+
lm_head: VocabParallelEmbedding,
|
| 846 |
+
embedding_bias: Optional[torch.Tensor] = None,
|
| 847 |
+
) -> torch.Tensor:
|
| 848 |
+
if self.jev_selected_ids is not None:
|
| 849 |
+
if not hasattr(lm_head, "weight") or lm_head.weight.dtype not in (torch.bfloat16, torch.float16, torch.float32) or hasattr(lm_head, "set_lora"):
|
| 850 |
+
raise ValueError("Selected head requires a frozen unquantized output projection")
|
| 851 |
+
if embedding_bias is not None or self.final_logit_softcapping:
|
| 852 |
+
raise ValueError("Selected head does not support output bias or softcapping")
|
| 853 |
+
if self.jev_head_cache is None:
|
| 854 |
+
ids = torch.tensor(self.jev_selected_ids, device=lm_head.weight.device, dtype=torch.long)
|
| 855 |
+
weight = lm_head.weight.index_select(0, ids).contiguous()
|
| 856 |
+
self.jev_head_cache = (ids, weight)
|
| 857 |
+
ids, weight = self.jev_head_cache
|
| 858 |
+
if self.use_fp32_lm_head:
|
| 859 |
+
selected = torch.matmul(hidden_states.float(), weight.float().T)
|
| 860 |
+
else:
|
| 861 |
+
selected = torch.matmul(hidden_states.to(weight.dtype), weight.T)
|
| 862 |
+
logits = torch.full((hidden_states.shape[0], self.vocab_size), -float("inf"), device=hidden_states.device, dtype=selected.dtype)
|
| 863 |
+
return logits.index_copy_(1, ids, selected)
|
| 864 |
+
quant_method = getattr(lm_head, "quant_method", None)
|
| 865 |
+
if hasattr(lm_head, "set_lora") and hasattr(lm_head, "apply_lora"):
|
| 866 |
+
# This is a LoRA-wrapped module, use its forward method
|
| 867 |
+
logits = lm_head(hidden_states)
|
| 868 |
+
elif should_apply_lm_head_quant_method(lm_head, quant_method):
|
| 869 |
+
logits = quant_method.apply(lm_head, hidden_states, embedding_bias)
|
| 870 |
+
elif hasattr(lm_head, "weight"):
|
| 871 |
+
# Normal linear layer
|
| 872 |
+
if self.use_fp32_lm_head:
|
| 873 |
+
# Avoid materializing FP32 copies for same-dtype CUDA FP16/BF16
|
| 874 |
+
# inputs. Retain explicit FP32 casts for unsupported devices or
|
| 875 |
+
# dtype combinations.
|
| 876 |
+
use_mm_out_dtype = (
|
| 877 |
+
hidden_states.is_cuda
|
| 878 |
+
and hidden_states.dtype == lm_head.weight.dtype
|
| 879 |
+
and hidden_states.dtype in (torch.float16, torch.bfloat16)
|
| 880 |
+
)
|
| 881 |
+
if use_mm_out_dtype:
|
| 882 |
+
logits = torch.mm(
|
| 883 |
+
hidden_states,
|
| 884 |
+
lm_head.weight.T,
|
| 885 |
+
out_dtype=torch.float32,
|
| 886 |
+
)
|
| 887 |
+
else:
|
| 888 |
+
logits = torch.matmul(
|
| 889 |
+
hidden_states.to(torch.float32),
|
| 890 |
+
lm_head.weight.to(torch.float32).T,
|
| 891 |
+
)
|
| 892 |
+
elif use_intel_amx_backend(lm_head):
|
| 893 |
+
logits = torch.ops.sgl_kernel.weight_packed_linear(
|
| 894 |
+
hidden_states.to(lm_head.weight.dtype),
|
| 895 |
+
lm_head.weight,
|
| 896 |
+
None, # bias
|
| 897 |
+
True, # is_vnni
|
| 898 |
+
)
|
| 899 |
+
elif self.rl_on_policy_target is not None:
|
| 900 |
+
# Due to tie-weight, we may not be able to change lm_head's weight dtype
|
| 901 |
+
logits = torch.matmul(
|
| 902 |
+
hidden_states.bfloat16(), lm_head.weight.T.bfloat16()
|
| 903 |
+
)
|
| 904 |
+
else:
|
| 905 |
+
logits = torch.matmul(
|
| 906 |
+
hidden_states.to(lm_head.weight.dtype), lm_head.weight.T
|
| 907 |
+
)
|
| 908 |
+
else:
|
| 909 |
+
# GGUF models
|
| 910 |
+
# TODO: use weight_packed_linear for GGUF models
|
| 911 |
+
if self.use_fp32_lm_head:
|
| 912 |
+
with torch.cuda.amp.autocast(enabled=False):
|
| 913 |
+
logits = lm_head.quant_method.apply(
|
| 914 |
+
lm_head, hidden_states.to(torch.float32), embedding_bias
|
| 915 |
+
)
|
| 916 |
+
else:
|
| 917 |
+
logits = lm_head.quant_method.apply(
|
| 918 |
+
lm_head, hidden_states, embedding_bias
|
| 919 |
+
)
|
| 920 |
+
return logits
|
| 921 |
+
|
| 922 |
+
def _gather_dp_attn_hidden_states(
|
| 923 |
+
self, hidden_states: torch.Tensor, logits_metadata: LogitsMetadata
|
| 924 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 925 |
+
if self.do_tensor_parallel_all_gather_dp_attn:
|
| 926 |
+
_trace_e2e_logits(
|
| 927 |
+
"dp_metadata_enter",
|
| 928 |
+
local_shape=tuple(hidden_states.shape),
|
| 929 |
+
global_counts_cpu=logits_metadata.global_num_tokens_for_logprob_cpu,
|
| 930 |
+
)
|
| 931 |
+
logits_metadata.compute_dp_attention_metadata()
|
| 932 |
+
_trace_e2e_logits(
|
| 933 |
+
"dp_metadata_returned",
|
| 934 |
+
buffer_shape=tuple(logits_metadata.gathered_buffer.shape),
|
| 935 |
+
local_start=logits_metadata.dp_local_start_pos,
|
| 936 |
+
local_tokens=logits_metadata.dp_local_num_tokens,
|
| 937 |
+
)
|
| 938 |
+
local_hidden_states = hidden_states
|
| 939 |
+
hidden_states = logits_metadata.gathered_buffer
|
| 940 |
+
_trace_e2e_logits(
|
| 941 |
+
"dp_hidden_gather_enter",
|
| 942 |
+
global_shape=tuple(hidden_states.shape),
|
| 943 |
+
local_shape=tuple(local_hidden_states.shape),
|
| 944 |
+
)
|
| 945 |
+
dp_gather_replicate(hidden_states, local_hidden_states, logits_metadata)
|
| 946 |
+
_trace_e2e_logits("dp_hidden_gather_collective_returned")
|
| 947 |
+
return hidden_states, local_hidden_states
|
| 948 |
+
return hidden_states, hidden_states
|
| 949 |
+
|
| 950 |
+
def _gather_attn_tp_logits(self, logits: torch.Tensor) -> torch.Tensor:
|
| 951 |
+
if self.vocab_size % self.attn_tp_size == 0:
|
| 952 |
+
global_logits = torch.empty(
|
| 953 |
+
(
|
| 954 |
+
self.attn_tp_size,
|
| 955 |
+
logits.shape[0],
|
| 956 |
+
self.vocab_size // self.attn_tp_size,
|
| 957 |
+
),
|
| 958 |
+
device=logits.device,
|
| 959 |
+
dtype=logits.dtype,
|
| 960 |
+
)
|
| 961 |
+
attn_tp_all_gather_into_tensor(global_logits, logits)
|
| 962 |
+
global_logits = global_logits.permute(1, 0, 2).reshape(
|
| 963 |
+
logits.shape[0], self.vocab_size
|
| 964 |
+
)
|
| 965 |
+
else:
|
| 966 |
+
global_logits = torch.empty(
|
| 967 |
+
(self.vocab_size, logits.shape[0]),
|
| 968 |
+
device=logits.device,
|
| 969 |
+
dtype=logits.dtype,
|
| 970 |
+
)
|
| 971 |
+
global_logits = global_logits.T
|
| 972 |
+
attn_tp_all_gather(
|
| 973 |
+
list(global_logits.tensor_split(self.attn_tp_size, dim=-1)),
|
| 974 |
+
logits,
|
| 975 |
+
)
|
| 976 |
+
return global_logits
|
| 977 |
+
|
| 978 |
+
def _can_use_tp_lm_head_all_to_all(
|
| 979 |
+
self,
|
| 980 |
+
logits: torch.Tensor,
|
| 981 |
+
local_hidden_states: torch.Tensor,
|
| 982 |
+
lm_head: VocabParallelEmbedding,
|
| 983 |
+
logits_metadata: LogitsMetadata,
|
| 984 |
+
) -> bool:
|
| 985 |
+
if not self.use_tp_lm_head_all_to_all:
|
| 986 |
+
return False
|
| 987 |
+
|
| 988 |
+
tp_size = get_parallel().tp_size
|
| 989 |
+
base_lm_head = getattr(lm_head, "base_layer", lm_head)
|
| 990 |
+
if getattr(base_lm_head, "tp_size", None) != tp_size:
|
| 991 |
+
# Tied embeddings may be replicated across DP ranks (tp_size=1),
|
| 992 |
+
# even though the logits processor runs in a larger global TP
|
| 993 |
+
# group. Such logits are full-vocabulary rather than TP shards and
|
| 994 |
+
# therefore do not satisfy the all-to-all layout contract.
|
| 995 |
+
return False
|
| 996 |
+
|
| 997 |
+
# Every participant must make the same collective choice. Decode CUDA
|
| 998 |
+
# graphs omit CPU counts and fill every GPU count with the same padded
|
| 999 |
+
# bucket size. Eager batches carry the same global CPU count list on
|
| 1000 |
+
# every rank, so they are also safe when all entries are equal.
|
| 1001 |
+
global_counts_cpu = logits_metadata.global_num_tokens_for_logprob_cpu
|
| 1002 |
+
is_equal_padded_graph_layout = global_counts_cpu is None and (
|
| 1003 |
+
logits_metadata.global_num_tokens_for_logprob_gpu is not None
|
| 1004 |
+
)
|
| 1005 |
+
is_equal_eager_layout = (
|
| 1006 |
+
global_counts_cpu is not None
|
| 1007 |
+
and len(global_counts_cpu) == tp_size
|
| 1008 |
+
and len(global_counts_cpu) > 0
|
| 1009 |
+
and all(count == global_counts_cpu[0] for count in global_counts_cpu)
|
| 1010 |
+
)
|
| 1011 |
+
if not (is_equal_padded_graph_layout or is_equal_eager_layout):
|
| 1012 |
+
return False
|
| 1013 |
+
|
| 1014 |
+
local_rows = local_hidden_states.shape[0]
|
| 1015 |
+
return local_rows > 0 and logits.shape[0] == local_rows * tp_size
|
| 1016 |
+
|
| 1017 |
+
def _tp_lm_head_all_to_all(self, logits: torch.Tensor) -> torch.Tensor:
|
| 1018 |
+
"""Exchange only the row block owned by each destination DP rank."""
|
| 1019 |
+
logits = logits.contiguous()
|
| 1020 |
+
all_to_all_output = torch.empty_like(logits)
|
| 1021 |
+
get_tp_group().all_to_all_single(all_to_all_output.view(-1), logits.view(-1))
|
| 1022 |
+
return _reassemble_tp_lm_head_all_to_all_output(
|
| 1023 |
+
all_to_all_output, get_parallel().tp_size
|
| 1024 |
+
)
|
| 1025 |
+
|
| 1026 |
+
def _scatter_dp_attn_logits(
|
| 1027 |
+
self,
|
| 1028 |
+
logits: torch.Tensor,
|
| 1029 |
+
local_hidden_states: torch.Tensor,
|
| 1030 |
+
logits_metadata: LogitsMetadata,
|
| 1031 |
+
) -> torch.Tensor:
|
| 1032 |
+
if self.do_tensor_parallel_all_gather_dp_attn:
|
| 1033 |
+
global_logits = logits
|
| 1034 |
+
logits = torch.empty(
|
| 1035 |
+
(local_hidden_states.shape[0], global_logits.shape[1]),
|
| 1036 |
+
device=global_logits.device,
|
| 1037 |
+
dtype=global_logits.dtype,
|
| 1038 |
+
)
|
| 1039 |
+
dp_scatter(logits, global_logits, logits_metadata)
|
| 1040 |
+
return logits
|
| 1041 |
+
|
| 1042 |
+
def _copy_logits_to_buffer(
|
| 1043 |
+
self,
|
| 1044 |
+
logits: torch.Tensor,
|
| 1045 |
+
logits_metadata: LogitsMetadata,
|
| 1046 |
+
use_buffer: bool = True,
|
| 1047 |
+
) -> torch.Tensor:
|
| 1048 |
+
logits_buffer = logits_metadata.next_token_logits_buffer if use_buffer else None
|
| 1049 |
+
if logits.shape[-1] > self.vocab_size:
|
| 1050 |
+
logits = logits[:, : self.vocab_size]
|
| 1051 |
+
logits_width = logits.shape[-1]
|
| 1052 |
+
# The shared logits buffer is keyed by vocab width and rows; skip it
|
| 1053 |
+
# when this batch has a different logits shape than the graph buffer.
|
| 1054 |
+
if logits_buffer is not None and tuple(logits_buffer.shape) == tuple(
|
| 1055 |
+
logits.shape
|
| 1056 |
+
):
|
| 1057 |
+
assert logits_buffer.dtype == torch.float
|
| 1058 |
+
logits_buffer.copy_(logits)
|
| 1059 |
+
logits = logits_buffer
|
| 1060 |
+
else:
|
| 1061 |
+
logits = logits.float()
|
| 1062 |
+
return logits
|
| 1063 |
+
|
| 1064 |
+
def _get_dllm_logits(
|
| 1065 |
+
self,
|
| 1066 |
+
hidden_states: torch.Tensor,
|
| 1067 |
+
lm_head: VocabParallelEmbedding,
|
| 1068 |
+
logits_metadata: LogitsMetadata,
|
| 1069 |
+
) -> LogitsProcessorOutput:
|
| 1070 |
+
assert self.return_full_logits
|
| 1071 |
+
full_logits = self._get_logits(hidden_states, lm_head, logits_metadata)
|
| 1072 |
+
return LogitsProcessorOutput(
|
| 1073 |
+
full_logits=full_logits,
|
| 1074 |
+
next_token_logits=None,
|
| 1075 |
+
)
|
| 1076 |
+
|
| 1077 |
+
def compute_logprobs_for_multi_item_scoring(
|
| 1078 |
+
self,
|
| 1079 |
+
input_ids,
|
| 1080 |
+
hidden_states,
|
| 1081 |
+
lm_head: VocabParallelEmbedding,
|
| 1082 |
+
logits_metadata: Union[LogitsMetadata, ForwardBatch],
|
| 1083 |
+
multi_item_delimiter_indices: List[torch.Tensor],
|
| 1084 |
+
):
|
| 1085 |
+
"""
|
| 1086 |
+
Compute logprobs for multi-item scoring using pre-computed delimiter indices.
|
| 1087 |
+
|
| 1088 |
+
Sequence format: Query<delimiter>Item1<delimiter>Item2<delimiter>...
|
| 1089 |
+
Scoring positions: Extracts logprobs at positions before each <delimiter>
|
| 1090 |
+
|
| 1091 |
+
Args:
|
| 1092 |
+
input_ids: Input token IDs. Shape: [total_sequence_length].
|
| 1093 |
+
hidden_states: Hidden states from the model. Shape: [sequence_length, hidden_dim].
|
| 1094 |
+
lm_head: Language model head for computing logits.
|
| 1095 |
+
logits_metadata: Metadata containing batch info and logprob specs.
|
| 1096 |
+
multi_item_delimiter_indices: Pre-computed delimiter positions per request (CPU tensors).
|
| 1097 |
+
"""
|
| 1098 |
+
# Compute positions just before each delimiter.
|
| 1099 |
+
# Build offset-adjusted indices on CPU, then do a single CPU→GPU transfer.
|
| 1100 |
+
device = input_ids.device
|
| 1101 |
+
all_tensors = []
|
| 1102 |
+
if logits_metadata.extend_seq_lens_cpu is not None:
|
| 1103 |
+
offset = 0
|
| 1104 |
+
for req_seq_len, indices_tensor in zip(
|
| 1105 |
+
logits_metadata.extend_seq_lens_cpu, multi_item_delimiter_indices
|
| 1106 |
+
):
|
| 1107 |
+
if len(indices_tensor) > 0:
|
| 1108 |
+
# Note: if the first delimiter is at position 0 (empty query),
|
| 1109 |
+
# indices - 1 wraps to -1. This is harmless — the first
|
| 1110 |
+
# delimiter entry is always discarded by
|
| 1111 |
+
# _process_multi_item_scoring_results.
|
| 1112 |
+
all_tensors.append(indices_tensor + (offset - 1))
|
| 1113 |
+
offset += req_seq_len
|
| 1114 |
+
else:
|
| 1115 |
+
all_tensors.append(multi_item_delimiter_indices[0] - 1)
|
| 1116 |
+
multi_item_indices = torch.cat(all_tensors).to(device, non_blocking=True)
|
| 1117 |
+
|
| 1118 |
+
# Extract hidden states at delimiter positions for multi-item scoring
|
| 1119 |
+
sliced_hidden = hidden_states[multi_item_indices]
|
| 1120 |
+
|
| 1121 |
+
sliced_logits = self._get_logits(sliced_hidden, lm_head, logits_metadata)
|
| 1122 |
+
sliced_logprobs = torch.nn.functional.log_softmax(sliced_logits, dim=-1)
|
| 1123 |
+
|
| 1124 |
+
# Initialize return values
|
| 1125 |
+
input_token_ids_logprobs_val = []
|
| 1126 |
+
input_token_ids_logprobs_idx = []
|
| 1127 |
+
input_top_logprobs_val = None
|
| 1128 |
+
input_top_logprobs_idx = None
|
| 1129 |
+
|
| 1130 |
+
# Recalculate extend_logprob_pruned_lens_cpu to match delimiter counts per request
|
| 1131 |
+
if (
|
| 1132 |
+
logits_metadata.token_ids_logprobs
|
| 1133 |
+
or logits_metadata.extend_return_top_logprob
|
| 1134 |
+
):
|
| 1135 |
+
logits_metadata.extend_logprob_pruned_lens_cpu = [
|
| 1136 |
+
len(t) for t in multi_item_delimiter_indices
|
| 1137 |
+
]
|
| 1138 |
+
|
| 1139 |
+
# Get the logprobs of specified token ids
|
| 1140 |
+
if logits_metadata.extend_token_ids_logprob:
|
| 1141 |
+
(
|
| 1142 |
+
input_token_ids_logprobs_val,
|
| 1143 |
+
input_token_ids_logprobs_idx,
|
| 1144 |
+
) = get_token_ids_logprobs_raw(
|
| 1145 |
+
sliced_logprobs,
|
| 1146 |
+
logits_metadata.token_ids_logprobs,
|
| 1147 |
+
stage=LogprobStage.PREFILL,
|
| 1148 |
+
extend_logprob_pruned_lens_cpu=logits_metadata.extend_logprob_pruned_lens_cpu,
|
| 1149 |
+
no_copy_to_cpu=True,
|
| 1150 |
+
)
|
| 1151 |
+
|
| 1152 |
+
# Get the logprob of top-k tokens
|
| 1153 |
+
if logits_metadata.extend_return_top_logprob:
|
| 1154 |
+
(
|
| 1155 |
+
input_top_logprobs_val,
|
| 1156 |
+
input_top_logprobs_idx,
|
| 1157 |
+
) = get_top_logprobs_raw(
|
| 1158 |
+
sliced_logprobs,
|
| 1159 |
+
logits_metadata.top_logprobs_nums,
|
| 1160 |
+
stage=LogprobStage.PREFILL,
|
| 1161 |
+
extend_logprob_pruned_lens_cpu=logits_metadata.extend_logprob_pruned_lens_cpu,
|
| 1162 |
+
)
|
| 1163 |
+
|
| 1164 |
+
# MIS scores come from input_token_ids_logprobs_val (label-token logprobs),
|
| 1165 |
+
# not from per-position input_token_logprobs. However, the shared logprob
|
| 1166 |
+
# pipeline (add_input_logprob_return_values) asserts input_token_logprobs is
|
| 1167 |
+
# non-None, converts it to a tuple, slices it, and validates its length —
|
| 1168 |
+
# all before score_request() ever sees the result. We can't set it to None
|
| 1169 |
+
# without changing those shared asserts, so we fill with zeros to satisfy
|
| 1170 |
+
# the pipeline. score_request() ignores this field entirely.
|
| 1171 |
+
input_token_logprobs = torch.zeros(multi_item_indices.shape[0], device=device)
|
| 1172 |
+
|
| 1173 |
+
return LogitsProcessorOutput(
|
| 1174 |
+
next_token_logits=None,
|
| 1175 |
+
input_token_logprobs=input_token_logprobs,
|
| 1176 |
+
input_top_logprobs_val=input_top_logprobs_val,
|
| 1177 |
+
input_top_logprobs_idx=input_top_logprobs_idx,
|
| 1178 |
+
input_token_ids_logprobs_val=input_token_ids_logprobs_val,
|
| 1179 |
+
input_token_ids_logprobs_idx=input_token_ids_logprobs_idx,
|
| 1180 |
+
mm_input_embeds=logits_metadata.mm_input_embeds,
|
| 1181 |
+
)
|
| 1182 |
+
|
| 1183 |
+
|
| 1184 |
+
def _reassemble_tp_lm_head_all_to_all_output(
|
| 1185 |
+
all_to_all_output: torch.Tensor, tp_size: int
|
| 1186 |
+
) -> torch.Tensor:
|
| 1187 |
+
"""Convert source-major all-to-all output to row-major full-vocab logits.
|
| 1188 |
+
|
| 1189 |
+
Each source TP rank contributes ``[local_rows, vocab_shard]`` for this
|
| 1190 |
+
destination DP rank. ``all_to_all_single`` concatenates those contributions
|
| 1191 |
+
along dim 0, while the sampler expects the vocab shards concatenated along
|
| 1192 |
+
dim 1.
|
| 1193 |
+
"""
|
| 1194 |
+
assert all_to_all_output.shape[0] % tp_size == 0
|
| 1195 |
+
local_rows = all_to_all_output.shape[0] // tp_size
|
| 1196 |
+
vocab_shard = all_to_all_output.shape[1]
|
| 1197 |
+
return (
|
| 1198 |
+
all_to_all_output.view(tp_size, local_rows, vocab_shard)
|
| 1199 |
+
.permute(1, 0, 2)
|
| 1200 |
+
.reshape(local_rows, tp_size * vocab_shard)
|
| 1201 |
+
)
|
| 1202 |
+
|
| 1203 |
+
|
| 1204 |
+
def _has_lm_head_runtime_attrs(lm_head, attr_names: Tuple[str, ...]) -> bool:
|
| 1205 |
+
return all(hasattr(lm_head, attr_name) for attr_name in attr_names)
|
| 1206 |
+
|
| 1207 |
+
|
| 1208 |
+
def should_apply_lm_head_quant_method(lm_head, quant_method) -> bool:
|
| 1209 |
+
if (
|
| 1210 |
+
quant_method is None
|
| 1211 |
+
or not hasattr(lm_head, "weight")
|
| 1212 |
+
or not callable(getattr(quant_method, "apply", None))
|
| 1213 |
+
):
|
| 1214 |
+
return False
|
| 1215 |
+
|
| 1216 |
+
method_name = type(quant_method).__name__
|
| 1217 |
+
if method_name in _UNQUANTIZED_LM_HEAD_METHODS:
|
| 1218 |
+
return False
|
| 1219 |
+
|
| 1220 |
+
# Some draft models share an unquantized target lm_head tensor while still
|
| 1221 |
+
# carrying the draft model's stale ModelOpt quant_method. Only use the
|
| 1222 |
+
# ModelOpt lm_head kernel when the runtime quantization state matches it.
|
| 1223 |
+
if method_name == "ModelOptFp4LinearMethod":
|
| 1224 |
+
if quant_method.quant_mode == "w4a16":
|
| 1225 |
+
return lm_head.weight.dtype == torch.uint8 and _has_lm_head_runtime_attrs(
|
| 1226 |
+
lm_head,
|
| 1227 |
+
(
|
| 1228 |
+
"weight_scale_interleaved",
|
| 1229 |
+
"alpha",
|
| 1230 |
+
"input_size_per_partition",
|
| 1231 |
+
"output_size_per_partition",
|
| 1232 |
+
),
|
| 1233 |
+
)
|
| 1234 |
+
if lm_head.weight.dtype == torch.int32 and _has_lm_head_runtime_attrs(
|
| 1235 |
+
lm_head,
|
| 1236 |
+
(
|
| 1237 |
+
"weight_scale",
|
| 1238 |
+
"weight_global_scale",
|
| 1239 |
+
"workspace",
|
| 1240 |
+
"input_size_per_partition",
|
| 1241 |
+
"output_size_per_partition",
|
| 1242 |
+
),
|
| 1243 |
+
):
|
| 1244 |
+
return True
|
| 1245 |
+
return lm_head.weight.dtype == torch.uint8 and _has_lm_head_runtime_attrs(
|
| 1246 |
+
lm_head,
|
| 1247 |
+
(
|
| 1248 |
+
"weight_scale_interleaved",
|
| 1249 |
+
"alpha",
|
| 1250 |
+
"input_scale_inv",
|
| 1251 |
+
"input_size_per_partition",
|
| 1252 |
+
"output_size_per_partition",
|
| 1253 |
+
),
|
| 1254 |
+
)
|
| 1255 |
+
if method_name == "ModelOptNvFp4A16LinearMethod":
|
| 1256 |
+
return lm_head.weight.dtype == torch.int32 and _has_lm_head_runtime_attrs(
|
| 1257 |
+
lm_head,
|
| 1258 |
+
(
|
| 1259 |
+
"weight_scale",
|
| 1260 |
+
"weight_global_scale",
|
| 1261 |
+
"workspace",
|
| 1262 |
+
"input_size_per_partition",
|
| 1263 |
+
"output_size_per_partition",
|
| 1264 |
+
),
|
| 1265 |
+
)
|
| 1266 |
+
if method_name == "ModelOptFp8LinearMethod":
|
| 1267 |
+
return (
|
| 1268 |
+
lm_head.weight.dtype == torch.float8_e4m3fn
|
| 1269 |
+
and _has_lm_head_runtime_attrs(lm_head, ("weight_scale", "input_scale"))
|
| 1270 |
+
)
|
| 1271 |
+
|
| 1272 |
+
return True
|
runtime/patches/sglang-qwen35-text.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 Qwen Team
|
| 2 |
+
# Copyright 2025 SGLang Team
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
# ==============================================================================
|
| 15 |
+
|
| 16 |
+
import logging
|
| 17 |
+
from typing import Iterable, Optional, Set, Tuple, Union
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
from torch import nn
|
| 21 |
+
|
| 22 |
+
from sglang.srt.distributed import get_pp_group
|
| 23 |
+
from sglang.srt.eplb.expert_location import ModelConfigForExpertLocation
|
| 24 |
+
from sglang.srt.layers.logits_processor import LogitsProcessor
|
| 25 |
+
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
| 26 |
+
from sglang.srt.layers.utils import PPMissingLayer
|
| 27 |
+
from sglang.srt.layers.vocab_parallel_embedding import ParallelLMHead
|
| 28 |
+
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, PPProxyTensors
|
| 29 |
+
from sglang.srt.model_loader.weight_utils import default_weight_loader
|
| 30 |
+
from sglang.srt.models import qwen3_5
|
| 31 |
+
from sglang.srt.models.qwen2_moe import Qwen2MoeSparseMoeBlock
|
| 32 |
+
from sglang.srt.runtime_context import get_parallel
|
| 33 |
+
from sglang.srt.utils import LazyValue, add_prefix
|
| 34 |
+
|
| 35 |
+
logger = logging.getLogger(__name__)
|
| 36 |
+
|
| 37 |
+
_MODEL_PREFIX = "model."
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class Qwen3_5ForCausalLM(nn.Module):
|
| 41 |
+
body_cls = qwen3_5.Qwen3_5ForCausalLM
|
| 42 |
+
|
| 43 |
+
packed_modules_mapping = qwen3_5.Qwen3_5ForCausalLM.packed_modules_mapping
|
| 44 |
+
supported_lora_modules = qwen3_5.Qwen3_5ForCausalLM.supported_lora_modules
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def shared_experts_fusion_disable_reason(cls, hf_config, quant_config):
|
| 48 |
+
# The body decides; it is handed this config and quantization verbatim.
|
| 49 |
+
return cls.body_cls.shared_experts_fusion_disable_reason(
|
| 50 |
+
hf_config, quant_config
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
def __init__(
|
| 54 |
+
self,
|
| 55 |
+
config,
|
| 56 |
+
quant_config: Optional[QuantizationConfig] = None,
|
| 57 |
+
prefix: str = "",
|
| 58 |
+
) -> None:
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.config = config
|
| 61 |
+
self.quant_config = quant_config
|
| 62 |
+
self.pp_group = get_pp_group()
|
| 63 |
+
|
| 64 |
+
if quant_config is not None and hasattr(quant_config, "packed_modules_mapping"):
|
| 65 |
+
quant_config.packed_modules_mapping = self.packed_modules_mapping
|
| 66 |
+
|
| 67 |
+
self.model = self.body_cls(
|
| 68 |
+
config=config,
|
| 69 |
+
quant_config=quant_config,
|
| 70 |
+
prefix=add_prefix("model", prefix),
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
if self.pp_group.is_last_rank:
|
| 74 |
+
if self.pp_group.world_size == 1 and config.tie_word_embeddings:
|
| 75 |
+
self.lm_head = self.model.embed_tokens
|
| 76 |
+
else:
|
| 77 |
+
self.lm_head = ParallelLMHead(
|
| 78 |
+
config.vocab_size,
|
| 79 |
+
config.hidden_size,
|
| 80 |
+
quant_config=quant_config,
|
| 81 |
+
org_num_embeddings=config.vocab_size,
|
| 82 |
+
prefix=add_prefix("lm_head", prefix),
|
| 83 |
+
use_attn_tp_group=get_parallel().enable_dp_lm_head,
|
| 84 |
+
)
|
| 85 |
+
else:
|
| 86 |
+
self.lm_head = PPMissingLayer()
|
| 87 |
+
|
| 88 |
+
self.logits_processor = LogitsProcessor(config)
|
| 89 |
+
|
| 90 |
+
# Text-only checkpoints retain mrope_section, but identical position rows
|
| 91 |
+
# make its rotary embedding equivalent to 1-D RoPE.
|
| 92 |
+
rope_config = getattr(config, "rope_parameters", None) or getattr(
|
| 93 |
+
config, "rope_scaling", None
|
| 94 |
+
)
|
| 95 |
+
self.is_mrope_enabled = bool(rope_config) and "mrope_section" in rope_config
|
| 96 |
+
|
| 97 |
+
self.capture_aux_hidden_states = False
|
| 98 |
+
|
| 99 |
+
@property
|
| 100 |
+
def start_layer(self) -> int:
|
| 101 |
+
return self.model.start_layer
|
| 102 |
+
|
| 103 |
+
@property
|
| 104 |
+
def end_layer(self) -> int:
|
| 105 |
+
return self.model.end_layer
|
| 106 |
+
|
| 107 |
+
def get_input_embeddings(self) -> nn.Embedding:
|
| 108 |
+
return self.model.embed_tokens
|
| 109 |
+
|
| 110 |
+
def prepare_before_cuda_graph_capture(self, model_runner) -> None:
|
| 111 |
+
"""Forward model-owned warmup to the text backbone."""
|
| 112 |
+
self.model.prepare_before_cuda_graph_capture(model_runner)
|
| 113 |
+
|
| 114 |
+
def set_dflash_layers_to_capture(self, layer_ids: list[int]) -> None:
|
| 115 |
+
if self.pp_group.world_size > 1:
|
| 116 |
+
raise NotImplementedError("DFLASH/DSPARK aux hidden capture requires PP=1.")
|
| 117 |
+
num_layers = len(self.model.layers)
|
| 118 |
+
if sorted(set(layer_ids)) != list(layer_ids) or not all(
|
| 119 |
+
0 <= layer_id < num_layers - 1 for layer_id in layer_ids
|
| 120 |
+
):
|
| 121 |
+
raise ValueError(
|
| 122 |
+
"target_layer_ids must be unique, strictly increasing, and in "
|
| 123 |
+
f"[0, {num_layers - 1}); got {layer_ids}"
|
| 124 |
+
)
|
| 125 |
+
self.capture_aux_hidden_states = True
|
| 126 |
+
self.model.set_dflash_layers_to_capture(
|
| 127 |
+
[layer_id + 1 for layer_id in layer_ids]
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
def get_embed_and_head(self):
|
| 131 |
+
# PP splits embedding and lm_head across first/last stages; the draft keeps
|
| 132 |
+
# its own copy of whichever half its stage cannot receive.
|
| 133 |
+
embed = (
|
| 134 |
+
None
|
| 135 |
+
if isinstance(self.model.embed_tokens, PPMissingLayer)
|
| 136 |
+
else self.model.embed_tokens.weight
|
| 137 |
+
)
|
| 138 |
+
head = None if isinstance(self.lm_head, PPMissingLayer) else self.lm_head.weight
|
| 139 |
+
return embed, head
|
| 140 |
+
|
| 141 |
+
def set_embed_and_head(self, embed, head):
|
| 142 |
+
del self.model.embed_tokens.weight
|
| 143 |
+
del self.lm_head.weight
|
| 144 |
+
self.model.embed_tokens.weight = embed
|
| 145 |
+
self.lm_head.weight = head
|
| 146 |
+
torch.cuda.empty_cache()
|
| 147 |
+
torch.cuda.synchronize()
|
| 148 |
+
|
| 149 |
+
def set_dflash_layers_to_capture(self, layers_to_capture: list[int]):
|
| 150 |
+
if not self.pp_group.is_last_rank:
|
| 151 |
+
return
|
| 152 |
+
if layers_to_capture is None:
|
| 153 |
+
raise ValueError(
|
| 154 |
+
"DFLASH requires explicit layer ids for aux hidden capture."
|
| 155 |
+
)
|
| 156 |
+
self.capture_aux_hidden_states = True
|
| 157 |
+
self.model.set_dflash_layers_to_capture(layers_to_capture)
|
| 158 |
+
|
| 159 |
+
@torch.no_grad()
|
| 160 |
+
def forward(
|
| 161 |
+
self,
|
| 162 |
+
input_ids: torch.Tensor,
|
| 163 |
+
positions: torch.Tensor,
|
| 164 |
+
forward_batch: ForwardBatch,
|
| 165 |
+
input_embeds: Optional[torch.Tensor] = None,
|
| 166 |
+
pp_proxy_tensors: Optional[PPProxyTensors] = None,
|
| 167 |
+
**kwargs,
|
| 168 |
+
) -> Union[torch.Tensor, PPProxyTensors]:
|
| 169 |
+
if self.is_mrope_enabled:
|
| 170 |
+
positions = forward_batch.mrope_positions
|
| 171 |
+
|
| 172 |
+
hidden_states = self.model(
|
| 173 |
+
input_ids,
|
| 174 |
+
positions,
|
| 175 |
+
forward_batch,
|
| 176 |
+
input_embeds,
|
| 177 |
+
pp_proxy_tensors=pp_proxy_tensors,
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
if not self.pp_group.is_last_rank:
|
| 181 |
+
return hidden_states
|
| 182 |
+
|
| 183 |
+
aux_hidden_states = None
|
| 184 |
+
if isinstance(hidden_states, tuple):
|
| 185 |
+
hidden_states, aux_hidden_states = hidden_states
|
| 186 |
+
|
| 187 |
+
return self.logits_processor(
|
| 188 |
+
input_ids, hidden_states, self.lm_head, forward_batch, aux_hidden_states
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]) -> Set[str]:
|
| 192 |
+
params_dict = dict(self.named_parameters())
|
| 193 |
+
loaded_params: Set[str] = set()
|
| 194 |
+
|
| 195 |
+
# Keep prefix stripping lazy: materializing mmap-backed checkpoint tensors
|
| 196 |
+
# across model processes can exceed host memory before GPU copies finish.
|
| 197 |
+
def portable_weights():
|
| 198 |
+
# HF native export packs experts and shared gate/up matrices. Expand
|
| 199 |
+
# lazily into the loader's per-expert format; no tensor values change.
|
| 200 |
+
for name, tensor in weights:
|
| 201 |
+
name = name.replace("model.language_model.", "model.")
|
| 202 |
+
if ".mlp.experts.gate_up_proj" in name:
|
| 203 |
+
stem = name.split(".mlp.experts.gate_up_proj")[0]
|
| 204 |
+
for expert in range(tensor.shape[0]):
|
| 205 |
+
gate, up = tensor[expert].chunk(2, dim=0)
|
| 206 |
+
yield f"{stem}.mlp.experts.{expert}.gate_proj.weight", gate
|
| 207 |
+
yield f"{stem}.mlp.experts.{expert}.up_proj.weight", up
|
| 208 |
+
elif ".mlp.experts.down_proj" in name:
|
| 209 |
+
stem = name.split(".mlp.experts.down_proj")[0]
|
| 210 |
+
for expert in range(tensor.shape[0]):
|
| 211 |
+
yield f"{stem}.mlp.experts.{expert}.down_proj.weight", tensor[expert]
|
| 212 |
+
elif ".mlp.shared_expert.gate_up_proj.weight" in name:
|
| 213 |
+
gate, up = tensor.chunk(2, dim=0)
|
| 214 |
+
yield name.replace("gate_up_proj", "gate_proj"), gate
|
| 215 |
+
yield name.replace("gate_up_proj", "up_proj"), up
|
| 216 |
+
else:
|
| 217 |
+
yield name, tensor
|
| 218 |
+
|
| 219 |
+
def body_weights():
|
| 220 |
+
fused_shared = max((getattr(layer.mlp, "num_fused_shared_experts", 0) for layer in self.model.layers), default=0)
|
| 221 |
+
for name, loaded_weight in portable_weights():
|
| 222 |
+
if fused_shared and ".mlp.shared_expert." in name:
|
| 223 |
+
name = name.replace(".mlp.shared_expert.", f".mlp.experts.{self.config.num_experts}.")
|
| 224 |
+
if name.startswith(_MODEL_PREFIX):
|
| 225 |
+
yield name[len(_MODEL_PREFIX) :], loaded_weight
|
| 226 |
+
elif name == "lm_head.weight":
|
| 227 |
+
if self.config.tie_word_embeddings:
|
| 228 |
+
continue
|
| 229 |
+
if "lm_head.weight" not in params_dict:
|
| 230 |
+
continue
|
| 231 |
+
param = params_dict["lm_head.weight"]
|
| 232 |
+
weight_loader = getattr(
|
| 233 |
+
param, "weight_loader", default_weight_loader
|
| 234 |
+
)
|
| 235 |
+
weight_loader(param, loaded_weight)
|
| 236 |
+
loaded_params.add("lm_head.weight")
|
| 237 |
+
|
| 238 |
+
body_loaded = self.model.load_weights(body_weights())
|
| 239 |
+
loaded_params.update(f"{_MODEL_PREFIX}{n}" for n in body_loaded)
|
| 240 |
+
|
| 241 |
+
if self.config.tie_word_embeddings and self.pp_group.is_last_rank:
|
| 242 |
+
loaded_params.add("lm_head.weight")
|
| 243 |
+
|
| 244 |
+
missing = set(params_dict) - loaded_params
|
| 245 |
+
if (self.quant_config is not None
|
| 246 |
+
and self.quant_config.get_name() == "fp8"
|
| 247 |
+
and not self.quant_config.is_checkpoint_fp8_serialized):
|
| 248 |
+
# Online BF16->FP8 creates these scales itself. They are deliberately
|
| 249 |
+
# absent from the source BF16 checkpoint; all model weights still
|
| 250 |
+
# have to load. Do not apply this exception to serialized FP8.
|
| 251 |
+
generated_suffixes = (
|
| 252 |
+
".mlp.experts.w13_weight_scale", ".mlp.experts.w2_weight_scale",
|
| 253 |
+
".mlp.experts.w13_weight_scale1", ".mlp.experts.w2_weight_scale1",
|
| 254 |
+
".attn.k_scale", ".attn.v_scale",
|
| 255 |
+
)
|
| 256 |
+
generated = {name for name in missing if name.endswith(generated_suffixes)}
|
| 257 |
+
missing -= generated
|
| 258 |
+
if missing:
|
| 259 |
+
raise RuntimeError(f"Incomplete text checkpoint load: {sorted(missing)[:20]}")
|
| 260 |
+
return loaded_params
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
class Qwen3_5MoeForCausalLM(Qwen3_5ForCausalLM):
|
| 264 |
+
body_cls = qwen3_5.Qwen3_5MoeForCausalLM
|
| 265 |
+
|
| 266 |
+
packed_modules_mapping = qwen3_5.Qwen3_5MoeForCausalLM.packed_modules_mapping
|
| 267 |
+
supported_lora_modules = qwen3_5.Qwen3_5MoeForCausalLM.supported_lora_modules
|
| 268 |
+
|
| 269 |
+
def __init__(
|
| 270 |
+
self,
|
| 271 |
+
config,
|
| 272 |
+
quant_config: Optional[QuantizationConfig] = None,
|
| 273 |
+
prefix: str = "",
|
| 274 |
+
) -> None:
|
| 275 |
+
super().__init__(config=config, quant_config=quant_config, prefix=prefix)
|
| 276 |
+
|
| 277 |
+
self._routed_experts_weights_of_layer = LazyValue(
|
| 278 |
+
lambda: {
|
| 279 |
+
layer_id: layer.mlp.get_moe_weights()
|
| 280 |
+
for layer_id, layer in enumerate(self.model.layers)
|
| 281 |
+
if isinstance(layer.mlp, Qwen2MoeSparseMoeBlock)
|
| 282 |
+
}
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
@property
|
| 286 |
+
def routed_experts_weights_of_layer(self):
|
| 287 |
+
return self._routed_experts_weights_of_layer.value
|
| 288 |
+
|
| 289 |
+
@classmethod
|
| 290 |
+
def get_model_config_for_expert_location(cls, config):
|
| 291 |
+
return ModelConfigForExpertLocation(
|
| 292 |
+
num_layers=config.num_hidden_layers,
|
| 293 |
+
num_logical_experts=config.num_experts,
|
| 294 |
+
num_groups=None,
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
EntryClass = [Qwen3_5MoeForCausalLM, Qwen3_5ForCausalLM]
|
runtime/patches/sglang-qwen35.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
serve.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
model_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
| 5 |
+
image='lmsysorg/sglang@sha256:a0ffcdd013af6d79c9b7c4348c42b14a79a48f6c43dd3ca0235ec3802da45f75'
|
| 6 |
+
container_name=${NYX_CONTAINER_NAME:-nyx}
|
| 7 |
+
port=${NYX_PORT:-30000}
|
| 8 |
+
cache_dir=${NYX_CACHE_DIR:-/tmp/nyx-sglang-cache}
|
| 9 |
+
memory_fraction=${NYX_MEM_FRACTION:-0.75}
|
| 10 |
+
|
| 11 |
+
mkdir -p "$cache_dir/aiter-jit" "$cache_dir/sglang"
|
| 12 |
+
|
| 13 |
+
exec docker run --rm --name "$container_name" \
|
| 14 |
+
--device /dev/kfd --device /dev/dri --group-add video \
|
| 15 |
+
--ipc=host -p "127.0.0.1:${port}:30000" \
|
| 16 |
+
-e JEV_SELECTED_HEAD=0 \
|
| 17 |
+
-e SGLANG_USE_AITER_UNIFIED_ATTN=0 \
|
| 18 |
+
-e SGLANG_MAMBA_SSM_DTYPE=float32 \
|
| 19 |
+
-e AITER_JIT_DIR=/aiter-jit \
|
| 20 |
+
-v "$model_dir:/model:ro" \
|
| 21 |
+
-v "$cache_dir/aiter-jit:/aiter-jit" \
|
| 22 |
+
-v "$cache_dir/sglang:/root/.cache" \
|
| 23 |
+
-v "$model_dir/runtime/label-token-ids.json:/jev-label-token-ids.json:ro" \
|
| 24 |
+
-v "$model_dir/runtime/patches/sglang-logits-processor.py:/sgl-workspace/sglang/python/sglang/srt/layers/logits_processor.py:ro" \
|
| 25 |
+
-v "$model_dir/runtime/patches/sglang-qwen35.py:/sgl-workspace/sglang/python/sglang/srt/models/qwen3_5.py:ro" \
|
| 26 |
+
-v "$model_dir/runtime/patches/sglang-batch-result-processor.py:/sgl-workspace/sglang/python/sglang/srt/managers/scheduler_components/batch_result_processor.py:ro" \
|
| 27 |
+
--entrypoint /model/runtime/entrypoint.sh "$image" \
|
| 28 |
+
-m sglang.launch_server \
|
| 29 |
+
--model-path /model \
|
| 30 |
+
--load-format presharded \
|
| 31 |
+
--model-loader-extra-config '{"presharded_path":"/model/weights","max_file_bytes":5368709120,"verify_on_load":true}' \
|
| 32 |
+
--host 0.0.0.0 --port 30000 \
|
| 33 |
+
--dtype bfloat16 \
|
| 34 |
+
--quantization quark_int4fp8_moe \
|
| 35 |
+
--fp8-gemm-backend aiter \
|
| 36 |
+
--moe-runner-backend aiter \
|
| 37 |
+
--tp-size 1 --mem-fraction-static "$memory_fraction" \
|
| 38 |
+
--context-length 262144 --max-running-requests 64 \
|
| 39 |
+
--chunked-prefill-size 4096 \
|
| 40 |
+
--mamba-radix-cache-strategy extra_buffer \
|
| 41 |
+
--page-size 64 --attention-backend aiter \
|
| 42 |
+
--cuda-graph-backend-prefill breakable \
|
| 43 |
+
--cuda-graph-max-bs-decode 64 \
|
| 44 |
+
--cuda-graph-bs-prefill 128 256 512 1024 2048 4096
|
| 45 |
+
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523
|
| 3 |
+
size 19989325
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"audio_bos_token": "<|audio_start|>",
|
| 4 |
+
"audio_eos_token": "<|audio_end|>",
|
| 5 |
+
"audio_token": "<|audio_pad|>",
|
| 6 |
+
"backend": "tokenizers",
|
| 7 |
+
"bos_token": null,
|
| 8 |
+
"clean_up_tokenization_spaces": false,
|
| 9 |
+
"eos_token": "<|im_end|>",
|
| 10 |
+
"errors": "replace",
|
| 11 |
+
"image_token": "<|image_pad|>",
|
| 12 |
+
"is_local": true,
|
| 13 |
+
"local_files_only": true,
|
| 14 |
+
"model_max_length": 262144,
|
| 15 |
+
"model_specific_special_tokens": {
|
| 16 |
+
"audio_bos_token": "<|audio_start|>",
|
| 17 |
+
"audio_eos_token": "<|audio_end|>",
|
| 18 |
+
"audio_token": "<|audio_pad|>",
|
| 19 |
+
"image_token": "<|image_pad|>",
|
| 20 |
+
"video_token": "<|video_pad|>",
|
| 21 |
+
"vision_bos_token": "<|vision_start|>",
|
| 22 |
+
"vision_eos_token": "<|vision_end|>"
|
| 23 |
+
},
|
| 24 |
+
"pad_token": "<|endoftext|>",
|
| 25 |
+
"pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
|
| 26 |
+
"split_special_tokens": false,
|
| 27 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 28 |
+
"unk_token": null,
|
| 29 |
+
"video_token": "<|video_pad|>",
|
| 30 |
+
"vision_bos_token": "<|vision_start|>",
|
| 31 |
+
"vision_eos_token": "<|vision_end|>"
|
| 32 |
+
}
|
verify.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Fail-closed integrity check for the published Nyx package."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import hashlib
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
ROOT = Path(__file__).resolve().parent
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def digest(path: Path) -> str:
|
| 15 |
+
checksum = hashlib.sha256()
|
| 16 |
+
with path.open("rb") as stream:
|
| 17 |
+
for block in iter(lambda: stream.read(8 * 1024 * 1024), b""):
|
| 18 |
+
checksum.update(block)
|
| 19 |
+
return checksum.hexdigest()
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
manifest = json.loads((ROOT / "release-manifest.json").read_text())
|
| 23 |
+
if manifest.get("repository") != "devanshbatham/nyx":
|
| 24 |
+
raise SystemExit("Unexpected repository identity")
|
| 25 |
+
|
| 26 |
+
for name, metadata in manifest["files"].items():
|
| 27 |
+
path = ROOT / name
|
| 28 |
+
if not path.is_file() or path.is_symlink():
|
| 29 |
+
raise SystemExit(f"Missing or unsafe release file: {name}")
|
| 30 |
+
if path.stat().st_size != metadata["bytes"]:
|
| 31 |
+
raise SystemExit(f"Size mismatch: {name}")
|
| 32 |
+
if digest(path) != metadata["sha256"]:
|
| 33 |
+
raise SystemExit(f"SHA-256 mismatch: {name}")
|
| 34 |
+
|
| 35 |
+
print(json.dumps({
|
| 36 |
+
"verified": True,
|
| 37 |
+
"files": len(manifest["files"]),
|
| 38 |
+
"total_bytes": manifest["total_bytes"],
|
| 39 |
+
"repository": manifest["repository"],
|
| 40 |
+
}))
|
| 41 |
+
|
weights/TP-1-sig-e793a9432f30cd4e/READY
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"plan_version": 1, "world_size": 1, "created_at": 1789971566.5357497}
|
weights/TP-1-sig-e793a9432f30cd4e/checksum.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
weights/TP-1-sig-e793a9432f30cd4e/model-00000-common.safetensor
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:16e3906e9a161a6337a4a0f6d06e352d09360b37a9d4c4ea341ea978192ee289
|
| 3 |
+
size 5268047400
|
weights/TP-1-sig-e793a9432f30cd4e/model-00001-common.safetensor
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7ad7affbcc8912528ff2859568b51ee9427cc17119312ea8750cf118544ea5d3
|
| 3 |
+
size 5120198752
|
weights/TP-1-sig-e793a9432f30cd4e/model-00002-common.safetensor
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fa4a08a1e0e0de30543d343a629c6710e59575cd6f98f5beae789f34a59b79b7
|
| 3 |
+
size 5254941984
|
weights/TP-1-sig-e793a9432f30cd4e/model-00003-common.safetensor
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dfb557a70a97b91208837dac145569c844971062970f41466738e61e77d53eb3
|
| 3 |
+
size 4014197624
|