autumn-kvcache 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autumn_kvcache-0.1.0/LICENSE +201 -0
- autumn_kvcache-0.1.0/PKG-INFO +323 -0
- autumn_kvcache-0.1.0/README.md +308 -0
- autumn_kvcache-0.1.0/autumn_kvcache/__init__.py +33 -0
- autumn_kvcache-0.1.0/autumn_kvcache/_bridge.py +84 -0
- autumn_kvcache-0.1.0/autumn_kvcache/_identity.py +401 -0
- autumn_kvcache-0.1.0/autumn_kvcache/_keys.py +145 -0
- autumn_kvcache-0.1.0/autumn_kvcache/sglang_backend.py +883 -0
- autumn_kvcache-0.1.0/autumn_kvcache/vllm_connector.py +1279 -0
- autumn_kvcache-0.1.0/autumn_kvcache.egg-info/PKG-INFO +323 -0
- autumn_kvcache-0.1.0/autumn_kvcache.egg-info/SOURCES.txt +23 -0
- autumn_kvcache-0.1.0/autumn_kvcache.egg-info/dependency_links.txt +1 -0
- autumn_kvcache-0.1.0/autumn_kvcache.egg-info/requires.txt +4 -0
- autumn_kvcache-0.1.0/autumn_kvcache.egg-info/top_level.txt +1 -0
- autumn_kvcache-0.1.0/pyproject.toml +28 -0
- autumn_kvcache-0.1.0/setup.cfg +4 -0
- autumn_kvcache-0.1.0/tests/test_iface_resolution.py +216 -0
- autumn_kvcache-0.1.0/tests/test_load_atomic.py +157 -0
- autumn_kvcache-0.1.0/tests/test_model_identity.py +574 -0
- autumn_kvcache-0.1.0/tests/test_no_hit_dedup.py +177 -0
- autumn_kvcache-0.1.0/tests/test_page_layouts.py +423 -0
- autumn_kvcache-0.1.0/tests/test_smoke.py +167 -0
- autumn_kvcache-0.1.0/tests/test_vllm_arena.py +82 -0
- autumn_kvcache-0.1.0/tests/test_vllm_dataplane.py +173 -0
- autumn_kvcache-0.1.0/tests/test_vllm_long_prefix.py +390 -0
|
@@ -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 [yyyy] [name of copyright owner]
|
|
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.
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autumn-kvcache
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: sglang HiCache L3 and vLLM KV-connector backends on autumn-rs
|
|
5
|
+
Author: autumn-rs contributors
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/journeymidnight/autumn_kvcache
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: autumn-rs==0.1.0
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Provides-Extra: sglang
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# autumn-kvcache
|
|
17
|
+
|
|
18
|
+
Daemon-less KV-cache **L3 storage backends** built on the autumn-rs partition
|
|
19
|
+
layer. Two adapters, one data plane (`autumn.BatchClient` + the sync/async
|
|
20
|
+
`_bridge`):
|
|
21
|
+
|
|
22
|
+
| Adapter | Engine | Contract | Module |
|
|
23
|
+
|---------|--------|----------|--------|
|
|
24
|
+
| `AutumnKVCacheStorage` | **sglang** | HiCache `HiCacheStorage` (L3) | `autumn_kvcache.sglang_backend` |
|
|
25
|
+
| `AutumnKVConnector` | **vLLM** | `KVConnectorBase_V1` (external KV transfer) | `autumn_kvcache.vllm_connector` |
|
|
26
|
+
|
|
27
|
+
## Which autumn-rs this tracks
|
|
28
|
+
|
|
29
|
+
`AUTUMN_REV` names the autumn-rs commit this tree was last verified against.
|
|
30
|
+
It matters more than a normal version pin: the adapters ride autumn's rkyv
|
|
31
|
+
client, and a client/cluster mismatch does **not** fail the handshake — it can
|
|
32
|
+
decode garbage. FreeToken's `docker/Dockerfile.freetoken-l3` carries the same
|
|
33
|
+
pin as `AUTUMN_IMAGE_PIN` for exactly this reason.
|
|
34
|
+
|
|
35
|
+
The PyO3 extension (`import autumn`) is the `autumn-rs` distribution on PyPI:
|
|
36
|
+
one manylinux abi3 wheel per architecture for every CPython >= 3.11. Its
|
|
37
|
+
version must match the cluster's autumn-rs release; for a cluster built from
|
|
38
|
+
another commit, build the extension from that commit (below).
|
|
39
|
+
|
|
40
|
+
There is **no daemon and no local DRAM cache** — the autumn partition layer is
|
|
41
|
+
the only persistence path. KV pages are content-addressed
|
|
42
|
+
(`kvc/{h}/{model_scope}/{pool}/{hash}/{layer}`, `h` = the hash's first hex
|
|
43
|
+
char), so there is no invalidation. The cluster serves them once the `kvc`
|
|
44
|
+
namespace exists: `autumn-op namespace-create --name kvc --parts 16` registers
|
|
45
|
+
it and cuts it at `kvc/<hex>` (autumn-rs `cluster.sh` does this on its first
|
|
46
|
+
start). `h` comes first so those 16 partitions take every model's pages with
|
|
47
|
+
no per-model step.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install autumn-kvcache # pulls autumn-rs, the native `autumn` client
|
|
53
|
+
pip install "git+https://github.com/journeymidnight/autumn_kvcache@<rev>" # or from git
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The native client from source, for a cluster at another commit or for RDMA
|
|
57
|
+
(an autumn-rs checkout at that commit):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd autumn-rs/python
|
|
61
|
+
maturin build --release --interpreter /path/to/venv/bin/python
|
|
62
|
+
# for UCX: add `--features ucx --auditwheel skip`; the wheel must link the
|
|
63
|
+
# system UCX, since auditwheel's bundled copy has no mlx5 transport modules
|
|
64
|
+
# and the client then cannot connect over RDMA
|
|
65
|
+
/path/to/venv/bin/pip install --force-reinstall target/wheels/autumn_rs-*.whl
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Both adapter modules import **without** the engine (sglang / vLLM) installed —
|
|
69
|
+
the engine import is defensive — so `python -c "from
|
|
70
|
+
autumn_kvcache.vllm_connector import AutumnKVConnector"` works for tooling and
|
|
71
|
+
the data-plane smoke test regardless.
|
|
72
|
+
|
|
73
|
+
## Faster reads: the partition server's memory tier
|
|
74
|
+
|
|
75
|
+
Start the partition servers with `--mem-tier-bytes <budget> --mem-tier-prefix kvc/`
|
|
76
|
+
and KV pages are held in their DRAM and acknowledged without a log write; reads
|
|
77
|
+
come from memory. Pages held there are lost on a PS restart (a cache miss, then
|
|
78
|
+
recompute). Measured with SGLang + Qwen3-VL-32B: per-request p50 1344 → 980 ms,
|
|
79
|
+
2 GiB fetch 999 → 699 ms. See autumn-rs `docs/ops.md` ("Memory tier for KV cache").
|
|
80
|
+
|
|
81
|
+
The tier evicts least recently used first, and among pages used at about the
|
|
82
|
+
same time the deeper ones first, so a prefix loses its tail before its head
|
|
83
|
+
(a hit is a run of pages from the start). The adapters pass each page's
|
|
84
|
+
position in its prefix with the write (`evict_rank`, autumn wire 87): vLLM the
|
|
85
|
+
prefix's length; sglang `(pages before the batch + page index) × page_size`.
|
|
86
|
+
sglang tells the backend the pages before a batch only with
|
|
87
|
+
`"hicache_storage_pass_prefix_keys": true` in
|
|
88
|
+
`--hicache-storage-backend-extra-config`; set it. Without it every batch ranks
|
|
89
|
+
from 0, and even with it a node written from the root over several 128-page
|
|
90
|
+
batches gets an empty list after the first (sglang only extends a non-empty
|
|
91
|
+
one): those batches rank from 0 too, which orders them by write time alone.
|
|
92
|
+
|
|
93
|
+
The tier can evict a prefix's layers while its vLLM `__present__` marker stays.
|
|
94
|
+
A load that then finds a layer missing injects nothing, and the connector
|
|
95
|
+
deletes that marker (the worker its own rank's, the scheduler the one it
|
|
96
|
+
probes), so the next request for the prefix saves it again. Run vLLM with
|
|
97
|
+
`kv_load_failure_policy="recompute"`: under the default `"fail"` the request
|
|
98
|
+
whose load failed ends with an error instead of being recomputed.
|
|
99
|
+
|
|
100
|
+
## Using autumn-kvcache as a vLLM L3 (cross-instance prefix cache)
|
|
101
|
+
|
|
102
|
+
`AutumnKVConnector` is a native vLLM V1 KV connector (Phase 3a — CPU-offload
|
|
103
|
+
path). It offloads each request's block-aligned prompt-prefix KV into autumn on
|
|
104
|
+
the producing instance, and loads it back on **any** instance that later sees
|
|
105
|
+
the same prefix — a cross-instance / cross-restart prefix cache backed by the
|
|
106
|
+
partition layer, with no extra service to run.
|
|
107
|
+
|
|
108
|
+
> **Pinned vLLM:** verified against **vLLM 0.23.0** (torch 2.11). The
|
|
109
|
+
> `KVConnectorBase_V1` signatures drift across vLLM releases (e.g. the external
|
|
110
|
+
> connector constructor gained a 3rd `kv_cache_config` argument); the adapter
|
|
111
|
+
> forwards it when present and falls back to the 2-arg form, but pin a known
|
|
112
|
+
> version in production.
|
|
113
|
+
|
|
114
|
+
Point both engines at a running autumn cluster's **manager** endpoint and give
|
|
115
|
+
each `vllm serve` the connector config:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
KVCFG='{
|
|
119
|
+
"kv_connector":"AutumnKVConnector",
|
|
120
|
+
"kv_connector_module_path":"autumn_kvcache.vllm_connector",
|
|
121
|
+
"kv_role":"kv_both",
|
|
122
|
+
"kv_connector_extra_config":{"endpoint":"127.0.0.1:9001","transport":"tcp"}
|
|
123
|
+
}'
|
|
124
|
+
|
|
125
|
+
# instance 1 (e.g. GPU 0)
|
|
126
|
+
CUDA_VISIBLE_DEVICES=0 vllm serve /models/Qwen3-8B --served-model-name qwen3 \
|
|
127
|
+
--port 8101 --enforce-eager --kv-transfer-config "$KVCFG"
|
|
128
|
+
|
|
129
|
+
# instance 2 (e.g. GPU 1) — a SEPARATE engine, empty local prefix cache
|
|
130
|
+
CUDA_VISIBLE_DEVICES=1 vllm serve /models/Qwen3-8B --served-model-name qwen3 \
|
|
131
|
+
--port 8102 --enforce-eager --kv-transfer-config "$KVCFG"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`kv_connector_extra_config` keys: `endpoint` (autumn manager `host:port`,
|
|
135
|
+
required), `transport` (`tcp` default, or `ucx`), `max_inflight`, `ttl_secs`,
|
|
136
|
+
`model_id` (optional explicit model identity — see below). The client reads
|
|
137
|
+
with one Rust worker: ~5 GB/s per rank on TCP, ~42 GB/s on UCX (rendezvous
|
|
138
|
+
into the page); `client_workers` is no longer read.
|
|
139
|
+
|
|
140
|
+
### What the external cache does (and does not) speed up
|
|
141
|
+
|
|
142
|
+
The connector is an **L3** behind vLLM's own local prefix cache (GPU + host
|
|
143
|
+
RAM). vLLM matches the local cache first and asks the connector only for the
|
|
144
|
+
tokens *beyond* the local match, because remote storage is slower than a local
|
|
145
|
+
hit — you would never want to prefer autumn over GPU cache. Concretely:
|
|
146
|
+
|
|
147
|
+
- **Same engine, repeated prompt** → the local prefix cache serves it, so the
|
|
148
|
+
external cache is (correctly) never *loaded* and its hit rate is ~0%. This is
|
|
149
|
+
expected, not a failure.
|
|
150
|
+
- **A *different* engine / a *restarted* engine / after local eviction** → the
|
|
151
|
+
local cache is cold, so the connector loads the prefix from autumn and skips
|
|
152
|
+
prefill. This is the whole point (measured: a 1.3 k-token prefix loads in
|
|
153
|
+
~0.3 s vs. ~2 s of recompute — a ~3–4× TTFT win on the cold-local path).
|
|
154
|
+
|
|
155
|
+
So judge the connector by **cross-instance / post-restart** hit rate, not by
|
|
156
|
+
same-instance repeats. It also **never re-saves a prefix that is already durable
|
|
157
|
+
in autumn** (BUG-KVC-NO-HIT): a repeat whose KV the local cache already served
|
|
158
|
+
writes nothing, so it costs neither storage nor prefill time. (It issues one
|
|
159
|
+
lightweight presence probe per new request to make that de-dup decision.)
|
|
160
|
+
|
|
161
|
+
### Saves are asynchronous (almost nothing on the prefill critical path)
|
|
162
|
+
|
|
163
|
+
On the forward pass, `save_kv_layer` does **only** the cheap GPU-side gather
|
|
164
|
+
(`_extract_layer` → a *standalone* tensor, decoupled from the paged blocks — no
|
|
165
|
+
CPU sync). Everything expensive runs on a background thread: the D2H `.cpu()`
|
|
166
|
+
copy, the store-dedup probe, the durable batched `put_from`, and the marker. A
|
|
167
|
+
CUDA event lets the background thread wait for the gathers before the D2H, and
|
|
168
|
+
the `__present__` marker is published only *after* every layer ACKs (so a reader
|
|
169
|
+
that sees the marker always finds a complete, correct prefix — verified: an
|
|
170
|
+
external-load reproduces vLLM's local-cache output token-for-token).
|
|
171
|
+
|
|
172
|
+
Measured no-hit overhead (Qwen3-VL-32B, 1 k-token prompt, H200): **TTFT +≈6 ms,
|
|
173
|
+
TPOT ≈0** vs. no connector — i.e. a prefill that never gets reused costs almost
|
|
174
|
+
nothing. (Before moving the D2H off the path it was +≈148 ms.) When the prefix
|
|
175
|
+
*is* reused cross-instance, the connector skips the whole prefill instead.
|
|
176
|
+
|
|
177
|
+
A prompt longer than `max_num_batched_tokens` is prefilled in chunks over
|
|
178
|
+
several steps. Each step gathers only the tokens it computed (so the GPU staging
|
|
179
|
+
stays within vLLM's token budget), the background thread copies them into the
|
|
180
|
+
prefix's host buffer, and the prefix is written once its last chunk is in. A
|
|
181
|
+
layer's value larger than the partition server's 64 MiB cap (Qwen3-VL-4B: a
|
|
182
|
+
prefix over 16K tokens) is stored as equal pieces `{layer}/{i}`.
|
|
183
|
+
|
|
184
|
+
At most `_MAX_INFLIGHT_SAVES` saves are in flight, from a prefix's first chunk to
|
|
185
|
+
its write; each holds a registered host buffer the size of its prefix's KV, and
|
|
186
|
+
the buffers grow to the longest prefix seen (at most 2 × `max_model_len` ×
|
|
187
|
+
per-token KV). Over that a save is dropped rather than blocking prefill — it is
|
|
188
|
+
a pure cache, so a later request re-saves. The **store-dedup also lives in the
|
|
189
|
+
background**: a prefix already durable in autumn is neither re-copied nor
|
|
190
|
+
re-written, so a repeated prompt (served by the local GPU cache) costs no storage
|
|
191
|
+
and no extra latency.
|
|
192
|
+
|
|
193
|
+
### Model-scope isolation: the model's real identity is part of the key
|
|
194
|
+
|
|
195
|
+
The model-scope segment of every key is
|
|
196
|
+
`{model}_{fingerprint}_{tp_rank}_{tp_size}[...]`, where `fingerprint` is a
|
|
197
|
+
short hash of the model's **identity**: architecture shape
|
|
198
|
+
(layers / hidden / kv-heads / head-size / vocab / dtype / quantization / MLA)
|
|
199
|
+
plus the weights source (`load_format`, and for `--load-format autumn` the
|
|
200
|
+
autumn weights `path` from `model_loader_extra_config`), plus `model_id` if
|
|
201
|
+
you set one, plus the **layout versions** — the running **vLLM version**
|
|
202
|
+
(full `x.y.z`: the KV page layout is a vLLM-internal detail with no stability
|
|
203
|
+
contract, patch releases included) and the connector's own storage-format
|
|
204
|
+
version (`VLLM_KV_STORAGE_FORMAT`, bumped whenever the extract/inject byte
|
|
205
|
+
layout changes).
|
|
206
|
+
|
|
207
|
+
This exists because the model *path* is not an identity: with
|
|
208
|
+
a shared config dir, every model is served from the same fixed local config
|
|
209
|
+
dir, so two different models used to share one model scope and cross-read each
|
|
210
|
+
other's KV (observed live: Qwen2.5-7B and 32B under one model scope; a same-shape
|
|
211
|
+
pair would have been silently wrong). Set
|
|
212
|
+
`kv_connector_extra_config["model_id"]` when even the fingerprint can't
|
|
213
|
+
distinguish your deployments — e.g. two finetunes with identical architecture
|
|
214
|
+
loaded from the **same** path, or weights overwritten in place at one autumn
|
|
215
|
+
path (don't do that — store new weights at a new path).
|
|
216
|
+
|
|
217
|
+
> **Upgrade note:** any change to the fingerprint inputs — **including every
|
|
218
|
+
> vLLM version change, patch releases too** — moves the model scope, so the whole
|
|
219
|
+
> vLLM pool goes cold and rebuilds on first use (pure cache, content-addressed
|
|
220
|
+
> — no migration needed). This is deliberate: an upgrade already restarts the
|
|
221
|
+
> pods (GPU cache is lost anyway), so the re-warm is a one-time, predictable
|
|
222
|
+
> cost, whereas carrying KV across a layout-incompatible vLLM would be silent
|
|
223
|
+
> garbage. Keys written by older connectors stay behind under the old model scope;
|
|
224
|
+
> with `ttl_secs=0` they never expire, so reclaim them manually if you care
|
|
225
|
+
> about the space: `client.batch_delete(f"kvc/{h}/<old-model-scope>/vllm/".encode())`
|
|
226
|
+
> for each `h` in `0123456789abcdef` (keys from before the hash-bucket layout:
|
|
227
|
+
> the single prefix `kvc/<old-model-scope>/`; the old model scope is in the
|
|
228
|
+
> previous deployment's startup log).
|
|
229
|
+
|
|
230
|
+
**`ttl_secs`** (default `0` = no expiry) is the relative TTL after which an
|
|
231
|
+
offloaded prefix stops being *served*. Content-addressed keys never invalidate,
|
|
232
|
+
so a TTL is the only reclamation knob for a long-running cluster. Two caveats on
|
|
233
|
+
what exactly it bounds:
|
|
234
|
+
|
|
235
|
+
- The connector writes the per-prefix completion marker with `ttl_secs` and the
|
|
236
|
+
layer pages with `ttl_secs + grace` (grace = 300 s), so the marker always
|
|
237
|
+
expires *before* its layers. The scheduler admits a load on the marker; a
|
|
238
|
+
marker that outlived its layers would let the worker silently load
|
|
239
|
+
uninitialised KV (a correctness bug). So `ttl_secs` bounds **admissibility**
|
|
240
|
+
(when the prefix can still be hit); the layer **data** lingers ~`grace`
|
|
241
|
+
longer before its own lazy expiry.
|
|
242
|
+
- Reclamation is **lazy** (keys are dropped on read after they expire, plus
|
|
243
|
+
background compaction), not a hard wall-clock free — size capacity planning
|
|
244
|
+
accordingly.
|
|
245
|
+
|
|
246
|
+
### Verifying the cross-instance hit
|
|
247
|
+
|
|
248
|
+
Send a long prompt (longer than the cache block size) to **instance 1**, then
|
|
249
|
+
the **same** prompt to **instance 2**. Instance 2 has never seen it locally, so
|
|
250
|
+
any external-cache hit is served from autumn:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
curl -s localhost:8101/v1/completions -H 'Content-Type: application/json' \
|
|
254
|
+
-d '{"model":"qwen3","prompt":"<long prompt>","max_tokens":1}' >/dev/null
|
|
255
|
+
curl -s localhost:8102/v1/completions -H 'Content-Type: application/json' \
|
|
256
|
+
-d '{"model":"qwen3","prompt":"<same long prompt>","max_tokens":1}' >/dev/null
|
|
257
|
+
|
|
258
|
+
# instance 2's connector metrics now show the cross-instance hit:
|
|
259
|
+
curl -s localhost:8102/metrics | grep -E \
|
|
260
|
+
'vllm:external_prefix_cache_hits_total|external_kv_transfer'
|
|
261
|
+
# vllm:external_prefix_cache_hits_total{...} 272.0
|
|
262
|
+
# vllm:prompt_tokens_by_source_total{source="external_kv_transfer"} 272.0
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
(vLLM 0.23's OpenAI `usage` does **not** surface `cached_tokens` for the
|
|
266
|
+
connector path — use the `vllm:external_prefix_cache_*` /
|
|
267
|
+
`prompt_tokens_by_source{source="external_kv_transfer"}` metrics, which are the
|
|
268
|
+
authoritative external-hit signal.)
|
|
269
|
+
|
|
270
|
+
A scripted version of this check is `tests/` + the e2e in the repo.
|
|
271
|
+
|
|
272
|
+
## Data-plane smoke test (no engine required)
|
|
273
|
+
|
|
274
|
+
Exercises the autumn-facing core (`_AutumnKVStore`) against a real 1-node
|
|
275
|
+
cluster — key format, byte-exact page store/load round-trip, existence probe:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
AUTUMN_KVCACHE_ENDPOINT=127.0.0.1:9001 AUTUMN_KVCACHE_TRANSPORT=tcp \
|
|
279
|
+
python -m tests.test_vllm_dataplane # run from this repo root
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Architecture notes
|
|
283
|
+
|
|
284
|
+
- **Stateless adapter / content-addressed keys** — no invalidation, partition
|
|
285
|
+
is the only persistence path.
|
|
286
|
+
- **TTL reclamation (`ttl_secs`)** — both adapters accept `ttl_secs` in their
|
|
287
|
+
extra-config (vLLM `kv_connector_extra_config`, sglang
|
|
288
|
+
`hicache-storage-backend-extra-config`); default `0` = no expiry. The
|
|
289
|
+
partition layer expires keys lazily on read. The sglang backend just stamps
|
|
290
|
+
every page (its own L1/L2 + hash manage existence, so an expired L3 key is a
|
|
291
|
+
clean miss); the vLLM connector additionally keeps the completion marker's TTL
|
|
292
|
+
shorter than its layers' (see above) to preserve load correctness.
|
|
293
|
+
- **sglang host-pool layouts** — a page is read into, and written from, the
|
|
294
|
+
host pool itself. Where the page (MLA `page_first*`) or each of its K/V
|
|
295
|
+
halves (MHA `page_first*`) is a window into the pool, it moves with no copy;
|
|
296
|
+
otherwise (`layer_first`, the default, and pools that assemble a page, such
|
|
297
|
+
as Mamba) it is copied out to write, and a read lands in a scratch buffer
|
|
298
|
+
that `set_from_flat_data_page` puts into the pool. Stored bytes follow the
|
|
299
|
+
pool's own order, so every layout but `layer_first` carries its name in the
|
|
300
|
+
key: instances with different layouts never read each other's pages. For the
|
|
301
|
+
copy-free path, run sglang with `--hicache-mem-layout page_first_direct`; the
|
|
302
|
+
copy path keeps a scratch buffer the size of one batch (128 pages) on each of
|
|
303
|
+
sglang's backup and prefetch threads.
|
|
304
|
+
These layouts need the v1 interface (`"interface_v1": 1`); the v0 wrappers
|
|
305
|
+
use the untagged key.
|
|
306
|
+
- **No duplicate writes** — a page already stored (all of its values present)
|
|
307
|
+
is not written again; one existence probe per batch. Its `ttl_secs`
|
|
308
|
+
therefore counts from the first write: backing a hot page up again does not
|
|
309
|
+
extend it.
|
|
310
|
+
- **Return fast** — never block past the engine's step budget; the load path is
|
|
311
|
+
synchronous in Phase 3a (per-layer overlap is Phase 3b).
|
|
312
|
+
- **Model-scope isolation** — keys carry a model scope derived from model +
|
|
313
|
+
model-identity fingerprint (`_identity.py`) + TP/PP, so different models /
|
|
314
|
+
parallel layouts never alias. The sglang model-scope format is unchanged
|
|
315
|
+
(`model_name` is a real identity there); its escape hatch is
|
|
316
|
+
`extra_config["model_id"]`.
|
|
317
|
+
|
|
318
|
+
## Offline unit tests (no cluster, no engine, no native module)
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
uv run --with pytest python -m pytest tests/test_model_identity.py -q # from this repo root
|
|
322
|
+
python -m pytest tests/test_page_layouts.py -q # needs torch; every sglang host-pool layout
|
|
323
|
+
```
|