aither-kvcache 2.2.0__tar.gz → 2.3.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.
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/PKG-INFO +1 -1
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/__init__.py +2 -2
- aither_kvcache-2.3.0/aither_kvcache/persistence.py +253 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/backend.py +791 -115
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/engine.py +122 -19
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/hooks.py +5 -5
- aither_kvcache-2.3.0/aither_kvcache/vllm/plugin.py +159 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/PKG-INFO +1 -1
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/SOURCES.txt +4 -7
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/pyproject.toml +1 -1
- aither_kvcache-2.3.0/tests/test_block_selector.py +69 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_core.py +3 -3
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_fused.py +3 -3
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_hybrid_hooks.py +3 -3
- aither_kvcache-2.3.0/tests/test_persistence.py +158 -0
- aither_kvcache-2.3.0/tests/test_vllm_plugin.py +76 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/__init__.py +0 -3
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/bench.py +1 -1
- aither_kvcache-2.2.0/aither_kvcache/bench.py +0 -140
- aither_kvcache-2.2.0/aither_kvcache/codebook.py +0 -166
- aither_kvcache-2.2.0/aither_kvcache/fused_attention.py +0 -748
- aither_kvcache-2.2.0/aither_kvcache/quantizer.py +0 -370
- aither_kvcache-2.2.0/aither_kvcache/vllm/plugin.py +0 -71
- aither_kvcache-2.2.0/turboquant/packing.py +0 -131
- aither_kvcache-2.2.0/turboquant/rotation.py +0 -108
- aither_kvcache-2.2.0/turboquant/triton_ops.py +0 -259
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/LICENSE +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/README.md +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/block_selector.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/eviction_advisor.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/fused_kv_update.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/hybrid_quantizer.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/kvcache_graph.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/__init__.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/attention.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/cache.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/calibration.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/config.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/encoder.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/scorer.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/spectral.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/__init__.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/cache.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/eviction_plugin.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/sitecustomize.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/tenant_isolation.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/dependency_links.txt +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/entry_points.txt +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/requires.txt +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/top_level.txt +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/setup.cfg +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_kvcache_graph.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_triattention.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/codebook.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/fused_attention.py +0 -0
- {aither_kvcache-2.2.0/aither_kvcache → aither_kvcache-2.3.0/turboquant}/packing.py +0 -0
- {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/quantizer.py +0 -0
- {aither_kvcache-2.2.0/aither_kvcache → aither_kvcache-2.3.0/turboquant}/rotation.py +0 -0
- {aither_kvcache-2.2.0/aither_kvcache → aither_kvcache-2.3.0/turboquant}/triton_ops.py +0 -0
|
@@ -40,7 +40,7 @@ Usage:
|
|
|
40
40
|
candidates = advisor.get_eviction_candidates(n=16)
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
-
from
|
|
43
|
+
from turboquant import TurboQuant, TurboQuantConfig
|
|
44
44
|
from .hybrid_quantizer import HybridTurboQuant, HybridLayout, GroupLayout
|
|
45
45
|
from .kvcache_graph import KVCacheGraph, KVBlockNode, KVEdge, EdgeType, get_kvcache_graph
|
|
46
46
|
from .eviction_advisor import GraphEvictionAdvisor, reorder_by_ranking
|
|
@@ -56,4 +56,4 @@ __all__ = [
|
|
|
56
56
|
# TriAttention — lazy-loaded via subpackage
|
|
57
57
|
# from aither_kvcache.triattention import TriAttention, TriAttentionConfig
|
|
58
58
|
]
|
|
59
|
-
__version__ = "2.0
|
|
59
|
+
__version__ = "2.1.0"
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TQKV Binary Persistence Format for TurboQuant-compressed KV cache blocks.
|
|
3
|
+
|
|
4
|
+
Enables persistent prefix caching, cross-process sharing via mmap, and
|
|
5
|
+
SSD cold tier integration.
|
|
6
|
+
|
|
7
|
+
Format (v1):
|
|
8
|
+
Header (32 bytes):
|
|
9
|
+
magic: 4 bytes b"TQKV"
|
|
10
|
+
version: 1 byte (1)
|
|
11
|
+
bits: 1 byte (2, 3, or 4)
|
|
12
|
+
head_dim: 2 bytes uint16 LE
|
|
13
|
+
num_heads: 2 bytes uint16 LE
|
|
14
|
+
num_layers: 2 bytes uint16 LE
|
|
15
|
+
block_size: 2 bytes uint16 LE
|
|
16
|
+
num_blocks: 4 bytes uint32 LE
|
|
17
|
+
flags: 1 byte (bit 0: has_correction, bits 1-7: reserved)
|
|
18
|
+
reserved: 13 bytes (zeros)
|
|
19
|
+
Block data (per block, repeated num_blocks times):
|
|
20
|
+
packed_k: [block_size, num_heads, packed_dim] uint8
|
|
21
|
+
k_norms: [block_size, num_heads] float32
|
|
22
|
+
packed_v: [block_size, num_heads, packed_dim] uint8
|
|
23
|
+
v_norms: [block_size, num_heads] float32
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import struct
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Optional, Tuple
|
|
29
|
+
|
|
30
|
+
import torch
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
from turboquant.packing import packed_size
|
|
34
|
+
|
|
35
|
+
MAGIC = b"TQKV"
|
|
36
|
+
FORMAT_VERSION = 1
|
|
37
|
+
HEADER_SIZE = 32
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _pack_header(
|
|
41
|
+
bits: int,
|
|
42
|
+
head_dim: int,
|
|
43
|
+
num_heads: int,
|
|
44
|
+
num_layers: int,
|
|
45
|
+
block_size: int,
|
|
46
|
+
num_blocks: int,
|
|
47
|
+
has_correction: bool = False,
|
|
48
|
+
) -> bytes:
|
|
49
|
+
flags = int(has_correction) & 0x01
|
|
50
|
+
header = struct.pack(
|
|
51
|
+
"<4sBBHHHHIB13s",
|
|
52
|
+
MAGIC,
|
|
53
|
+
FORMAT_VERSION,
|
|
54
|
+
bits,
|
|
55
|
+
head_dim,
|
|
56
|
+
num_heads,
|
|
57
|
+
num_layers,
|
|
58
|
+
block_size,
|
|
59
|
+
num_blocks,
|
|
60
|
+
flags,
|
|
61
|
+
b"\x00" * 13,
|
|
62
|
+
)
|
|
63
|
+
assert len(header) == HEADER_SIZE
|
|
64
|
+
return header
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _unpack_header(data: bytes) -> dict:
|
|
68
|
+
if len(data) < HEADER_SIZE:
|
|
69
|
+
raise ValueError(f"Header too short: {len(data)} < {HEADER_SIZE}")
|
|
70
|
+
magic = data[:4]
|
|
71
|
+
if magic != MAGIC:
|
|
72
|
+
raise ValueError(f"Invalid magic: {magic!r}, expected {MAGIC!r}")
|
|
73
|
+
(
|
|
74
|
+
_magic, version, bits, head_dim, num_heads, num_layers,
|
|
75
|
+
block_size, num_blocks, flags, _reserved,
|
|
76
|
+
) = struct.unpack("<4sBBHHHHIB13s", data[:HEADER_SIZE])
|
|
77
|
+
if version != FORMAT_VERSION:
|
|
78
|
+
raise ValueError(f"Unsupported format version: {version}")
|
|
79
|
+
return {
|
|
80
|
+
"version": version,
|
|
81
|
+
"bits": bits,
|
|
82
|
+
"head_dim": head_dim,
|
|
83
|
+
"num_heads": num_heads,
|
|
84
|
+
"num_layers": num_layers,
|
|
85
|
+
"block_size": block_size,
|
|
86
|
+
"num_blocks": num_blocks,
|
|
87
|
+
"has_correction": bool(flags & 0x01),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def save_tqkv(
|
|
92
|
+
path: str,
|
|
93
|
+
k_packed: torch.Tensor,
|
|
94
|
+
k_norms: torch.Tensor,
|
|
95
|
+
v_packed: torch.Tensor,
|
|
96
|
+
v_norms: torch.Tensor,
|
|
97
|
+
bits: int,
|
|
98
|
+
head_dim: int,
|
|
99
|
+
num_layers: int = 1,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Save TQ-compressed KV cache blocks to a .tqkv file.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
path: Output file path (recommend .tqkv extension).
|
|
106
|
+
k_packed: [num_blocks, block_size, num_heads, packed_dim] uint8
|
|
107
|
+
k_norms: [num_blocks, block_size, num_heads] float32
|
|
108
|
+
v_packed: same shape as k_packed
|
|
109
|
+
v_norms: same shape as k_norms
|
|
110
|
+
bits: quantization bit-width (2, 3, or 4)
|
|
111
|
+
head_dim: original head dimension
|
|
112
|
+
num_layers: number of layers (metadata only)
|
|
113
|
+
"""
|
|
114
|
+
num_blocks, block_size, num_heads = k_norms.shape
|
|
115
|
+
pd = packed_size(head_dim, bits)
|
|
116
|
+
assert k_packed.shape == (num_blocks, block_size, num_heads, pd)
|
|
117
|
+
assert v_packed.shape == k_packed.shape
|
|
118
|
+
assert v_norms.shape == k_norms.shape
|
|
119
|
+
|
|
120
|
+
header = _pack_header(
|
|
121
|
+
bits=bits,
|
|
122
|
+
head_dim=head_dim,
|
|
123
|
+
num_heads=num_heads,
|
|
124
|
+
num_layers=num_layers,
|
|
125
|
+
block_size=block_size,
|
|
126
|
+
num_blocks=num_blocks,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
k_packed_np = k_packed.cpu().numpy()
|
|
130
|
+
k_norms_np = k_norms.cpu().float().numpy()
|
|
131
|
+
v_packed_np = v_packed.cpu().numpy()
|
|
132
|
+
v_norms_np = v_norms.cpu().float().numpy()
|
|
133
|
+
|
|
134
|
+
with open(path, "wb") as f:
|
|
135
|
+
f.write(header)
|
|
136
|
+
for blk in range(num_blocks):
|
|
137
|
+
f.write(k_packed_np[blk].tobytes())
|
|
138
|
+
f.write(k_norms_np[blk].tobytes())
|
|
139
|
+
f.write(v_packed_np[blk].tobytes())
|
|
140
|
+
f.write(v_norms_np[blk].tobytes())
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def load_tqkv(
|
|
144
|
+
path: str,
|
|
145
|
+
device: str = "cpu",
|
|
146
|
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, dict]:
|
|
147
|
+
"""
|
|
148
|
+
Load TQ-compressed KV cache blocks from a .tqkv file.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
k_packed: [num_blocks, block_size, num_heads, packed_dim] uint8
|
|
152
|
+
k_norms: [num_blocks, block_size, num_heads] float32
|
|
153
|
+
v_packed: same shape as k_packed
|
|
154
|
+
v_norms: same shape as k_norms
|
|
155
|
+
meta: dict with format metadata (bits, head_dim, num_heads, etc.)
|
|
156
|
+
"""
|
|
157
|
+
data = Path(path).read_bytes()
|
|
158
|
+
meta = _unpack_header(data[:HEADER_SIZE])
|
|
159
|
+
|
|
160
|
+
bits = meta["bits"]
|
|
161
|
+
head_dim = meta["head_dim"]
|
|
162
|
+
num_heads = meta["num_heads"]
|
|
163
|
+
block_size = meta["block_size"]
|
|
164
|
+
num_blocks = meta["num_blocks"]
|
|
165
|
+
pd = packed_size(head_dim, bits)
|
|
166
|
+
|
|
167
|
+
packed_bytes = block_size * num_heads * pd
|
|
168
|
+
norms_bytes = block_size * num_heads * 4 # float32
|
|
169
|
+
block_data_size = 2 * (packed_bytes + norms_bytes) # K + V
|
|
170
|
+
|
|
171
|
+
expected = HEADER_SIZE + num_blocks * block_data_size
|
|
172
|
+
if len(data) < expected:
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"File too short: {len(data)} < {expected} "
|
|
175
|
+
f"(expected {num_blocks} blocks)"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
k_packed_list = []
|
|
179
|
+
k_norms_list = []
|
|
180
|
+
v_packed_list = []
|
|
181
|
+
v_norms_list = []
|
|
182
|
+
|
|
183
|
+
offset = HEADER_SIZE
|
|
184
|
+
for _ in range(num_blocks):
|
|
185
|
+
kp = np.frombuffer(data[offset:offset + packed_bytes], dtype=np.uint8)
|
|
186
|
+
kp = kp.reshape(block_size, num_heads, pd)
|
|
187
|
+
offset += packed_bytes
|
|
188
|
+
|
|
189
|
+
kn = np.frombuffer(data[offset:offset + norms_bytes], dtype=np.float32)
|
|
190
|
+
kn = kn.reshape(block_size, num_heads)
|
|
191
|
+
offset += norms_bytes
|
|
192
|
+
|
|
193
|
+
vp = np.frombuffer(data[offset:offset + packed_bytes], dtype=np.uint8)
|
|
194
|
+
vp = vp.reshape(block_size, num_heads, pd)
|
|
195
|
+
offset += packed_bytes
|
|
196
|
+
|
|
197
|
+
vn = np.frombuffer(data[offset:offset + norms_bytes], dtype=np.float32)
|
|
198
|
+
vn = vn.reshape(block_size, num_heads)
|
|
199
|
+
offset += norms_bytes
|
|
200
|
+
|
|
201
|
+
k_packed_list.append(kp)
|
|
202
|
+
k_norms_list.append(kn)
|
|
203
|
+
v_packed_list.append(vp)
|
|
204
|
+
v_norms_list.append(vn)
|
|
205
|
+
|
|
206
|
+
k_packed = torch.from_numpy(np.stack(k_packed_list)).to(device)
|
|
207
|
+
k_norms = torch.from_numpy(np.stack(k_norms_list)).to(device)
|
|
208
|
+
v_packed = torch.from_numpy(np.stack(v_packed_list)).to(device)
|
|
209
|
+
v_norms = torch.from_numpy(np.stack(v_norms_list)).to(device)
|
|
210
|
+
|
|
211
|
+
return k_packed, k_norms, v_packed, v_norms, meta
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def mmap_tqkv(
|
|
215
|
+
path: str,
|
|
216
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, dict]:
|
|
217
|
+
"""
|
|
218
|
+
Memory-map a .tqkv file for zero-copy cross-process sharing.
|
|
219
|
+
|
|
220
|
+
Returns numpy arrays backed by the mmap. Read-only.
|
|
221
|
+
"""
|
|
222
|
+
data = np.memmap(path, dtype=np.uint8, mode="r")
|
|
223
|
+
meta = _unpack_header(bytes(data[:HEADER_SIZE]))
|
|
224
|
+
|
|
225
|
+
bits = meta["bits"]
|
|
226
|
+
head_dim = meta["head_dim"]
|
|
227
|
+
num_heads = meta["num_heads"]
|
|
228
|
+
block_size = meta["block_size"]
|
|
229
|
+
num_blocks = meta["num_blocks"]
|
|
230
|
+
pd = packed_size(head_dim, bits)
|
|
231
|
+
|
|
232
|
+
packed_bytes = block_size * num_heads * pd
|
|
233
|
+
norms_bytes = block_size * num_heads * 4
|
|
234
|
+
block_data_size = 2 * (packed_bytes + norms_bytes)
|
|
235
|
+
|
|
236
|
+
body = data[HEADER_SIZE:HEADER_SIZE + num_blocks * block_data_size]
|
|
237
|
+
body = body.reshape(num_blocks, block_data_size)
|
|
238
|
+
|
|
239
|
+
# Split each block's data into K packed, K norms, V packed, V norms
|
|
240
|
+
off = 0
|
|
241
|
+
k_packed = body[:, off:off + packed_bytes].reshape(
|
|
242
|
+
num_blocks, block_size, num_heads, pd)
|
|
243
|
+
off += packed_bytes
|
|
244
|
+
k_norms = body[:, off:off + norms_bytes].view(np.float32).reshape(
|
|
245
|
+
num_blocks, block_size, num_heads)
|
|
246
|
+
off += norms_bytes
|
|
247
|
+
v_packed = body[:, off:off + packed_bytes].reshape(
|
|
248
|
+
num_blocks, block_size, num_heads, pd)
|
|
249
|
+
off += packed_bytes
|
|
250
|
+
v_norms = body[:, off:off + norms_bytes].view(np.float32).reshape(
|
|
251
|
+
num_blocks, block_size, num_heads)
|
|
252
|
+
|
|
253
|
+
return k_packed, k_norms, v_packed, v_norms, meta
|