Commit ·
bc55caa
0
Parent(s):
Escha Runtime qwen3dense — escha 1.1.0+qwen3dense SGLang wheel, serve.sh, cookbook
Browse files- .gitattributes +36 -0
- LICENSE +201 -0
- README.md +164 -0
- THIRD_PARTY_LICENSES/AQLM-NOTICE.txt +15 -0
- THIRD_PARTY_LICENSES/BSD-3-Clause.txt +33 -0
- THIRD_PARTY_LICENSES/MIT.txt +26 -0
- THIRD_PARTY_LICENSES/SGLang-LICENSE.txt +201 -0
- THIRD_PARTY_LICENSES/THIRD_PARTY_NOTICES.md +62 -0
- THIRD_PARTY_LICENSES/exllamav3-LICENSE.txt +23 -0
- sglang/INSTALL.md +328 -0
- sglang/escha-1.1.0+qwen3dense-cp312-cp312-manylinux_2_28_x86_64.whl +3 -0
- sglang/serve.sh +313 -0
- sglang/thinking_budget.py +144 -0
.gitattributes
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 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 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 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
|
| 36 |
+
sglang/escha-1.1.0+qwen3dense-cp312-cp312-manylinux_2_28_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright 2026 The og Authors
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
tags:
|
| 4 |
+
- quantization
|
| 5 |
+
- sglang
|
| 6 |
+
- cuda
|
| 7 |
+
- inference
|
| 8 |
+
- escha
|
| 9 |
+
- qwen3
|
| 10 |
+
- dense
|
| 11 |
+
library_name: sglang
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# Escha Runtime — `qwen3dense`
|
| 15 |
+
|
| 16 |
+
By **[Escha Labs Inc.](https://eschalabs.com/)**
|
| 17 |
+
|
| 18 |
+
The serving runtime for **Escha** 2-/3-bit (`escha`) quantized models of the **`qwen3_5` dense
|
| 19 |
+
architecture** (Qwen3.8-27B and siblings). One repo per model architecture, one directory per
|
| 20 |
+
engine — this architecture currently has **one** engine, [`sglang/`](sglang/).
|
| 21 |
+
|
| 22 |
+
| | **SGLang** — [`sglang/`](sglang/) |
|
| 23 |
+
|---|---|
|
| 24 |
+
| Best for | everything: single user, teams, agents |
|
| 25 |
+
| Concurrency | continuous batching, paged KV, optional radix prefix cache |
|
| 26 |
+
| Tool calls / JSON schema / thinking parser | yes |
|
| 27 |
+
| Interface | OpenAI-compatible (`/v1/chat/completions`, `/v1/completions`, `/v1/models`) |
|
| 28 |
+
| Install | Python 3.12 venv + CUDA-12 PyTorch, then one wheel |
|
| 29 |
+
|
| 30 |
+
The engine is a fork of [SGLang](https://github.com/sgl-project/sglang) bundled inside the wheel,
|
| 31 |
+
running the Escha CUDA kernels. No separate `sglang` install is needed, and none should be
|
| 32 |
+
present — the wheel ships its own.
|
| 33 |
+
|
| 34 |
+
### Compatible models
|
| 35 |
+
|
| 36 |
+
| Model repo | Bits |
|
| 37 |
+
|---|---|
|
| 38 |
+
| [EschaLabs/Qwen3.8-27B-Escha-W2](https://huggingface.co/EschaLabs/Qwen3.8-27B-Escha-W2) | 2-bit, mixed-rate (`escha`) |
|
| 39 |
+
<!-- add each qwen3_5 dense Escha model you publish here -->
|
| 40 |
+
|
| 41 |
+
> This runtime targets the **`qwen3_5` dense** architecture. Its wheel also happens to register the
|
| 42 |
+
> `eschamoe` mixture-of-experts method, so a `qwen3_5_moe` model will load too — but the tuning,
|
| 43 |
+
> the defaults in `sglang/serve.sh` and the documentation here are all written for the dense
|
| 44 |
+
> architecture. For a mixture-of-experts model use
|
| 45 |
+
> [`escha-runtime-qwen3moe`](https://huggingface.co/EschaLabs/escha-runtime-qwen3moe), whose
|
| 46 |
+
> defaults are measured on it. A model of a genuinely different architecture will not load — use
|
| 47 |
+
> the matching `escha-runtime-<arch>` repo.
|
| 48 |
+
|
| 49 |
+
## Quickstart
|
| 50 |
+
|
| 51 |
+
Full detail, including the per-GPU cookbook and troubleshooting:
|
| 52 |
+
[`sglang/INSTALL.md`](sglang/INSTALL.md).
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
python3.12 -m venv .venv && source .venv/bin/activate
|
| 56 |
+
pip install -U pip wheel
|
| 57 |
+
pip install "torch==2.9.*" --index-url https://download.pytorch.org/whl/cu128 # cu12 torch FIRST
|
| 58 |
+
pip install ./sglang/escha-*.whl # pulls the bundled sglang fork + its full dep closure
|
| 59 |
+
|
| 60 |
+
hf download EschaLabs/Qwen3.8-27B-Escha-W2 --local-dir ./Qwen3.8-27B-Escha-W2
|
| 61 |
+
MODEL=./Qwen3.8-27B-Escha-W2 bash sglang/serve.sh
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Then check the stack and the endpoint:
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
python -c "import torch, escha, sglang; print(torch.cuda.is_available(), hasattr(torch.ops.escha, 'escham_decode_gemv'), escha.__version__)"
|
| 68 |
+
curl -s http://127.0.0.1:30000/v1/models | python3 -m json.tool
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
> `pip install "torch==2.9.*"` is a hard pin, not a suggestion. A bare `torch>=2.9` resolves to a
|
| 72 |
+
> newer minor and `import escha` then fails with `undefined symbol: _ZN3c10...` — the compiled
|
| 73 |
+
> extension is ABI-linked to libtorch, and that ABI is not stable across PyTorch minors.
|
| 74 |
+
|
| 75 |
+
## Thinking, and why you probably want a budget
|
| 76 |
+
|
| 77 |
+
This is a reasoning model. With thinking on, the reasoning arrives in `reasoning_content` and the
|
| 78 |
+
answer in `content` — **read both**, or you will see half the response.
|
| 79 |
+
|
| 80 |
+
Two per-request levers, both inside `chat_template_kwargs` (a *top-level* `enable_thinking` field
|
| 81 |
+
is silently ignored):
|
| 82 |
+
|
| 83 |
+
```jsonc
|
| 84 |
+
{ "chat_template_kwargs": {"enable_thinking": true, "reasoning_effort": "xhigh"} }
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
`reasoning_effort` is `"xhigh"` (**the default**), `"medium"` or `"low"`; anything else makes the
|
| 88 |
+
template raise, which surfaces as an HTTP 400 rather than a silent fallback. It works by injecting
|
| 89 |
+
one sentence of system instruction — `xhigh` asks the model to validate assumptions and weigh
|
| 90 |
+
alternatives, `low` asks it to keep thinking brief, and **`medium` injects nothing at all**, so
|
| 91 |
+
`medium` is the neutral, unsteered model rather than a midpoint. It therefore *asks* for shorter
|
| 92 |
+
reasoning; it does not bound it. If you are running a benchmark
|
| 93 |
+
or an agent, set a **thinking budget** instead, which forces `</think>` after N reasoning tokens so
|
| 94 |
+
an answer is always produced: see
|
| 95 |
+
[`sglang/INSTALL.md` → Bounded thinking](sglang/INSTALL.md#bounded-thinking-thinking_budget) and
|
| 96 |
+
[`sglang/thinking_budget.py`](sglang/thinking_budget.py). Without one, the usual failure is
|
| 97 |
+
`finish_reason: "length"` with `content: null`, which a harness scores as *wrong* rather than as
|
| 98 |
+
*slow*.
|
| 99 |
+
|
| 100 |
+
## Requirements
|
| 101 |
+
|
| 102 |
+
- **NVIDIA GPU, compute capability 8.0–12.0** (Ampere → Blackwell), Linux x86-64 with
|
| 103 |
+
**glibc ≥ 2.28**. The kernel launch route auto-selects per GPU; you never set it. Per-architecture
|
| 104 |
+
and per-VRAM launch recipes:
|
| 105 |
+
[`sglang/INSTALL.md` → Running on your GPU](sglang/INSTALL.md#running-on-your-gpu).
|
| 106 |
+
- **Python 3.12** (the wheel is `cp312`-only) + **CUDA-12 PyTorch 2.9.x**. The wheel handles every
|
| 107 |
+
other dependency.
|
| 108 |
+
- **A working host C compiler and Python dev headers.** Triton JIT-compiles a small shim at
|
| 109 |
+
CUDA-graph capture time — this is separate from `ptxas` and from a CUDA toolkit, so "driver only"
|
| 110 |
+
does not cover it. On slim container images a stripped `libisl` breaks `cc1` while
|
| 111 |
+
`gcc --version` still succeeds, and the failure surfaces ~40 s in as a `gcc`
|
| 112 |
+
`CalledProcessError` inside `cuda_graph_runner.py` — which reads like a runtime bug and is not.
|
| 113 |
+
Preflight in [`sglang/INSTALL.md`](sglang/INSTALL.md#requirements).
|
| 114 |
+
- **24 GB VRAM** for the shipped defaults (65,536-token context, ~8–9 concurrent streams) with a
|
| 115 |
+
~10.15 GB model. Note the stream ceiling: `MAMBA_RATIO=0.3` sizes the recurrent-state pool, which
|
| 116 |
+
clamps `max_running_requests` to 8–9 on a 24 GB card, so the `12`/`16` entries in the default
|
| 117 |
+
`CUDA_GRAPH_BS` are **dropped and never captured**. To serve more streams raise
|
| 118 |
+
`MAXREQ`/`MAXMAMBA` with `MEM` — the throughput recipe is in the
|
| 119 |
+
[model card](https://huggingface.co/EschaLabs/Qwen3.8-27B-Escha-W2#verified-configurations).
|
| 120 |
+
16 GB should fit at a reduced context — the cookbook has a recipe, but we have not run it.
|
| 121 |
+
|
| 122 |
+
## Known limitations
|
| 123 |
+
|
| 124 |
+
- **Serving throughput has been measured on three cards** — RTX 5090 (32 GB, sm_120), RTX 4090
|
| 125 |
+
(24 GB, sm_89) and RTX 3090 (24 GB, sm_86); the 5090 and 3090 by independent evaluators working
|
| 126 |
+
only from these docs. The 4090 is also where the model's thinking-on benchmarks (GPQA-Diamond,
|
| 127 |
+
LiveCodeBench) were produced; its thinking-off commonsense suite ran on an L40. The **16 GB and
|
| 128 |
+
40 GB+** tiers in the cookbook remain configuration guidance derived from the model size and the
|
| 129 |
+
wheel's architecture coverage, **not measurements**. Per-GPU numbers live on the
|
| 130 |
+
[model card](https://huggingface.co/EschaLabs/Qwen3.8-27B-Escha-W2#performance-across-gpus).
|
| 131 |
+
- **On Ampere the auto-selected kernel route is the slower one at batch 1.** `ESCHA_ROUTE`
|
| 132 |
+
resolves to `lovelace` on sm_80/sm_86, but forcing `ESCHA_ROUTE=blackwell` measured **1.72×
|
| 133 |
+
faster single-stream on an RTX 3090** (23.6 → 40.7 tok/s, TPOT 42.4 → 24.6 ms) with identical
|
| 134 |
+
output. The two routes are bit-identical launch geometries, so this is safe to set; the gain is
|
| 135 |
+
batch-1-only (parity at 2–16). Serving one user on Ampere? Set it.
|
| 136 |
+
- **`DETERMINISTIC=1` fails on consumer Blackwell (sm_120).** The deterministic attention kernel
|
| 137 |
+
requests 104 KB of shared memory per block, above the sm_120 limit, and the server exits during
|
| 138 |
+
startup. It works on Ampere, Ada and Hopper.
|
| 139 |
+
- **Greedy output is not bit-reproducible across requests.** Batch composition changes fp16
|
| 140 |
+
accumulation order, so a near-tie can flip and a long reasoning chain diverges from there. Two
|
| 141 |
+
identical greedy requests may return different text. Use `DETERMINISTIC=1` when you need
|
| 142 |
+
reproducibility, and never A/B two configurations by diffing one generation.
|
| 143 |
+
- **CUDA-graph batch sizes are capped at 32**, the decode kernel's maximum M
|
| 144 |
+
(`torch.ops.escha.escham_decode_gemv_max_m()`). The shipped default list stops at 16 because
|
| 145 |
+
that is where aggregate throughput peaks on a 4090; capture at `24`/`32` works and is worth it if
|
| 146 |
+
you serve that many streams. Past 32 a batch falls through to a large-M path meant for prefill,
|
| 147 |
+
so the runtime refuses to capture it rather than bake in the wrong kernel.
|
| 148 |
+
- **`ATTN_BACKEND=triton` is required on consumer Blackwell (RTX 50-series).** The default
|
| 149 |
+
flashinfer backend asserts on this hybrid architecture at sm_120. The assertion names three
|
| 150 |
+
acceptable backends — `triton`, `trtllm_mha`, `fa4` — of which only `triton` has been run on
|
| 151 |
+
this model. Note that sm_120 shows steeper long-prompt decode decay than sm_89 (88.5% vs 96.3%
|
| 152 |
+
of short-prompt rate at a 5,000-token prompt); the attention path is the obvious suspect and
|
| 153 |
+
nobody has run the A/B that would confirm it.
|
| 154 |
+
- **The model's stock chat template raises** on a few malformed conversations (a missing user
|
| 155 |
+
query, a system message in the wrong position). That surfaces as an HTTP 400 from the template,
|
| 156 |
+
not as a server fault.
|
| 157 |
+
|
| 158 |
+
## License
|
| 159 |
+
|
| 160 |
+
Everything here is released under the **Apache License, Version 2.0** — see [`LICENSE`](LICENSE).
|
| 161 |
+
All bundled third-party code is permissive (Apache-2.0 / MIT / BSD-3-Clause) — **no copyleft**.
|
| 162 |
+
Full texts and the component inventory:
|
| 163 |
+
[`THIRD_PARTY_LICENSES/`](THIRD_PARTY_LICENSES/). Model weights are **not** in this repo and carry
|
| 164 |
+
their own license in the model repository.
|
THIRD_PARTY_LICENSES/AQLM-NOTICE.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
AQLM — https://github.com/Vahe1994/AQLM
|
| 2 |
+
|
| 3 |
+
Licensed under the Apache License, Version 2.0.
|
| 4 |
+
Copyright the AQLM authors.
|
| 5 |
+
|
| 6 |
+
The `escha` runtime's additive-codebook GEMV/HMMA kernels (`escha_aqlm_*`, compiled
|
| 7 |
+
into `escha/_C`) are an independent implementation that follows the AQLM additive
|
| 8 |
+
codebook *numeric format*; they are adapted from the escha runtime's own
|
| 9 |
+
kernels rather than copied from AQLM source. This notice is provided out of an
|
| 10 |
+
abundance of caution.
|
| 11 |
+
|
| 12 |
+
The full text of the Apache License, Version 2.0 is reproduced in
|
| 13 |
+
`SGLang-LICENSE.txt` in this directory (the license body is identical for every
|
| 14 |
+
Apache-2.0 component in this release; only the per-component copyright line
|
| 15 |
+
differs).
|
THIRD_PARTY_LICENSES/BSD-3-Clause.txt
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 3-Clause License
|
| 2 |
+
|
| 3 |
+
Applies to code within the bundled SGLang fork that is adapted from
|
| 4 |
+
FlashAttention and Mamba:
|
| 5 |
+
Copyright (c) 2023, Tri Dao.
|
| 6 |
+
Copyright (c) 2024, Tri Dao, Albert Gu.
|
| 7 |
+
(and other BSD-3-Clause-licensed upstreams whose per-file copyright notices are
|
| 8 |
+
retained verbatim in the corresponding source headers within `sglang/`.)
|
| 9 |
+
|
| 10 |
+
Redistribution and use in source and binary forms, with or without
|
| 11 |
+
modification, are permitted provided that the following conditions are met:
|
| 12 |
+
|
| 13 |
+
1. Redistributions of source code must retain the above copyright notice, this
|
| 14 |
+
list of conditions and the following disclaimer.
|
| 15 |
+
|
| 16 |
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
| 17 |
+
this list of conditions and the following disclaimer in the documentation
|
| 18 |
+
and/or other materials provided with the distribution.
|
| 19 |
+
|
| 20 |
+
3. Neither the name of the copyright holder nor the names of its contributors
|
| 21 |
+
may be used to endorse or promote products derived from this software
|
| 22 |
+
without specific prior written permission.
|
| 23 |
+
|
| 24 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 25 |
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 26 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 27 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 28 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 29 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 30 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 31 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 32 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 33 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
THIRD_PARTY_LICENSES/MIT.txt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Applies to code within the bundled SGLang fork that carries an
|
| 4 |
+
`SPDX-License-Identifier: MIT` header and/or is adapted from MIT-licensed
|
| 5 |
+
upstreams (e.g. flash-linear-attention):
|
| 6 |
+
Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
|
| 7 |
+
(and other MIT-licensed upstreams whose per-file copyright notices are retained
|
| 8 |
+
verbatim in the corresponding source headers within `sglang/`.)
|
| 9 |
+
|
| 10 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 11 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 12 |
+
in the Software without restriction, including without limitation the rights
|
| 13 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 14 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 15 |
+
furnished to do so, subject to the following conditions:
|
| 16 |
+
|
| 17 |
+
The above copyright notice and this permission notice shall be included in all
|
| 18 |
+
copies or substantial portions of the Software.
|
| 19 |
+
|
| 20 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 21 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 22 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 23 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 24 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 25 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 26 |
+
SOFTWARE.
|
THIRD_PARTY_LICENSES/SGLang-LICENSE.txt
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright 2023-2024 SGLang Team
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
THIRD_PARTY_LICENSES/THIRD_PARTY_NOTICES.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Third-Party Notices — Escha Runtime (`escha` 1.1.0)
|
| 2 |
+
|
| 3 |
+
This repository distributes the **runtime** only (the `escha` wheel: quantization/serving
|
| 4 |
+
kernels + a modified SGLang fork). SGLang is the only engine in this repository. The quantized **model weights** are distributed
|
| 5 |
+
separately and carry their own license — see the model repository
|
| 6 |
+
(**`EschaLabs/Qwen3.8-27B-Escha-W2`**).
|
| 7 |
+
|
| 8 |
+
All bundled third-party code is under **permissive licenses (Apache-2.0, MIT, BSD-3-Clause)** —
|
| 9 |
+
an audit found **no copyleft (GPL / LGPL / AGPL / MPL / SSPL) code**. The obligations below are
|
| 10 |
+
satisfied by the files in this `THIRD_PARTY_LICENSES/` directory together with the copyright
|
| 11 |
+
notices retained verbatim in the redistributed source (inside the wheel). The same texts are also
|
| 12 |
+
embedded in the wheel metadata (`escha-1.1.0+qwen3dense.dist-info/licenses/LICENSE`).
|
| 13 |
+
|
| 14 |
+
The `escha` runtime itself is released under the Apache License, Version 2.0 — see the top-level
|
| 15 |
+
`LICENSE` file of this repository.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## 1. Acknowledgements — formats referenced, no code bundled
|
| 20 |
+
|
| 21 |
+
The compiled CUDA runtime (`escha._C`) is **original work**. Its kernels implement, as
|
| 22 |
+
independent (clean-room) reimplementations, numeric *formats* referenced from the two upstream
|
| 23 |
+
projects below. **No upstream source is copied into the binary** and neither project's licence
|
| 24 |
+
obliges us to reproduce it here — the attribution is a courtesy, and we would rather over-credit
|
| 25 |
+
than under-credit work we learned from.
|
| 26 |
+
|
| 27 |
+
| Upstream | Relationship | License | Text |
|
| 28 |
+
|---|---|---|---|
|
| 29 |
+
| **exllamav3** (Turboderp) | quantization codec *format* referenced by the escha decode kernels | MIT, © 2025 Turboderp | `exllamav3-LICENSE.txt` |
|
| 30 |
+
| **AQLM** (Vahe1994/AQLM) | additive-codebook *format* referenced by `escha_aqlm_*` kernels | Apache-2.0 | `AQLM-NOTICE.txt` (text in `SGLang-LICENSE.txt`) |
|
| 31 |
+
|
| 32 |
+
`bitblas` (Microsoft, MIT) is an **optional runtime import** only — not bundled or redistributed here.
|
| 33 |
+
|
| 34 |
+
**Also gratefully acknowledged — the serving framework this engine is built on.**
|
| 35 |
+
Unlike the formats above, its code is genuinely **redistributed** here, so it is covered by real
|
| 36 |
+
licence obligations rather than courtesy: **[SGLang](https://github.com/sgl-project/sglang)**,
|
| 37 |
+
bundled as a modified fork — see §2. This runtime would not exist in this form without it.
|
| 38 |
+
|
| 39 |
+
## 2. Bundled serving engine — `sglang/` (modified fork)
|
| 40 |
+
|
| 41 |
+
The wheel bundles a **modified fork of SGLang** as source.
|
| 42 |
+
|
| 43 |
+
- **License:** Apache-2.0 — © 2023–2026 SGLang Team. Full text: `SGLang-LICENSE.txt`.
|
| 44 |
+
- **Statement of changes (Apache-2.0 §4(b)):** the fork adds quantization-format support
|
| 45 |
+
(the `escha` dense and `eschamoe` mixture-of-experts methods, plus the int8 dense-layer
|
| 46 |
+
path) and kernel dispatch.
|
| 47 |
+
- **NOTICE (Apache-2.0 §4(d)):** upstream sgl-project/sglang publishes **no NOTICE file**
|
| 48 |
+
(verified against the fork's pristine upstream-mirror at the merge base and across the full
|
| 49 |
+
tree); therefore none is reproduced.
|
| 50 |
+
|
| 51 |
+
SGLang itself vendors code from other permissive projects; those files are redistributed
|
| 52 |
+
**unmodified with their copyright headers intact**, which preserves their notices:
|
| 53 |
+
|
| 54 |
+
| Upstream (as vendored in SGLang) | License | Reference text |
|
| 55 |
+
|---|---|---|
|
| 56 |
+
| vLLM project | Apache-2.0 | `SGLang-LICENSE.txt` |
|
| 57 |
+
| IST-DASLab / Marlin (Elias Frantar) | Apache-2.0 | `SGLang-LICENSE.txt` |
|
| 58 |
+
| EleutherAI + HuggingFace (transformers) | Apache-2.0 | `SGLang-LICENSE.txt` |
|
| 59 |
+
| NVIDIA (CUTLASS / kernels) | BSD-3-Clause / Apache-2.0 | `BSD-3-Clause.txt` |
|
| 60 |
+
| FlashAttention, Mamba (Tri Dao, Albert Gu) | BSD-3-Clause | `BSD-3-Clause.txt` |
|
| 61 |
+
| flash-linear-attention (Songlin Yang, Yu Zhang) | MIT | `MIT.txt` |
|
| 62 |
+
| Model configs (Qwen, DeepSeek, Moonshot, LG AI, Black Forest Labs, PaddlePaddle, Antgroup, …) | Apache-2.0 | `SGLang-LICENSE.txt` |
|
THIRD_PARTY_LICENSES/exllamav3-LICENSE.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
exllamav3 — https://github.com/turboderp-org/exllamav3
|
| 2 |
+
|
| 3 |
+
MIT License
|
| 4 |
+
|
| 5 |
+
Copyright (c) 2025 Turboderp
|
| 6 |
+
|
| 7 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 8 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 9 |
+
in the Software without restriction, including without limitation the rights
|
| 10 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 11 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 12 |
+
furnished to do so, subject to the following conditions:
|
| 13 |
+
|
| 14 |
+
The above copyright notice and this permission notice shall be included in all
|
| 15 |
+
copies or substantial portions of the Software.
|
| 16 |
+
|
| 17 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 18 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 19 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 20 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 21 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 22 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 23 |
+
SOFTWARE.
|
sglang/INSTALL.md
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Escha Runtime — install & serve
|
| 2 |
+
|
| 3 |
+
This repository is the **runtime**: one wheel (`escha-*.whl`) holding the compiled `escha` package +
|
| 4 |
+
a scrubbed `sglang` fork, plus the `serve.sh` launcher. It is **model-agnostic** — point it at any
|
| 5 |
+
Escha model directory of a supported architecture. The quantized weights are a **separate**
|
| 6 |
+
repository: **`EschaLabs/Qwen3.8-27B-Escha-W2`**
|
| 7 |
+
(<https://huggingface.co/EschaLabs/Qwen3.8-27B-Escha-W2>).
|
| 8 |
+
|
| 9 |
+
## Requirements
|
| 10 |
+
|
| 11 |
+
**All are hard, and three of them fail quietly:**
|
| 12 |
+
|
| 13 |
+
- **Linux x86-64 with glibc ≥ 2.28.** That covers RHEL / Rocky / Alma **8** and newer, Ubuntu 20.04+,
|
| 14 |
+
and Debian 10+. The wheel is tagged `manylinux_2_28_x86_64`, so pip refuses it up front on an
|
| 15 |
+
older distro instead of failing later at `import escha`.
|
| 16 |
+
- **Python 3.12.** The wheel is built `cp312`; any other interpreter rejects it at install time
|
| 17 |
+
with "not a supported wheel on this platform". (A compiled-extension wheel is tied to one CPython
|
| 18 |
+
minor — there is intentionally no 3.11/3.13 build.)
|
| 19 |
+
- **NVIDIA GPU, compute capability 8.0–12.0.** The build is a fat binary carrying SASS for
|
| 20 |
+
Ampere (`sm_80`/`sm_86`), Ada (`sm_89`, e.g. RTX 4090 / L40S), Hopper (`sm_90`), and Blackwell
|
| 21 |
+
(`sm_100`/`sm_120`), plus PTX for forward-compat JIT onto newer GPUs. Older cards (`< sm_80`,
|
| 22 |
+
e.g. Turing) are unsupported. Verify the installed wheel actually covers your card with
|
| 23 |
+
`cuobjdump --list-elf $(python -c "import escha,os;print(os.path.dirname(escha.__file__))")/_C*.so | grep -oE 'sm_[0-9]+' | sort -u`
|
| 24 |
+
— you should see all six. If it lists only one, it is a slim build and will fail on other GPUs
|
| 25 |
+
with "no kernel image is available for execution on the device" at the first launch (install and
|
| 26 |
+
the step-4 op-registration check still pass — the failure is at runtime).
|
| 27 |
+
- **A working host C compiler and Python dev headers.** Triton JIT-compiles a `cuda_utils.c` shim
|
| 28 |
+
at CUDA-graph capture time and shells out to `cc`; this is separate from `ptxas` and from a CUDA
|
| 29 |
+
toolkit, so "driver only, no toolkit" does not cover it. Preflight:
|
| 30 |
+
```bash
|
| 31 |
+
echo '#include <Python.h>' | gcc -xc -I"$(python -c 'import sysconfig;print(sysconfig.get_paths()["include"])')" -c - -o /dev/null
|
| 32 |
+
```
|
| 33 |
+
- **CUDA 12.x + matching PyTorch 2.9.x-for-CUDA-12.** `escha._C` is ABI-linked to libtorch and
|
| 34 |
+
`libcudart.so.12`, so the installed torch must be the CUDA-12 build of 2.9.x (a different torch
|
| 35 |
+
minor can fail to load `import escha`). Needs a driver new enough for CUDA 12.x.
|
| 36 |
+
- **`transformers >= 5.8`.** Older releases lack the config class for this architecture. The server
|
| 37 |
+
does **not** abort — it logs a single `WARNING ... config normalize FAILED`, serves with
|
| 38 |
+
un-normalized architecture parameters, and then produces fluent-looking nonsense. The wheel
|
| 39 |
+
**pins `transformers>=5.8`**, so a plain `pip install ./escha-*.whl` upgrades an old transformers
|
| 40 |
+
for you; you only hit this if you *downgrade* transformers afterwards. If generation is garbage,
|
| 41 |
+
check your transformers version *first*. (Conversely, transformers 5.x logs a scary-looking
|
| 42 |
+
`WARNING ... you can try downgrading to transformers==4.57.1` about RoPE — that is a generic
|
| 43 |
+
upstream sglang warning and is **safe to ignore**; do **not** downgrade.)
|
| 44 |
+
|
| 45 |
+
1. `python3.12 -m venv .venv && source .venv/bin/activate`
|
| 46 |
+
2. `pip install -U pip wheel`
|
| 47 |
+
3. **Install PyTorch from the CUDA-12 index first** (it must be the cu12 build — `escha._C` is
|
| 48 |
+
ABI-linked to it, and it is the one dependency that cannot come from PyPI). **Pin to 2.9.x**:
|
| 49 |
+
`pip install "torch==2.9.*" --index-url https://download.pytorch.org/whl/cu128`
|
| 50 |
+
A bare `torch>=2.9.0` resolves to a newer minor, and **nothing corrects it** — the wheel
|
| 51 |
+
declares 61 dependencies but *no* `torch` entry, and none of its transitive deps pin one. You
|
| 52 |
+
stay on the wrong minor and `import escha` fails later with `undefined symbol: _ZN3c10...`,
|
| 53 |
+
because that is the C++ ABI surface PyTorch does not keep stable across minor versions.
|
| 54 |
+
4. Install the runtime wheel (it ships in this repo):
|
| 55 |
+
`pip install ./escha-*.whl`
|
| 56 |
+
— installs the `escha` runtime, the bundled `sglang` fork, **and the fork's full runtime
|
| 57 |
+
dependency closure** (transformers ≥ 5.8, flashinfer, xgrammar, msgspec, uvloop, … — all pinned
|
| 58 |
+
in the wheel). No separate `sglang[srt]` install is needed. Sanity-check the whole stack (all
|
| 59 |
+
three should print `True`):
|
| 60 |
+
```bash
|
| 61 |
+
python -c "import torch, escha, sglang; print(torch.cuda.is_available(), hasattr(torch.ops.escha, 'escham_decode_gemv'), bool(sglang.__version__))"
|
| 62 |
+
```
|
| 63 |
+
(Note `import sglang` in the check — a `torch`+`escha`-only check passes on a box that cannot
|
| 64 |
+
actually serve.)
|
| 65 |
+
5. Download a model (the weights are a **separate** repo; it is a flat folder — safetensors, config
|
| 66 |
+
and tokenizer at the root, no nested subdir):
|
| 67 |
+
```bash
|
| 68 |
+
pip install -U "huggingface_hub[cli]"
|
| 69 |
+
hf download EschaLabs/Qwen3.8-27B-Escha-W2 --local-dir ./Qwen3.8-27B-Escha-W2
|
| 70 |
+
```
|
| 71 |
+
6. Serve it — point `MODEL` at the downloaded model directory:
|
| 72 |
+
```bash
|
| 73 |
+
MODEL=./Qwen3.8-27B-Escha-W2 bash serve.sh
|
| 74 |
+
```
|
| 75 |
+
- **Kernel route is auto-selected at runtime by GPU** — no env var needed. The kernels ship a
|
| 76 |
+
single fatbin with two launch-geometry routes; `escha` queries the device compute capability
|
| 77 |
+
and picks the measured-optimal one: `blackwell` (barrier-light, SM-adaptive split-K) on
|
| 78 |
+
**sm_89 Ada, sm_90 Hopper, and sm_100/sm_120 Blackwell**; `lovelace` (original geometry) on
|
| 79 |
+
**sm_80/sm_86 Ampere**. Both are bit-identical; force one with
|
| 80 |
+
`ESCHA_ROUTE=lovelace|blackwell` to A/B on your card (Hopper's default is a best-guess —
|
| 81 |
+
worth A/B'ing). Check the active route with
|
| 82 |
+
`python -c "import escha,torch; torch.zeros(1,device='cuda'); print(torch.ops.escha.escham_route())"`
|
| 83 |
+
(`2`=blackwell, `1`=lovelace).
|
| 84 |
+
- See the top of `serve.sh` for the full knob list (`MEM`, `CTXLEN`, `GRAPHS`, `THINK`, `RADIX`,
|
| 85 |
+
`INT8`, `DETERMINISTIC`, …). The defaults are the profile every published number for this
|
| 86 |
+
model was measured on, on a 24 GB card.
|
| 87 |
+
7. OpenAI-compatible endpoint on `:30000` (`http://127.0.0.1:30000/v1`). The served model id is
|
| 88 |
+
`SERVED_NAME` from `serve.sh` (override with `SERVED_NAME=...`); list it with:
|
| 89 |
+
```bash
|
| 90 |
+
curl -s http://127.0.0.1:30000/v1/models | python3 -m json.tool
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
## Behaviour worth knowing
|
| 94 |
+
|
| 95 |
+
- **Thinking is per-request.** `chat_template_kwargs: {"enable_thinking": false}` is honoured
|
| 96 |
+
against the default `THINK=1` server, with or without `tools`; a **top-level `enable_thinking`
|
| 97 |
+
field is ignored** — it must go inside `chat_template_kwargs`. Set `THINK=0` to serve the whole
|
| 98 |
+
server with thinking off.
|
| 99 |
+
- **`reasoning_effort` is a second lever, and the default is the expensive one.**
|
| 100 |
+
`chat_template_kwargs: {"reasoning_effort": "xhigh"|"medium"|"low"}`, default **`"xhigh"`**. Each
|
| 101 |
+
level is one sentence of injected system instruction: `xhigh` asks the model to validate
|
| 102 |
+
assumptions and weigh alternatives, `low` asks it to keep thinking brief, and **`medium` injects
|
| 103 |
+
nothing** — it is the neutral, unsteered model, not a midpoint. If `xhigh` is over-thinking your
|
| 104 |
+
traffic, `medium` is usually the right first move, `low` for latency-sensitive volume. Any other
|
| 105 |
+
value makes the template raise, which surfaces as an HTTP 400 — it is a typo, not a server fault.
|
| 106 |
+
All three only apply while thinking is on, and none of them *bounds* anything: a
|
| 107 |
+
`thinking_budget` (below) is the only hard guarantee.
|
| 108 |
+
- **Read `reasoning_content` AND `content`.** With thinking on, the reasoning arrives in
|
| 109 |
+
`reasoning_content` and the answer in `content`. A client that reads only one of them will see
|
| 110 |
+
half the response — this is the single most common integration mistake.
|
| 111 |
+
- **Structured output is stable.** `serve.sh` defaults `JSON_WS=1`
|
| 112 |
+
(`--constrained-json-disable-any-whitespace`), which keeps `json_schema` values consistent
|
| 113 |
+
run-to-run. `JSON_WS=0` opts out.
|
| 114 |
+
- **Over-long prompts are truncated, not rejected.** `serve.sh` defaults `TRUNCATE=1`
|
| 115 |
+
(`--allow-auto-truncate`). Without it, a prompt above `CTXLEN` is hard-rejected, and a harness
|
| 116 |
+
that scores the error as an answer reports it as a model failure. Set `TRUNCATE=0` if you would
|
| 117 |
+
rather see the error.
|
| 118 |
+
- **`usage.reasoning_tokens`** counts the reasoning span on both the streaming and non-streaming
|
| 119 |
+
paths, and is 0 when no reasoning parser is active. Use it to size a `thinking_budget` from
|
| 120 |
+
what reasoning actually costs.
|
| 121 |
+
- **`escha.__version__`** — `python -c "import escha; print(escha.__version__)"`. Read from the
|
| 122 |
+
installed distribution metadata, so it cannot drift from the wheel it shipped in. Paste it
|
| 123 |
+
into bug reports.
|
| 124 |
+
- **`DETERMINISTIC=1`** sets `--enable-deterministic-inference` — **not available on consumer
|
| 125 |
+
Blackwell (RTX 50 / sm_120)**, where the deterministic attention kernel exceeds the 104 KB
|
| 126 |
+
shared-memory-per-block limit and the server exits at startup. `serve.sh` forwards any extra
|
| 127 |
+
arguments straight to `launch_server`, so a flag it does not model is still reachable.
|
| 128 |
+
|
| 129 |
+
## Running on your GPU
|
| 130 |
+
|
| 131 |
+
The weights are **10.15 GB**. What changes across cards is the **compute-capability knob** (one
|
| 132 |
+
attention-backend override on the newest cards) and the **VRAM budget** (context length, the
|
| 133 |
+
CUDA-graph batch list, and whether `lm_head` is kept int8-as-stored). The kernel launch route is
|
| 134 |
+
chosen for you at runtime — you never set it.
|
| 135 |
+
|
| 136 |
+
> **Measured on three cards: a 32 GB RTX 5090 (sm_120), a 24 GB RTX 4090 (sm_89) and a 24 GB
|
| 137 |
+
> RTX 3090 (sm_86)**; the 4090 is also where the model's thinking-on benchmarks were produced.
|
| 138 |
+
> Per-GPU tokens/s live on the
|
| 139 |
+
> [model card](https://huggingface.co/EschaLabs/Qwen3.8-27B-Escha-W2#performance-across-gpus). The
|
| 140 |
+
> **16 GB and 40 GB+** rows below are still *configuration* guidance derived from the model's size
|
| 141 |
+
> and the wheel's architecture coverage, **not measurements** — we would rather ship a gap than a
|
| 142 |
+
> number we have not run.
|
| 143 |
+
|
| 144 |
+
### By compute capability (architecture)
|
| 145 |
+
|
| 146 |
+
| Arch | Compute cap | Cards | What you set |
|
| 147 |
+
|---|---|---|---|
|
| 148 |
+
| Ampere | sm_80 / sm_86 | A100, A6000, RTX 3090 | route auto-selects `lovelace`, but **`ESCHA_ROUTE=blackwell` measured 1.72× faster at batch 1 on an RTX 3090** (23.6 → 40.7 tok/s; parity at batch 2–16) — set it if you serve a single user |
|
| 149 |
+
| Ada | sm_89 | RTX 4090, L40S | nothing — route auto-selects `blackwell` (**the validated card**) |
|
| 150 |
+
| Blackwell (consumer, 32 GB) | sm_120 | RTX 5090 | **`ATTN_BACKEND=triton`** — measured, see *By VRAM* below |
|
| 151 |
+
| Hopper | sm_90 | H100 | nothing (route defaults `blackwell`; A/B `ESCHA_ROUTE` — Hopper is a best-guess) |
|
| 152 |
+
| Blackwell (data-center) | sm_100 | B200 | nothing — route auto-selects `blackwell` |
|
| 153 |
+
| Blackwell (consumer) | sm_120 | RTX 50-series | **`ATTN_BACKEND=triton`** (the default flashinfer backend asserts on this hybrid architecture at sm_120) |
|
| 154 |
+
|
| 155 |
+
Confirm the fatbin covers your card and the active route with:
|
| 156 |
+
```bash
|
| 157 |
+
python -c "import escha,torch; torch.zeros(1,device='cuda'); print('route', torch.ops.escha.escham_route())"
|
| 158 |
+
# expected: route 2 (blackwell) on sm_89/90/100/120 — incl. RTX 4090/5090; route 1 (lovelace) on sm_80/86
|
| 159 |
+
# Both routes are bit-identical launch geometries — forcing one is a performance choice, not a
|
| 160 |
+
# correctness one. Worth A/B'ing on Ampere (measured 1.72x at bs=1) and on Hopper (best-guess default).
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
### By VRAM
|
| 164 |
+
|
| 165 |
+
This checkpoint is text-only, so `SGLANG_VLM_TEXT_ONLY=1` is the `serve.sh` default.
|
| 166 |
+
|
| 167 |
+
**int8-as-stored `lm_head`.** The runtime can keep `lm_head` int8 in VRAM instead of dequantizing
|
| 168 |
+
it to fp16 at load: ~1.2 GB instead of ~2.4 GB, read on **every** decode step, so it buys both VRAM
|
| 169 |
+
and single-user decode bandwidth. `INT8=auto` (the default) turns it on at ≤ 24 GB VRAM
|
| 170 |
+
(`SGLANG_INT8_AUTO_VRAM_GB`) and off above; `INT8=on` / `INT8=off` force it. Note this is narrower
|
| 171 |
+
than the mixture-of-experts runtime: on this dense model only `lm_head` is affected —
|
| 172 |
+
`embed_tokens` is a row *lookup*, so keeping it int8 would save VRAM but ~no decode bandwidth, and
|
| 173 |
+
it stays on the dequant path.
|
| 174 |
+
|
| 175 |
+
**CUDA graphs matter on this model** (`GRAPHS=1`, the `serve.sh` default). It is a hybrid
|
| 176 |
+
architecture that launches many small kernels per token, so eager decode is launch-bound. Use
|
| 177 |
+
`GRAPHS=0` only to debug a capture failure.
|
| 178 |
+
|
| 179 |
+
| VRAM | Example | Suggested launch |
|
| 180 |
+
|---|---|---|
|
| 181 |
+
| **16 GB** | RTX 5060 Ti, RTX 5080 | `ATTN_BACKEND=triton MEM=0.90 CTXLEN=16384 CUDA_GRAPH_BS="1 2 4 8" bash serve.sh` — 10.15 GB of weights leaves a small pool; drop `CTXLEN` first if capture OOMs. Untested. |
|
| 182 |
+
| **24 GB** | RTX 4090, RTX 3090, L40S, A6000 | `MODEL=... bash serve.sh` — the shipped defaults (`MEM=0.72 CTXLEN=65536 MAMBA_RATIO=0.3 RADIX=0 GRAPHS=1 CUDA_GRAPH_BS="1 2 4 8 12 16"`). **Validated on RTX 4090 and RTX 3090.** Single-user profile: the recurrent pool clamps it to **8–9 streams**, so the `12`/`16` capture entries are dropped. For throughput use `MEM=0.86 CTXLEN=32768 MAXREQ=32 MAXMAMBA=32 CUDA_GRAPH_BS="1 2 4 8 12 16 24 32"` (4090: 649 tok/s @ 16; 3090: 383). On a 3090 add `ESCHA_ROUTE=blackwell` for single-user work. |
|
| 183 |
+
| **32 GB** | RTX 5090 | `ATTN_BACKEND=triton MEM=0.85 CTXLEN=65536 MAXREQ=32 MAXMAMBA=32 CUDA_GRAPH_BS="1 2 4 8 12 16 24 32" bash serve.sh` — **measured**: 87.1 tok/s bs=1, 955 tok/s peak at 16 streams, 28.2 GB peak of 32.6. `INT8=on` adds +7.2–7.4% decode at bs=1, paired A/B (and +12.2% KV tokens, not free VRAM — the saved ~1.2 GB is recycled into the pool). |
|
| 184 |
+
| **40 GB+** | A100, H100, L40S-48 | `MEM=0.85 CTXLEN=131072 bash serve.sh`; int8 auto-OFF, raise `CTXLEN`/concurrency freely. Untested. |
|
| 185 |
+
|
| 186 |
+
**Four settings that are worth getting right:**
|
| 187 |
+
|
| 188 |
+
1. **`CUDA_GRAPH_BS` must include your maximum batch size — and must not exceed 32.** A batch size
|
| 189 |
+
that was not captured runs *eager* (the server logs `cuda graph: False` at that
|
| 190 |
+
`#running-req`), which reads roughly 15% low. 32 is the decode kernel's maximum M
|
| 191 |
+
(`python -c "import torch, escha; print(torch.ops.escha.escham_decode_gemv_max_m())"`); beyond it
|
| 192 |
+
a batch falls through to a large-M path meant for prefill, and the runtime declines to capture a
|
| 193 |
+
graph that would bake in the wrong kernel. The default list stops at 16 because that is where
|
| 194 |
+
aggregate throughput peaks on a 4090 — extend it to `"1 2 4 8 12 16 24 32"` if you actually serve
|
| 195 |
+
more than 16 concurrent streams.
|
| 196 |
+
2. **Recurrent state bounds concurrency at short context; the KV pool bounds it at long context.**
|
| 197 |
+
Every stream holds ~0.15 GB of `ssm_state` regardless of context length — that is the limit with
|
| 198 |
+
short prompts. With long ones the KV pool binds first: `max_total_num_tokens` (printed at
|
| 199 |
+
startup) divided by your typical ISL+OSL is your real stream ceiling. **If the server logs a
|
| 200 |
+
`#running-req` below the batch you requested, the surplus is queueing and your TTFT includes
|
| 201 |
+
queue time** — measured on a 3090 at 2048/2048: asked 32, ran 18, mean TTFT 140 s, which reads
|
| 202 |
+
like a prefill catastrophe and is not one.
|
| 203 |
+
3. **`RADIX=0` unless you have measured that prefix caching helps you.** On this hybrid
|
| 204 |
+
architecture the radix cache is incompatible with the overlap scheduler, so `RADIX=1` *silently
|
| 205 |
+
disables it* — the server logs `Disabling overlap schedule since mamba no_buffer is not
|
| 206 |
+
compatible with overlap schedule`. Keep `RADIX=1` only for multi-turn agent traffic that
|
| 207 |
+
genuinely reuses long prefixes.
|
| 208 |
+
4. **If startup OOMs, step `MEM` DOWN, not up.** `mem-fraction-static` sizes the pool the KV cache
|
| 209 |
+
is carved from, so *too low* also fails, with the opposite message ("Not enough memory. Please
|
| 210 |
+
try to increase --mem-fraction-static"). The two failures are: pool-too-small at startup (raise
|
| 211 |
+
`MEM`) and capture-OOM after the weights load (lower `MEM`, or shorten `CUDA_GRAPH_BS`).
|
| 212 |
+
|
| 213 |
+
**Concurrency.** This is a **hybrid** model: most layers are linear-attention with a per-request
|
| 214 |
+
recurrent-state pool, so max concurrency is governed by that pool (`MAXMAMBA`), *not* by KV memory.
|
| 215 |
+
The effective `max_running_requests` is clamped to `min(MAXREQ, MAXMAMBA // ratio)`, ratio ≈ 4 with
|
| 216 |
+
the radix cache on, ≈ 1 with `RADIX=0`. A startup log line reports the clamp.
|
| 217 |
+
|
| 218 |
+
## Bounded thinking (`thinking_budget`)
|
| 219 |
+
|
| 220 |
+
**Read this before running any benchmark or agent against a thinking-ON server.** With thinking on,
|
| 221 |
+
the model can spend the entire `max_tokens` budget reasoning and never reach its answer. You get:
|
| 222 |
+
|
| 223 |
+
```
|
| 224 |
+
finish_reason: "length", content: null # all tokens went to reasoning_content
|
| 225 |
+
```
|
| 226 |
+
|
| 227 |
+
An eval harness scores that as **wrong**, not as slow, and an agent framework sees a no-op. Most
|
| 228 |
+
harnesses cap `max_tokens` at 512–4k, which is where this bites. How long the model thinks varies
|
| 229 |
+
run to run, so a cap that works on one prompt can fail on the next — you want a *guarantee*, not a
|
| 230 |
+
bigger number. `reasoning_effort: "low"` *asks* for less; it does not bound it.
|
| 231 |
+
|
| 232 |
+
A **thinking budget** is that guarantee: after N reasoning tokens the server forces `</think>`, so
|
| 233 |
+
the model must answer within the remaining budget. `serve.sh` enables the processor by default
|
| 234 |
+
(`ENABLE_CLP=1`); pass the budget per request:
|
| 235 |
+
|
| 236 |
+
```bash
|
| 237 |
+
MODEL=/path/to/model bash serve.sh # ENABLE_CLP=1 is the default
|
| 238 |
+
python thinking_budget.py --model /path/to/model > clp.json # one-time payload
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
```jsonc
|
| 242 |
+
{ "messages": [ /* ... */ ], "max_tokens": 32768,
|
| 243 |
+
"chat_template_kwargs": {"enable_thinking": true, "reasoning_effort": "xhigh"},
|
| 244 |
+
"custom_logit_processor": "<contents of clp.json>", // SGLang extension
|
| 245 |
+
"custom_params": {"thinking_budget": 28672} }
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
Leave real headroom between `thinking_budget` and `max_tokens` for the answer itself — at least
|
| 249 |
+
~512 tokens, and far more for a task that answers with code. **Every published thinking-on
|
| 250 |
+
benchmark number for this model (GPQA-Diamond, LiveCodeBench) was produced with `thinking_budget`
|
| 251 |
+
28,672 inside `max_tokens` 32,768** (so 4,096 tokens always remain for the answer), at
|
| 252 |
+
`reasoning_effort: "xhigh"`. The commonsense suite ran thinking-off and uses none of this.
|
| 253 |
+
|
| 254 |
+
`thinking_budget.py` reads `<think>` / `</think>` from your model's own tokenizer, so it works for
|
| 255 |
+
any Escha checkpoint that has a thinking mode. (SGLang's built-in
|
| 256 |
+
`Qwen3ThinkingBudgetLogitProcessor` hardcodes Qwen3's token ids, which are wrong for this model's
|
| 257 |
+
~248k vocabulary — hence the helper.) Verify against a running server with
|
| 258 |
+
`python thinking_budget.py --model <dir> --demo`.
|
| 259 |
+
|
| 260 |
+
If you would rather not think at all, `THINK=0` patches the model's own chat template to
|
| 261 |
+
default-closed — and also suppresses the reasoning-effort instruction the template would otherwise
|
| 262 |
+
inject into the system prompt — at some cost in reasoning-dependent accuracy.
|
| 263 |
+
|
| 264 |
+
## Troubleshooting
|
| 265 |
+
|
| 266 |
+
- **`subprocess.CalledProcessError` from `gcc` during "Capture cuda graph".** Not an escha or
|
| 267 |
+
sglang fault — your host C compiler cannot build triton's shim, and the traceback's visible
|
| 268 |
+
frames (`cuda_graph_runner.py`, `triton_backend.py`) all point elsewhere. Run the compiler
|
| 269 |
+
preflight in *Requirements*. On slim container images a `libisl` registered in dpkg but missing
|
| 270 |
+
from disk breaks `cc1` while `gcc --version` still works.
|
| 271 |
+
|
| 272 |
+
- **`finish_reason: "length"` with `content: null` on a thinking-ON server.** The reasoning ran past
|
| 273 |
+
your `max_tokens`. Raise the cap or, better, set a
|
| 274 |
+
[thinking budget](#bounded-thinking-thinking_budget) so an answer is always produced.
|
| 275 |
+
|
| 276 |
+
- **HTTP 400 mentioning "Unexpected reasoning effort".** `reasoning_effort` must be exactly
|
| 277 |
+
`xhigh`, `medium` or `low`. The chat template raises on anything else.
|
| 278 |
+
|
| 279 |
+
- **`... has no SGLang implementation and the Transformers implementation is not compatible`.** This
|
| 280 |
+
error is usually a **red herring** — the fork *does* register this architecture; the real cause is
|
| 281 |
+
an exception while *importing* the model module (typically an over-new `nvidia-cutlass-dsl`,
|
| 282 |
+
which the wheel pins `<4.4.0`). Surface the true traceback with:
|
| 283 |
+
```bash
|
| 284 |
+
python -c "import sglang.srt.models.qwen3_5"
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
- **`ValueError: Invalid quantization method ...`.** The wheel's registry ships exactly two
|
| 288 |
+
methods, `escha` (dense) and `eschamoe` (mixture-of-experts). Check your model's
|
| 289 |
+
`quantize_config.json` / `config.json` names one of them, and that you installed *this* wheel
|
| 290 |
+
(`python -c "import escha; print(escha.__version__)"` should print `1.1.0+qwen3dense`) — the
|
| 291 |
+
1.0.x wheels registered `eschamoe` only and cannot load a dense model at all.
|
| 292 |
+
|
| 293 |
+
- **`ModuleNotFoundError: pybase64` / `msgspec` / `xgrammar` / `flashinfer` at startup.** The wheel
|
| 294 |
+
declares the full runtime dependency closure, so a clean `pip install ./escha-*.whl` pulls
|
| 295 |
+
these automatically. If you see this, you likely installed the wheel with `--no-deps` or into a
|
| 296 |
+
venv where pip could not resolve them — reinstall without `--no-deps`.
|
| 297 |
+
|
| 298 |
+
- **`ImportError: undefined symbol: _ZN3c10...` on `import escha`.** Wrong torch. The wheel does
|
| 299 |
+
not pin it, so an unpinned install leaves you on a newer minor. Check with
|
| 300 |
+
`python -c "import torch;print(torch.__version__)"` — it must be `2.9.x+cu12`. Fix:
|
| 301 |
+
`pip install "torch==2.9.*" --index-url https://download.pytorch.org/whl/cu128`.
|
| 302 |
+
|
| 303 |
+
- **`PermissionError` on a CUDA `bin` directory during startup.** `TRITON_PTXAS_PATH` must point at
|
| 304 |
+
the ptxas **binary**, not at the directory containing it — `[[ -x <dir> ]]` is true for
|
| 305 |
+
directories, so a shell profile that sets the directory passes a naive check and then fails
|
| 306 |
+
inside triton. `serve.sh` detects and repairs this case; if you launch `launch_server` directly,
|
| 307 |
+
fix the variable yourself (or just unset it — triton ships its own ptxas).
|
| 308 |
+
|
| 309 |
+
- **A local benchmark client reporting HTTP 504 on `127.0.0.1`.** Check for an `HTTP_PROXY` /
|
| 310 |
+
`ALL_PROXY` in your environment without `no_proxy=127.0.0.1`. Proxies time out multi-minute
|
| 311 |
+
requests and synthesize a 504 while the server is still working fine.
|
| 312 |
+
|
| 313 |
+
- **OOM at startup naming a PID that is not yours.** A previous `sglang::scheduler` child is still
|
| 314 |
+
holding the GPU — killing the `launch_server` parent does not reap it. Check
|
| 315 |
+
`nvidia-smi --query-compute-apps=pid,used_memory --format=csv` and kill that PID. (Match on the
|
| 316 |
+
process *name*; a `pkill -f sglang` pattern also matches the shell running it.)
|
| 317 |
+
|
| 318 |
+
- **Garbage / fluent-nonsense generations.** Check `transformers` first (must be ≥ 5.8); the
|
| 319 |
+
transformers 5.x "downgrade to 4.57.1" RoPE warning is benign — do not act on it.
|
| 320 |
+
|
| 321 |
+
- **Two greedy requests give different text.** Expected: greedy is not bit-reproducible across
|
| 322 |
+
requests, because fp16 accumulation order depends on batch composition and a long reasoning chain
|
| 323 |
+
diverges once a near-tie flips. Use `DETERMINISTIC=1` when you need reproducibility.
|
| 324 |
+
|
| 325 |
+
- **Structured output (`response_format` / JSON schema) on a memory-tight card.** The grammar path
|
| 326 |
+
needs ~0.5 GB of extra headroom the first time it runs (xgrammar + its sampler bookkeeping). On a
|
| 327 |
+
card packed to the edge, budget for it — lower `CTXLEN` a notch if a first structured request is
|
| 328 |
+
tight.
|
sglang/escha-1.1.0+qwen3dense-cp312-cp312-manylinux_2_28_x86_64.whl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3897ab095afd5bd45ff225eb27294bd163408871157422a6195534d341f49a51
|
| 3 |
+
size 16750938
|
sglang/serve.sh
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Serve a Qwen3.8-27B 2-/3-bit `escha` (dense) export with escha-sglang, OpenAI-compatible,
|
| 3 |
+
# usable by any OpenAI-compatible client. The `escha` quant method is auto-detected from
|
| 4 |
+
# config.json — nothing here needs to name it.
|
| 5 |
+
#
|
| 6 |
+
# Linux x86-64 + NVIDIA CUDA 12.x only (native or WSL2). See ../README.md and INSTALL.md.
|
| 7 |
+
#
|
| 8 |
+
# Required: set MODEL to your downloaded model dir (a flat folder of safetensors +
|
| 9 |
+
# config + tokenizer — the model repo has no nested subfolder). Everything else has
|
| 10 |
+
# safe, MEASURED defaults for a 24 GB card.
|
| 11 |
+
# MODEL=./Qwen3.8-27B-Escha-W2 bash serve.sh
|
| 12 |
+
set -euo pipefail
|
| 13 |
+
|
| 14 |
+
# ---- paths ----------------------------------------------------------------------------------
|
| 15 |
+
MODEL=${MODEL:?set MODEL=/path/to/your/escha model dir}
|
| 16 |
+
SERVED_NAME=${SERVED_NAME:-escha-qwen38-27b-w2} # opencode/LM Studio must use this id verbatim
|
| 17 |
+
VENV=${VENV:-} # optional: path to your venv; if set, we activate it
|
| 18 |
+
ESCHA_SRC=${ESCHA_SRC:-} # optional: path to escha *source* tree's src/ (only if not pip-installed)
|
| 19 |
+
|
| 20 |
+
# ---- server ---------------------------------------------------------------------------------
|
| 21 |
+
HOST=${HOST:-127.0.0.1} # 0.0.0.0 to serve other machines — do this behind a VPN or tunnel and
|
| 22 |
+
# set API_KEY; never open the raw port to the internet.
|
| 23 |
+
PORT=${PORT:-30000}
|
| 24 |
+
API_KEY=${API_KEY:-} # REQUIRED if HOST=0.0.0.0 / exposed: clients must send this as the
|
| 25 |
+
# Bearer key. Empty = no auth (fine only for localhost).
|
| 26 |
+
MEM=${MEM:-0.72} # mem-fraction-static. 0.72 is the MEASURED default for a 24 GB card at
|
| 27 |
+
# CTXLEN 65536 with CUDA graphs on: capture needs headroom, and 0.78
|
| 28 |
+
# left none once the int8 head is resident. Too LOW is the other
|
| 29 |
+
# failure mode — the server exits at startup with "Not enough memory.
|
| 30 |
+
# Please try to increase --mem-fraction-static". If capture OOMs,
|
| 31 |
+
# step DOWN 0.70 / 0.68 rather than up. On a >24 GB card raise it.
|
| 32 |
+
CTXLEN=${CTXLEN:-65536} # the model's own limit is 262144; 65536 is what fits a 24 GB card
|
| 33 |
+
# alongside the weights. Raise on a bigger card, lower with MEM.
|
| 34 |
+
CHUNK=${CHUNK:-2048} # chunked-prefill-size. Bounds the activation spike on a long prompt;
|
| 35 |
+
# leave it alone unless you are tuning TTFT on a large card.
|
| 36 |
+
MAXREQ=${MAXREQ:-0} # max concurrent requests; 0 = auto.
|
| 37 |
+
MAXMAMBA=${MAXMAMBA:-0} # --max-mamba-cache-size; 0 = auto.
|
| 38 |
+
MAMBA_RATIO=${MAMBA_RATIO:-0.3} # --mamba-full-memory-ratio. 0.3 (not the 0.9 default) is what
|
| 39 |
+
# leaves the pool room for KV *and* CUDA-graph capture at CTXLEN
|
| 40 |
+
# 65536 on 24 GB. Raise it on a card with spare VRAM.
|
| 41 |
+
|
| 42 |
+
# ---- behavior -------------------------------------------------------------------------------
|
| 43 |
+
GRAPHS=${GRAPHS:-1} # 1 = CUDA graphs ON (DEFAULT). This is a hybrid model: many small
|
| 44 |
+
# kernels per token, so eager decode is launch-bound. Set GRAPHS=0
|
| 45 |
+
# only to debug a capture failure; expect several-fold slower decode.
|
| 46 |
+
CUDA_GRAPH_BS=${CUDA_GRAPH_BS:-"1 2 4 8 12 16"} # batch sizes captured when GRAPHS=1.
|
| 47 |
+
# HEADS UP: at the DEFAULT MEM/MAMBA_RATIO below, a 24 GB card's
|
| 48 |
+
# recurrent pool admits only 8-9 streams, so max_running_requests
|
| 49 |
+
# clamps there and the 12/16 entries here are DROPPED (the startup
|
| 50 |
+
# log prints the list actually captured). They start mattering once
|
| 51 |
+
# you raise MAXREQ/MAXMAMBA/MEM. 32 is the decode kernel's max M, so
|
| 52 |
+
# "1 2 4 8 12 16 24 32" captures fine on a throughput profile. Past
|
| 53 |
+
# 32 a batch falls through to a large-M path meant for prefill, and
|
| 54 |
+
# the server refuses to capture it.
|
| 55 |
+
THINK=${THINK:-1} # 1 = thinking-ON + --reasoning-parser qwen3 (simplest, always works).
|
| 56 |
+
# 0 = thinking-OFF: patches the model's chat template to default-closed
|
| 57 |
+
# AND drops the reasoning parser.
|
| 58 |
+
# A per-request chat_template_kwargs {"enable_thinking": false} is
|
| 59 |
+
# honoured on a THINK=1 server, so THINK=0 is not required for
|
| 60 |
+
# clients that disable thinking per call. With thinking ON, the
|
| 61 |
+
# reasoning arrives in `reasoning_content` and the answer in
|
| 62 |
+
# `content`: read BOTH.
|
| 63 |
+
# This model also takes chat_template_kwargs {"reasoning_effort":
|
| 64 |
+
# "xhigh"|"medium"|"low"} (default xhigh). An unrecognised value
|
| 65 |
+
# makes the template raise, which surfaces as a 400.
|
| 66 |
+
RADIX=${RADIX:-0} # 0 = prefix caching OFF (DEFAULT here). On this hybrid architecture the
|
| 67 |
+
# radix cache without speculative decoding disables the overlap
|
| 68 |
+
# scheduler, which costs more than the prefix reuse returns. Set
|
| 69 |
+
# RADIX=1 only if you have measured a win on your own workload.
|
| 70 |
+
ESCHA_ROUTE=${ESCHA_ROUTE:-} # kernel launch geometry; empty = auto-select per GPU. The two
|
| 71 |
+
# routes are BIT-IDENTICAL, so this is purely a perf choice. On
|
| 72 |
+
# AMPERE (sm_80/86) auto picks `lovelace` and that is the SLOWER one
|
| 73 |
+
# at batch 1: an RTX 3090 measured 23.6 -> 40.7 tok/s (1.72x) with
|
| 74 |
+
# ESCHA_ROUTE=blackwell. Parity at batch 2-16, so set it for
|
| 75 |
+
# single-user serving and leave it auto for batched.
|
| 76 |
+
if [[ -n "$ESCHA_ROUTE" ]]; then export ESCHA_ROUTE; fi # NB: `[ .. ] && export` would abort
|
| 77 |
+
# the script under `set -e` when unset.
|
| 78 |
+
ATTN_BACKEND=${ATTN_BACKEND:-} # full-attention backend override. Empty = sglang default (fine on
|
| 79 |
+
# Ada/4090). REQUIRED on consumer Blackwell (RTX 50 / sm_120): the
|
| 80 |
+
# default resolves to flashinfer but the fork asserts triton/trtllm_mha
|
| 81 |
+
# /fa4 for hybrid models on Blackwell -> set ATTN_BACKEND=triton.
|
| 82 |
+
TOOL_PARSER=${TOOL_PARSER:-qwen3_coder} # matches this template's <function=..>/<parameter=..> XML.
|
| 83 |
+
REASONING_PARSER=${REASONING_PARSER:-qwen3}
|
| 84 |
+
ENABLE_CLP=${ENABLE_CLP:-1} # 1 = --enable-custom-logit-processor (DEFAULT). Needed for the
|
| 85 |
+
# per-request thinking budget (see thinking_budget.py); it is how
|
| 86 |
+
# every published benchmark number for this model was produced.
|
| 87 |
+
TRUNCATE=${TRUNCATE:-1} # 1 = --allow-auto-truncate (DEFAULT). A prompt longer than CTXLEN is
|
| 88 |
+
# truncated instead of HARD-REJECTED. Without it an over-long prompt
|
| 89 |
+
# returns an error that reads like a model failure.
|
| 90 |
+
JSON_WS=${JSON_WS:-1} # 1 = --constrained-json-disable-any-whitespace (DEFAULT).
|
| 91 |
+
# With permissive whitespace, a json_schema `integer`/`number`
|
| 92 |
+
# property that follows a string property can take a spurious
|
| 93 |
+
# leading `-`: the grammar allows a "\n " branch after the
|
| 94 |
+
# colon, and from that off-distribution state the sign/digit choice
|
| 95 |
+
# is near-tied. Compact JSON is what structured-output consumers
|
| 96 |
+
# want anyway. `JSON_WS=0` opts out.
|
| 97 |
+
DETERMINISTIC=${DETERMINISTIC:-0} # 1 = --enable-deterministic-inference. Greedy is NOT
|
| 98 |
+
# bit-reproducible across requests by default (fp16 accumulation
|
| 99 |
+
# order depends on batch composition): long reasoning chains diverge
|
| 100 |
+
# once a near-tie flips. Set 1 for eval reproducibility; expect a
|
| 101 |
+
# throughput cost. NOT AVAILABLE on consumer Blackwell (RTX 50 /
|
| 102 |
+
# sm_120): the deterministic attention kernel asks for 104 KB of
|
| 103 |
+
# shared memory per block, above the sm_120 limit, and the server
|
| 104 |
+
# exits at startup. Works on Ampere / Ada / Hopper.
|
| 105 |
+
|
| 106 |
+
# ---- environment the escha stack needs ------------------------------------------------------
|
| 107 |
+
[[ -n "$VENV" && -f "$VENV/bin/activate" ]] && source "$VENV/bin/activate"
|
| 108 |
+
PYBIN=$(command -v python)
|
| 109 |
+
TORCH_LIB=$("$PYBIN" -c 'import torch,os;print(os.path.join(os.path.dirname(torch.__file__),"lib"))')
|
| 110 |
+
export LD_LIBRARY_PATH="$TORCH_LIB${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" # escha._C links libc10
|
| 111 |
+
[[ -n "$ESCHA_SRC" ]] && export PYTHONPATH="$ESCHA_SRC${PYTHONPATH:+:$PYTHONPATH}"
|
| 112 |
+
export SGLANG_MAMBA_CONV_DTYPE=float16 # must match --dtype float16 or causal_conv1d crashes
|
| 113 |
+
export SGLANG_DISABLE_CUDNN_CHECK=1
|
| 114 |
+
# This checkpoint is TEXT-ONLY (the vision tower is in the quant `ignore` list — the checkpoint
|
| 115 |
+
# has zero visual.* tensors). Default to NOT instantiating the vision tower: it saves VRAM on
|
| 116 |
+
# every card, and prevents an image request from silently decoding through a random-init tower
|
| 117 |
+
# (garbage instead of an error).
|
| 118 |
+
export SGLANG_VLM_TEXT_ONLY=${SGLANG_VLM_TEXT_ONLY:-1}
|
| 119 |
+
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True # correct var name — cuts reserved-vs-allocated
|
| 120 |
+
export PYTORCH_ALLOC_CONF=expandable_segments:True # (alias on newer torch; keep both)
|
| 121 |
+
export HF_HUB_OFFLINE=1
|
| 122 |
+
|
| 123 |
+
# ---- decode/prefill kernel profile ----------------------------------------------------------
|
| 124 |
+
# This is the exact profile every published number for this model was measured on. All five knobs
|
| 125 |
+
# are pinned rather than left to their defaults, because a stale value inherited from a shell
|
| 126 |
+
# would read as a quality regression rather than as a configuration difference.
|
| 127 |
+
#
|
| 128 |
+
# ESCHA_STRICT=1 — refuse to silently fall back to the reference decode path; raise instead.
|
| 129 |
+
# The two flags are mutually exclusive AT IMPORT, so the opposite one must not be in the
|
| 130 |
+
# environment: unset it rather than trusting the caller's shell.
|
| 131 |
+
unset ESCHA_FORCE_REF 2>/dev/null || true
|
| 132 |
+
export ESCHA_STRICT=${ESCHA_STRICT:-1}
|
| 133 |
+
# ESCHA_MULTI=1 — one fused launch per multi-shard projection (q+k+v, gate+up) instead of a
|
| 134 |
+
# per-shard loop. This is the default; pinned because it is load-bearing for decode latency.
|
| 135 |
+
export ESCHA_MULTI=${ESCHA_MULTI:-1}
|
| 136 |
+
# ESCHA_PREFILL=fused — run prefill through the fused GEMM rather than a dense reconstruction.
|
| 137 |
+
export ESCHA_PREFILL=${ESCHA_PREFILL:-fused}
|
| 138 |
+
# ESCHA_PREFILL_ACC=mixed / ESCHA_PREFILL_FP16ACC=0 — prefill accumulation precision. Under
|
| 139 |
+
# ESCHA_STRICT every shard's prefill goes through this path, so these govern prefill numerics
|
| 140 |
+
# for the whole model. "mixed" is the validated setting.
|
| 141 |
+
export ESCHA_PREFILL_ACC=${ESCHA_PREFILL_ACC:-mixed}
|
| 142 |
+
export ESCHA_PREFILL_FP16ACC=${ESCHA_PREFILL_FP16ACC:-0}
|
| 143 |
+
# ESCHA_DENSE_FUSED_EPI=0 — the fused split-K epilogue tail is OFF. It is not a numerics-neutral
|
| 144 |
+
# speedup on this model (a measured multi-point accuracy regression on MATH-500), and it is the
|
| 145 |
+
# only process-global mutable state on the decode side: a leaked environment value would look
|
| 146 |
+
# like a quantization regression. Pinned off.
|
| 147 |
+
export ESCHA_DENSE_FUSED_EPI=${ESCHA_DENSE_FUSED_EPI:-0}
|
| 148 |
+
# ESCHA_ROUTE is now a first-class knob above (see the AMPERE note there) — it is no longer
|
| 149 |
+
# "not set here". Other passthrough knobs, still not set here: ESCHA_KGROUP=0 reverts mixed-rate
|
| 150 |
+
# projections to a per-shard launch (debug only).
|
| 151 |
+
|
| 152 |
+
# ---- int8-as-stored lm_head -----------------------------------------------------------------
|
| 153 |
+
# The runtime can keep lm_head INT8 in VRAM ("int8-as-stored") instead of dequantizing it to fp16
|
| 154 |
+
# at load. On this dense model the fp16 head is ~2.4 GB of the bytes read EVERY decode step vs
|
| 155 |
+
# ~1.2 GB as stored, so it is worth real single-user throughput plus ~1.2 GB of VRAM. Unlike the
|
| 156 |
+
# mixture-of-experts runtime, only lm_head is affected here: embed_tokens is a row LOOKUP, so
|
| 157 |
+
# keeping it int8 would save VRAM but ~no decode bandwidth, and it stays on the dequant path.
|
| 158 |
+
# INT8=auto (default) — VRAM-threshold rule: ON at <= SGLANG_INT8_AUTO_VRAM_GB (24), OFF above.
|
| 159 |
+
# INT8=on — force ON: single-user / latency-sensitive serving on ANY card size.
|
| 160 |
+
# INT8=off — force OFF: batched / high-concurrency throughput serving on >24 GB cards.
|
| 161 |
+
INT8=${INT8:-auto}
|
| 162 |
+
case "$INT8" in
|
| 163 |
+
on) export SGLANG_INT8_LM_HEAD=1 ;;
|
| 164 |
+
off) export SGLANG_INT8_LM_HEAD=0 ;;
|
| 165 |
+
auto) : ;; # leave SGLANG_INT8_LM_HEAD as the caller set it (or unset -> AUTO)
|
| 166 |
+
*) echo "[serve] ERROR: INT8 must be auto|on|off (got '$INT8')" >&2; exit 1 ;;
|
| 167 |
+
esac
|
| 168 |
+
|
| 169 |
+
# ---- ptxas discovery ------------------------------------------------------------------------
|
| 170 |
+
# * Triton — which torch already pulls in — SHIPS a matching ptxas at
|
| 171 |
+
# site-packages/triton/backends/nvidia/bin/ptxas. Probe that FIRST: a CUDA toolkit is NOT a
|
| 172 |
+
# requirement of this runtime.
|
| 173 |
+
# * Validate whatever the caller exported. A common shell profile sets TRITON_PTXAS_PATH to the
|
| 174 |
+
# CUDA *bin directory*; `[[ -x <dir> ]]` is TRUE for directories, so a naive check accepts it
|
| 175 |
+
# and triton then dies mid-startup with a permission error on that directory. Require a
|
| 176 |
+
# regular file (-f) AND executable (-x); if a directory was given, try <dir>/ptxas first.
|
| 177 |
+
_ptxas_ok() { [[ -n "${1:-}" && -f "$1" && -x "$1" ]]; }
|
| 178 |
+
if ! _ptxas_ok "${TRITON_PTXAS_PATH:-}"; then
|
| 179 |
+
if [[ -n "${TRITON_PTXAS_PATH:-}" ]]; then
|
| 180 |
+
if _ptxas_ok "${TRITON_PTXAS_PATH%/}/ptxas"; then
|
| 181 |
+
TRITON_PTXAS_PATH="${TRITON_PTXAS_PATH%/}/ptxas"
|
| 182 |
+
echo "[serve] TRITON_PTXAS_PATH was a directory; using $TRITON_PTXAS_PATH"
|
| 183 |
+
else
|
| 184 |
+
echo "[serve] ignoring TRITON_PTXAS_PATH='$TRITON_PTXAS_PATH' (not an executable file)"
|
| 185 |
+
TRITON_PTXAS_PATH=""
|
| 186 |
+
fi
|
| 187 |
+
fi
|
| 188 |
+
if [[ -z "${TRITON_PTXAS_PATH:-}" ]]; then
|
| 189 |
+
for p in \
|
| 190 |
+
"$("$PYBIN" -c 'import os,triton;print(os.path.join(os.path.dirname(triton.__file__),"backends","nvidia","bin","ptxas"))' 2>/dev/null)" \
|
| 191 |
+
/usr/local/cuda*/bin/ptxas \
|
| 192 |
+
"$(command -v ptxas 2>/dev/null)"; do
|
| 193 |
+
_ptxas_ok "$p" && { TRITON_PTXAS_PATH="$p"; break; }
|
| 194 |
+
done
|
| 195 |
+
fi
|
| 196 |
+
export TRITON_PTXAS_PATH
|
| 197 |
+
fi
|
| 198 |
+
: "${TRITON_PTXAS_PATH:?no usable ptxas found. It normally ships inside the venv with triton (a torch dependency) — check that \`python -c \"import triton\"\` works. Otherwise point TRITON_PTXAS_PATH at a ptxas BINARY (not its directory).}"
|
| 199 |
+
|
| 200 |
+
# Only if you hit it: the first time triton compiles a kernel (during warmup AND during CUDA-graph
|
| 201 |
+
# capture), the multi-GB scheduler must fork() to spawn ptxas. Under the default overcommit
|
| 202 |
+
# heuristic that fork can fail -> the server dies with a triton JIT memory-allocation failure at
|
| 203 |
+
# kernel-compile time. Setting vm.overcommit_memory=1 fixes it. Many boxes never hit this (a warm
|
| 204 |
+
# triton cache, or enough free RAM), so it is advisory. In a container the sysctl is often
|
| 205 |
+
# read-only — then it is the HOST's job.
|
| 206 |
+
if [[ "$(cat /proc/sys/vm/overcommit_memory 2>/dev/null)" == "0" ]]; then
|
| 207 |
+
echo "[serve] note: if startup dies with a triton JIT memory-allocation failure at kernel-" >&2
|
| 208 |
+
echo "[serve] compile time, set 'sudo sysctl -w vm.overcommit_memory=1' (persist via" >&2
|
| 209 |
+
echo "[serve] /etc/sysctl.d/99-escha.conf). In a read-only container this must be set" >&2
|
| 210 |
+
echo "[serve] on the host; otherwise it's safe to ignore." >&2
|
| 211 |
+
fi
|
| 212 |
+
|
| 213 |
+
# Warn when the GPU is already substantially occupied (a foreign server / another model on a
|
| 214 |
+
# shared box is the #1 cause of an immediate, confusing pool-allocation failure at startup).
|
| 215 |
+
_USED_MB=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -dc '0-9')
|
| 216 |
+
if [[ -n "$_USED_MB" && "$_USED_MB" -gt 1024 ]]; then
|
| 217 |
+
echo "[serve] WARNING: GPU already has ${_USED_MB} MiB in use by other processes (nvidia-smi)." >&2
|
| 218 |
+
echo "[serve] The static pool sizing assumes a free GPU — expect an allocation failure" >&2
|
| 219 |
+
echo "[serve] if another server is resident. Check: nvidia-smi" >&2
|
| 220 |
+
fi
|
| 221 |
+
|
| 222 |
+
# ---- assemble flags -------------------------------------------------------------------------
|
| 223 |
+
# Piecewise CUDA graphs stay OFF on this model family in every profile (the fork's piecewise path
|
| 224 |
+
# does not handle its positional encoding), so it is not a knob.
|
| 225 |
+
GRAPH_ARGS=(--disable-piecewise-cuda-graph)
|
| 226 |
+
[[ "$GRAPHS" == "1" ]] && GRAPH_ARGS+=(--cuda-graph-bs $CUDA_GRAPH_BS) || GRAPH_ARGS+=(--disable-cuda-graph)
|
| 227 |
+
|
| 228 |
+
POOL_ARGS=()
|
| 229 |
+
[[ "$MAXREQ" != "0" ]] && POOL_ARGS+=(--max-running-requests "$MAXREQ")
|
| 230 |
+
[[ "$MAXMAMBA" != "0" ]] && POOL_ARGS+=(--max-mamba-cache-size "$MAXMAMBA")
|
| 231 |
+
[[ -n "$MAMBA_RATIO" ]] && POOL_ARGS+=(--mamba-full-memory-ratio "$MAMBA_RATIO")
|
| 232 |
+
RADIX_ARGS=(); [[ "$RADIX" == "0" ]] && RADIX_ARGS+=(--disable-radix-cache)
|
| 233 |
+
ATTN_ARGS=(); [[ -n "$ATTN_BACKEND" ]] && ATTN_ARGS+=(--attention-backend "$ATTN_BACKEND")
|
| 234 |
+
AUTH_ARGS=(); [[ -n "$API_KEY" ]] && AUTH_ARGS+=(--api-key "$API_KEY")
|
| 235 |
+
CLP_ARGS=(); [[ "$ENABLE_CLP" == "1" ]] && CLP_ARGS+=(--enable-custom-logit-processor)
|
| 236 |
+
TRUNC_ARGS=(); [[ "$TRUNCATE" == "1" ]] && TRUNC_ARGS+=(--allow-auto-truncate)
|
| 237 |
+
JSON_ARGS=(); [[ "$JSON_WS" == "1" ]] && JSON_ARGS+=(--constrained-json-disable-any-whitespace)
|
| 238 |
+
DET_ARGS=(); [[ "$DETERMINISTIC" == "1" ]] && DET_ARGS+=(--enable-deterministic-inference)
|
| 239 |
+
if [[ "$HOST" != "127.0.0.1" && -z "$API_KEY" ]]; then
|
| 240 |
+
echo "[serve] WARNING: HOST=$HOST is exposed but API_KEY is empty — no auth. Set API_KEY." >&2
|
| 241 |
+
fi
|
| 242 |
+
|
| 243 |
+
REASON_ARGS=(); TEMPLATE_ARGS=()
|
| 244 |
+
if [[ "$THINK" == "1" ]]; then
|
| 245 |
+
REASON_ARGS+=(--reasoning-parser "$REASONING_PARSER")
|
| 246 |
+
else
|
| 247 |
+
# thinking-OFF: generate a patched chat template (default-closed <think>) from the model's own.
|
| 248 |
+
NOTHINK="${TMPDIR:-/tmp}/escha_nothink_$(basename "$MODEL").jinja"
|
| 249 |
+
"$PYBIN" - "$MODEL" "$NOTHINK" <<'PY'
|
| 250 |
+
import json, os, sys
|
| 251 |
+
mdir, out = sys.argv[1], sys.argv[2]
|
| 252 |
+
src = os.path.join(mdir, "chat_template.jinja")
|
| 253 |
+
tmpl = open(src).read() if os.path.exists(src) else json.load(open(os.path.join(mdir,"tokenizer_config.json")))["chat_template"]
|
| 254 |
+
# Flip the "enable_thinking" default from ON to OFF. TWO edits are needed on this template and
|
| 255 |
+
# only the first is mandatory:
|
| 256 |
+
# (1) the generation-prompt block that decides whether <think> is left open or pre-closed;
|
| 257 |
+
# (2) the reasoning-effort gate near the top, which injects a "think carefully..." system
|
| 258 |
+
# instruction whenever enable_thinking is UNDEFINED. Without (2) a thinking-off server
|
| 259 |
+
# still tells the model to reason at length while pre-closing the block it would reason in.
|
| 260 |
+
# Each edit is attempted independently; (1) is required, (2) is best-effort.
|
| 261 |
+
GEN_PROMPT = [
|
| 262 |
+
("{%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- else %}\n {{- '<think>\\n' }}\n {%- endif %}",
|
| 263 |
+
"{%- if enable_thinking is defined and enable_thinking is true %}\n {{- '<think>\\n' }}\n {%- else %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- endif %}"),
|
| 264 |
+
("{%- set enable_thinking = enable_thinking if enable_thinking is defined else true %}",
|
| 265 |
+
"{%- set enable_thinking = enable_thinking if enable_thinking is defined else false %}"),
|
| 266 |
+
]
|
| 267 |
+
EFFORT_GATE = [
|
| 268 |
+
("{%- if enable_thinking is undefined or enable_thinking is true %}",
|
| 269 |
+
"{%- if enable_thinking is defined and enable_thinking is true %}"),
|
| 270 |
+
]
|
| 271 |
+
patched = tmpl
|
| 272 |
+
for old, new in GEN_PROMPT:
|
| 273 |
+
if old in patched:
|
| 274 |
+
patched = patched.replace(old, new); break
|
| 275 |
+
else:
|
| 276 |
+
sys.exit("THINK=0: could not find the enable_thinking block to patch in this model's chat "
|
| 277 |
+
"template. Serve with THINK=1, or hand-patch the template and pass it via --chat-template.")
|
| 278 |
+
for old, new in EFFORT_GATE:
|
| 279 |
+
if old in patched:
|
| 280 |
+
patched = patched.replace(old, new); break
|
| 281 |
+
open(out, "w").write(patched)
|
| 282 |
+
print(out)
|
| 283 |
+
PY
|
| 284 |
+
TEMPLATE_ARGS+=(--chat-template "$NOTHINK") # no reasoning parser when thinking is pre-closed
|
| 285 |
+
fi
|
| 286 |
+
|
| 287 |
+
echo "[serve] MODEL=$MODEL NAME=$SERVED_NAME $HOST:$PORT MEM=$MEM CTX=$CTXLEN GRAPHS=$GRAPHS THINK=$THINK RADIX=$RADIX INT8=$INT8"
|
| 288 |
+
echo "[serve] tool-call-parser=$TOOL_PARSER ptxas=$TRITON_PTXAS_PATH"
|
| 289 |
+
|
| 290 |
+
exec "$PYBIN" -m sglang.launch_server \
|
| 291 |
+
--model-path "$MODEL" \
|
| 292 |
+
--served-model-name "$SERVED_NAME" \
|
| 293 |
+
--host "$HOST" --port "$PORT" \
|
| 294 |
+
--dtype float16 \
|
| 295 |
+
--mem-fraction-static "$MEM" \
|
| 296 |
+
--context-length "$CTXLEN" \
|
| 297 |
+
--chunked-prefill-size "$CHUNK" \
|
| 298 |
+
--sampling-backend pytorch \
|
| 299 |
+
--trust-remote-code \
|
| 300 |
+
--tool-call-parser "$TOOL_PARSER" \
|
| 301 |
+
"${REASON_ARGS[@]}" \
|
| 302 |
+
"${TEMPLATE_ARGS[@]}" \
|
| 303 |
+
"${POOL_ARGS[@]}" \
|
| 304 |
+
"${RADIX_ARGS[@]}" \
|
| 305 |
+
"${ATTN_ARGS[@]}" \
|
| 306 |
+
"${AUTH_ARGS[@]}" \
|
| 307 |
+
"${CLP_ARGS[@]}" \
|
| 308 |
+
"${TRUNC_ARGS[@]}" \
|
| 309 |
+
"${GRAPH_ARGS[@]}" \
|
| 310 |
+
"${JSON_ARGS[@]}" \
|
| 311 |
+
"${DET_ARGS[@]}" \
|
| 312 |
+
--log-level info \
|
| 313 |
+
"$@" # forward any extra sglang flag verbatim
|
sglang/thinking_budget.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Bounded thinking for Escha `qwen3_5` models on SGLang.
|
| 3 |
+
|
| 4 |
+
Problem this solves: with thinking on, the model may spend the whole
|
| 5 |
+
`max_tokens` budget reasoning and never emit an answer. The response then comes
|
| 6 |
+
back as `finish_reason: "length"` with `content: null` (all tokens went to
|
| 7 |
+
`reasoning_content`), which an eval harness scores as *wrong* rather than as
|
| 8 |
+
*slow*, and an agent framework sees as a no-op.
|
| 9 |
+
|
| 10 |
+
A thinking budget fixes it: once the reasoning has run for N tokens, the server
|
| 11 |
+
forces `</think>` so the model must produce its answer inside the remaining
|
| 12 |
+
budget. This is SGLang's stock `ThinkingBudgetLogitProcessor`; the only reason a
|
| 13 |
+
helper is needed is that SGLang's built-in Qwen3 subclass hardcodes Qwen3's
|
| 14 |
+
token ids (151667/151668), which are wrong for this model's ~248k vocab — so we
|
| 15 |
+
look the ids up from the model's own tokenizer.
|
| 16 |
+
|
| 17 |
+
Serve with the processor enabled:
|
| 18 |
+
|
| 19 |
+
ENABLE_CLP=1 MODEL=/path/to/model bash serve.sh
|
| 20 |
+
|
| 21 |
+
Emit the payload once, then reuse it for every request:
|
| 22 |
+
|
| 23 |
+
python thinking_budget.py --model /path/to/model > clp.json
|
| 24 |
+
|
| 25 |
+
Send it (any OpenAI client — the two extra fields are SGLang extensions):
|
| 26 |
+
|
| 27 |
+
{"messages": [...], "max_tokens": 4096,
|
| 28 |
+
"chat_template_kwargs": {"enable_thinking": true, "reasoning_effort": "xhigh"},
|
| 29 |
+
"custom_logit_processor": <contents of clp.json>,
|
| 30 |
+
"custom_params": {"thinking_budget": 2048}}
|
| 31 |
+
|
| 32 |
+
Budget guidance: leave enough headroom between `thinking_budget` and
|
| 33 |
+
`max_tokens` for the answer itself — at least ~512 tokens, and far more for a
|
| 34 |
+
task that answers with code. Every published benchmark number for this model
|
| 35 |
+
was produced with `thinking_budget` 28,672 inside `max_tokens` 32,768 (so
|
| 36 |
+
4,096 tokens always remain for the answer), at `reasoning_effort: "xhigh"`.
|
| 37 |
+
|
| 38 |
+
`reasoning_effort` is a second, independent lever this model exposes through
|
| 39 |
+
`chat_template_kwargs` — `"xhigh"` (default), `"medium"` or `"low"`. It asks
|
| 40 |
+
for shorter reasoning; the budget *enforces* it. They compose.
|
| 41 |
+
|
| 42 |
+
Self-check (`--demo`) needs a running server and reports, for one prompt, the
|
| 43 |
+
finish_reason / reasoning length / answer length with and without a budget.
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
from __future__ import annotations
|
| 47 |
+
|
| 48 |
+
import argparse
|
| 49 |
+
import json
|
| 50 |
+
import sys
|
| 51 |
+
|
| 52 |
+
_THINK_OPEN = "<think>"
|
| 53 |
+
_THINK_CLOSE = "</think>"
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def token_ids(model_dir: str) -> tuple[int, int, int]:
|
| 57 |
+
"""(start, end, newline) token ids from the model's own tokenizer."""
|
| 58 |
+
from transformers import AutoTokenizer
|
| 59 |
+
|
| 60 |
+
tok = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=False)
|
| 61 |
+
start, end = tok.convert_tokens_to_ids([_THINK_OPEN, _THINK_CLOSE])
|
| 62 |
+
unk = getattr(tok, "unk_token_id", None)
|
| 63 |
+
if start is None or end is None or start == unk or end == unk:
|
| 64 |
+
raise SystemExit(
|
| 65 |
+
f"{model_dir}: tokenizer has no {_THINK_OPEN}/{_THINK_CLOSE} tokens — "
|
| 66 |
+
"this model has no thinking mode, so a thinking budget does not apply."
|
| 67 |
+
)
|
| 68 |
+
newline = tok.encode("\n", add_special_tokens=False)
|
| 69 |
+
if len(newline) != 1:
|
| 70 |
+
raise SystemExit(f"unexpected newline tokenization: {newline!r}")
|
| 71 |
+
return int(start), int(end), int(newline[0])
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def payload(model_dir: str) -> str:
|
| 75 |
+
"""The JSON string to pass as `custom_logit_processor`."""
|
| 76 |
+
from sglang.srt.sampling.custom_logit_processor import (
|
| 77 |
+
ThinkingBudgetLogitProcessor,
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
start, end, newline = token_ids(model_dir)
|
| 81 |
+
|
| 82 |
+
# dill serializes this subclass by value; the base class is resolved by name
|
| 83 |
+
# on the server, so the server must run the same SGLang version.
|
| 84 |
+
class EschaThinkingBudgetLogitProcessor(ThinkingBudgetLogitProcessor):
|
| 85 |
+
THINKING_START_TOKEN_ID = start
|
| 86 |
+
THINKING_END_TOKEN_ID = end
|
| 87 |
+
NEW_LINE_TOKEN_ID = newline
|
| 88 |
+
|
| 89 |
+
return EschaThinkingBudgetLogitProcessor.to_str()
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def demo(base_url: str, model: str, clp: str, budget: int, max_tokens: int) -> None:
|
| 93 |
+
import urllib.request
|
| 94 |
+
|
| 95 |
+
prompt = (
|
| 96 |
+
"What is the Hubble constant from Planck, and from local distance-ladder "
|
| 97 |
+
"measurements? Give both numbers with units."
|
| 98 |
+
)
|
| 99 |
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
| 100 |
+
for label, body in (
|
| 101 |
+
("no budget ", {}),
|
| 102 |
+
(f"budget {budget}", {"custom_logit_processor": clp,
|
| 103 |
+
"custom_params": {"thinking_budget": budget}}),
|
| 104 |
+
):
|
| 105 |
+
req = {"model": model, "messages": [{"role": "user", "content": prompt}],
|
| 106 |
+
"max_tokens": max_tokens, "temperature": 0,
|
| 107 |
+
"chat_template_kwargs": {"enable_thinking": True}, **body}
|
| 108 |
+
with opener.open(urllib.request.Request(
|
| 109 |
+
f"{base_url}/chat/completions", json.dumps(req).encode(),
|
| 110 |
+
{"Content-Type": "application/json"}), timeout=1800) as r:
|
| 111 |
+
out = json.load(r)
|
| 112 |
+
choice = out["choices"][0]
|
| 113 |
+
msg = choice["message"]
|
| 114 |
+
reasoning = msg.get("reasoning_content") or ""
|
| 115 |
+
content = msg.get("content") or ""
|
| 116 |
+
print(f"{label}: finish_reason={choice['finish_reason']:>6} "
|
| 117 |
+
f"completion_tokens={out['usage']['completion_tokens']:>6} "
|
| 118 |
+
f"reasoning_chars={len(reasoning):>6} answer_chars={len(content):>5}"
|
| 119 |
+
f"{' <-- NO ANSWER' if not content else ''}")
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def main() -> None:
|
| 123 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 124 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 125 |
+
ap.add_argument("--model", required=True, help="model directory (for the tokenizer)")
|
| 126 |
+
ap.add_argument("--demo", action="store_true", help="probe a running server")
|
| 127 |
+
ap.add_argument("--base-url", default="http://127.0.0.1:30000/v1")
|
| 128 |
+
ap.add_argument("--served-name", default="escha-qwen38-27b-w2")
|
| 129 |
+
ap.add_argument("--budget", type=int, default=2048)
|
| 130 |
+
ap.add_argument("--max-tokens", type=int, default=4096)
|
| 131 |
+
args = ap.parse_args()
|
| 132 |
+
|
| 133 |
+
clp = payload(args.model)
|
| 134 |
+
if args.demo:
|
| 135 |
+
start, end, newline = token_ids(args.model)
|
| 136 |
+
print(f"# {_THINK_OPEN}={start} {_THINK_CLOSE}={end} newline={newline}",
|
| 137 |
+
file=sys.stderr)
|
| 138 |
+
demo(args.base_url, args.served_name, clp, args.budget, args.max_tokens)
|
| 139 |
+
else:
|
| 140 |
+
print(clp)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
if __name__ == "__main__":
|
| 144 |
+
main()
|