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.
Files changed (59) hide show
  1. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/PKG-INFO +1 -1
  2. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/__init__.py +2 -2
  3. aither_kvcache-2.3.0/aither_kvcache/persistence.py +253 -0
  4. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/backend.py +791 -115
  5. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/engine.py +122 -19
  6. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/hooks.py +5 -5
  7. aither_kvcache-2.3.0/aither_kvcache/vllm/plugin.py +159 -0
  8. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/PKG-INFO +1 -1
  9. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/SOURCES.txt +4 -7
  10. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/pyproject.toml +1 -1
  11. aither_kvcache-2.3.0/tests/test_block_selector.py +69 -0
  12. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_core.py +3 -3
  13. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_fused.py +3 -3
  14. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_hybrid_hooks.py +3 -3
  15. aither_kvcache-2.3.0/tests/test_persistence.py +158 -0
  16. aither_kvcache-2.3.0/tests/test_vllm_plugin.py +76 -0
  17. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/__init__.py +0 -3
  18. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/bench.py +1 -1
  19. aither_kvcache-2.2.0/aither_kvcache/bench.py +0 -140
  20. aither_kvcache-2.2.0/aither_kvcache/codebook.py +0 -166
  21. aither_kvcache-2.2.0/aither_kvcache/fused_attention.py +0 -748
  22. aither_kvcache-2.2.0/aither_kvcache/quantizer.py +0 -370
  23. aither_kvcache-2.2.0/aither_kvcache/vllm/plugin.py +0 -71
  24. aither_kvcache-2.2.0/turboquant/packing.py +0 -131
  25. aither_kvcache-2.2.0/turboquant/rotation.py +0 -108
  26. aither_kvcache-2.2.0/turboquant/triton_ops.py +0 -259
  27. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/LICENSE +0 -0
  28. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/README.md +0 -0
  29. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/block_selector.py +0 -0
  30. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/eviction_advisor.py +0 -0
  31. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/fused_kv_update.py +0 -0
  32. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/hybrid_quantizer.py +0 -0
  33. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/kvcache_graph.py +0 -0
  34. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/__init__.py +0 -0
  35. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/attention.py +0 -0
  36. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/cache.py +0 -0
  37. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/calibration.py +0 -0
  38. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/config.py +0 -0
  39. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/encoder.py +0 -0
  40. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/scorer.py +0 -0
  41. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/triattention/spectral.py +0 -0
  42. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/__init__.py +0 -0
  43. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/cache.py +0 -0
  44. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/eviction_plugin.py +0 -0
  45. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/sitecustomize.py +0 -0
  46. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache/vllm/tenant_isolation.py +0 -0
  47. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/dependency_links.txt +0 -0
  48. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/entry_points.txt +0 -0
  49. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/requires.txt +0 -0
  50. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/aither_kvcache.egg-info/top_level.txt +0 -0
  51. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/setup.cfg +0 -0
  52. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_kvcache_graph.py +0 -0
  53. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/tests/test_triattention.py +0 -0
  54. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/codebook.py +0 -0
  55. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/fused_attention.py +0 -0
  56. {aither_kvcache-2.2.0/aither_kvcache → aither_kvcache-2.3.0/turboquant}/packing.py +0 -0
  57. {aither_kvcache-2.2.0 → aither_kvcache-2.3.0}/turboquant/quantizer.py +0 -0
  58. {aither_kvcache-2.2.0/aither_kvcache → aither_kvcache-2.3.0/turboquant}/rotation.py +0 -0
  59. {aither_kvcache-2.2.0/aither_kvcache → aither_kvcache-2.3.0/turboquant}/triton_ops.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aither-kvcache
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Near-optimal KV cache compression for LLM inference — TurboQuant (vector quantization) + TriAttention (spectral compression)
5
5
  Author: Aitherium
6
6
  License-Expression: CC-BY-4.0
@@ -40,7 +40,7 @@ Usage:
40
40
  candidates = advisor.get_eviction_candidates(n=16)
41
41
  """
42
42
 
43
- from .quantizer import TurboQuant, TurboQuantConfig
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.1"
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