aither-kvcache 2.1.0__tar.gz → 2.2.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.1.0 → aither_kvcache-2.2.0}/PKG-INFO +1 -1
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/plugin.py +20 -2
- aither_kvcache-2.2.0/aither_kvcache/vllm/tenant_isolation.py +364 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache.egg-info/PKG-INFO +1 -1
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache.egg-info/SOURCES.txt +1 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/pyproject.toml +1 -1
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/__init__.py +3 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/codebook.py +60 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/fused_attention.py +27 -18
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/quantizer.py +24 -1
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/LICENSE +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/README.md +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/__init__.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/bench.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/block_selector.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/codebook.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/eviction_advisor.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/fused_attention.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/fused_kv_update.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/hybrid_quantizer.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/kvcache_graph.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/packing.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/quantizer.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/rotation.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/__init__.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/attention.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/cache.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/calibration.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/config.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/encoder.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/scorer.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triattention/spectral.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/triton_ops.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/__init__.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/backend.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/cache.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/engine.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/eviction_plugin.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/hooks.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache/vllm/sitecustomize.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache.egg-info/dependency_links.txt +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache.egg-info/entry_points.txt +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache.egg-info/requires.txt +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/aither_kvcache.egg-info/top_level.txt +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/setup.cfg +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/tests/test_core.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/tests/test_fused.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/tests/test_hybrid_hooks.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/tests/test_kvcache_graph.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/tests/test_triattention.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/bench.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/packing.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/rotation.py +0 -0
- {aither_kvcache-2.1.0 → aither_kvcache-2.2.0}/turboquant/triton_ops.py +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
vLLM plugin entrypoint.
|
|
2
|
+
vLLM plugin entrypoint for the aither-kvcache wheel.
|
|
3
3
|
|
|
4
4
|
Registered via pyproject.toml entry_points under vllm.general_plugins.
|
|
5
5
|
vLLM loads this at startup in every process (API server + engine workers).
|
|
@@ -7,6 +7,11 @@ vLLM loads this at startup in every process (API server + engine workers).
|
|
|
7
7
|
Registers:
|
|
8
8
|
1. TurboQuant CUSTOM attention backend (--attention-backend CUSTOM)
|
|
9
9
|
2. Graph-aware eviction (replaces LRU with semantic scoring)
|
|
10
|
+
3. Per-request tenant isolation (default OFF; AITHER_REQUEST_TENANT_ISOLATION=1)
|
|
11
|
+
|
|
12
|
+
CANONICAL SOURCE. Synced by .github/workflows/sync-kvcache.yml into the public
|
|
13
|
+
Aitherium/aitherkvcache repo as aither_kvcache/vllm/plugin.py, alongside
|
|
14
|
+
tenant_isolation.py. Edit here, not in the public repo.
|
|
10
15
|
"""
|
|
11
16
|
|
|
12
17
|
import logging
|
|
@@ -24,8 +29,8 @@ def register():
|
|
|
24
29
|
# 1. Register TurboQuant attention backend
|
|
25
30
|
try:
|
|
26
31
|
from vllm.v1.attention.backends.registry import (
|
|
27
|
-
register_backend,
|
|
28
32
|
AttentionBackendEnum,
|
|
33
|
+
register_backend,
|
|
29
34
|
)
|
|
30
35
|
register_backend(
|
|
31
36
|
AttentionBackendEnum.CUSTOM,
|
|
@@ -51,3 +56,16 @@ def register():
|
|
|
51
56
|
except Exception as e:
|
|
52
57
|
print(f"[aither-kvcache] Eviction install failed: {e}",
|
|
53
58
|
file=sys.stderr, flush=True)
|
|
59
|
+
|
|
60
|
+
# 3. Per-request tenant isolation (default OFF; AITHER_REQUEST_TENANT_ISOLATION=1).
|
|
61
|
+
# Rides this plugin so it loads in every process (API server + EngineCore
|
|
62
|
+
# workers). Non-fatal: a failure leaves the process on the instance tenant.
|
|
63
|
+
if os.environ.get("AITHER_REQUEST_TENANT_ISOLATION") == "1":
|
|
64
|
+
try:
|
|
65
|
+
from .tenant_isolation import install_tenant_isolation
|
|
66
|
+
install_tenant_isolation()
|
|
67
|
+
except ImportError:
|
|
68
|
+
pass
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"[aither-kvcache] Tenant isolation install failed: {e}",
|
|
71
|
+
file=sys.stderr, flush=True)
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Per-request tenant isolation for the aither-kvcache vLLM plugin.
|
|
3
|
+
|
|
4
|
+
This is the SELF-CONTAINED, in-wheel port of AitherOS's per-request KV-cache
|
|
5
|
+
tenant isolation. It rides the ACTIVE path on the orchestrator: the aither-kvcache
|
|
6
|
+
`vllm.general_plugins` entrypoint (`plugin.register()`) loads in every vLLM
|
|
7
|
+
process (API server + EngineCore workers), so a hook installed here actually runs
|
|
8
|
+
— unlike the AitherOS `lib.gpu.turboquant` import hooks, which are not mounted
|
|
9
|
+
into the served image.
|
|
10
|
+
|
|
11
|
+
Three seams, all fail-closed and non-fatal (a wrong symbol degrades to the
|
|
12
|
+
instance tenant; it never crashes vLLM startup):
|
|
13
|
+
|
|
14
|
+
1. Serving layer (API-server process): read the gateway-signed OpenAI ``user``
|
|
15
|
+
field, verify its HMAC, and publish the tenant into a ContextVar.
|
|
16
|
+
2. Engine ``generate`` (API-server process): embed the verified tenant into the
|
|
17
|
+
real vLLM ``request_id`` (positional index 2 in vLLM 0.19.x), so it crosses
|
|
18
|
+
the frontend -> EngineCore process boundary verbatim.
|
|
19
|
+
3. Block manager (EngineCore worker): on ``KVCacheManager.allocate_slots`` the
|
|
20
|
+
``request.request_id`` carries the embedded tenant — tag the request's blocks
|
|
21
|
+
with it; untag on ``free``.
|
|
22
|
+
|
|
23
|
+
Self-contained by necessity: the wheel cannot import ``lib.gpu.turboquant``. The
|
|
24
|
+
crypto/transport helpers mirror ``request_tenant_registry.py`` byte-for-byte so
|
|
25
|
+
tokens the AitherOS gateway signs verify here.
|
|
26
|
+
|
|
27
|
+
Gate: AITHER_REQUEST_TENANT_ISOLATION=1 (default OFF). Key: AITHER_TENANT_SIGNING_KEY
|
|
28
|
+
(injected from AitherSecrets into the image env; empty => fail-closed).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import contextvars
|
|
34
|
+
import hashlib
|
|
35
|
+
import hmac
|
|
36
|
+
import logging
|
|
37
|
+
import os
|
|
38
|
+
import sys
|
|
39
|
+
import threading
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger("aither.kvcache.tenant")
|
|
43
|
+
|
|
44
|
+
_ENABLED = os.environ.get("AITHER_REQUEST_TENANT_ISOLATION", "0") == "1"
|
|
45
|
+
|
|
46
|
+
# Must match request_tenant_registry.py exactly (cross-repo token compatibility).
|
|
47
|
+
_TOKEN_SEP = "|"
|
|
48
|
+
_RID_PREFIX = "tqt1:"
|
|
49
|
+
_RID_SEP = "~"
|
|
50
|
+
|
|
51
|
+
_pending_tenant: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
52
|
+
"tq_pending_tenant", default=None
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# block_id -> tenant_slug. Populated at allocate_slots, cleared at free. Guarded.
|
|
56
|
+
_block_tenant: dict[int, str] = {}
|
|
57
|
+
_block_lock = threading.Lock()
|
|
58
|
+
|
|
59
|
+
_isolation_status: dict[str, bool] = {
|
|
60
|
+
"enabled": _ENABLED,
|
|
61
|
+
"serving_patched": False,
|
|
62
|
+
"engine_patched": False,
|
|
63
|
+
"block_manager_patched": False,
|
|
64
|
+
"key_present": False,
|
|
65
|
+
"verified": False,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# Token crypto + request_id transport (mirror of request_tenant_registry.py)
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
def _signing_key() -> bytes:
|
|
73
|
+
return os.environ.get("AITHER_TENANT_SIGNING_KEY", "").encode("utf-8")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def verify_tenant_token(
|
|
77
|
+
token: str | None, *, now: float | None = None
|
|
78
|
+
) -> str | None:
|
|
79
|
+
"""Return the tenant slug iff ``token`` has a valid, unexpired gateway signature."""
|
|
80
|
+
import time
|
|
81
|
+
key = _signing_key()
|
|
82
|
+
if not token or not key:
|
|
83
|
+
return None
|
|
84
|
+
parts = token.split(_TOKEN_SEP)
|
|
85
|
+
if len(parts) != 3:
|
|
86
|
+
return None
|
|
87
|
+
tenant, exp_str, sig = parts
|
|
88
|
+
if not tenant or not exp_str or not sig:
|
|
89
|
+
return None
|
|
90
|
+
try:
|
|
91
|
+
exp = int(exp_str)
|
|
92
|
+
except (TypeError, ValueError):
|
|
93
|
+
return None
|
|
94
|
+
expected = hmac.new(
|
|
95
|
+
key, f"{tenant}{_TOKEN_SEP}{exp}".encode(), hashlib.sha256
|
|
96
|
+
).hexdigest()
|
|
97
|
+
if not hmac.compare_digest(sig, expected):
|
|
98
|
+
logger.warning("Rejected tenant token with invalid signature")
|
|
99
|
+
return None
|
|
100
|
+
if exp < (now if now is not None else time.time()):
|
|
101
|
+
logger.warning("Rejected expired tenant token")
|
|
102
|
+
return None
|
|
103
|
+
return tenant
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def embed_tenant_in_request_id(request_id: str, tenant_slug: str) -> str:
|
|
107
|
+
"""Prefix a verified tenant onto a request_id (idempotent, boundary-safe)."""
|
|
108
|
+
if not request_id or not tenant_slug:
|
|
109
|
+
return request_id
|
|
110
|
+
rid = str(request_id)
|
|
111
|
+
if _RID_SEP in tenant_slug or rid.startswith(_RID_PREFIX):
|
|
112
|
+
return request_id
|
|
113
|
+
return f"{_RID_PREFIX}{tenant_slug}{_RID_SEP}{rid}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def extract_tenant_from_request_id(
|
|
117
|
+
request_id: str | None,
|
|
118
|
+
) -> tuple[str | None, str | None]:
|
|
119
|
+
"""Inverse of embed_tenant_in_request_id -> (tenant_or_None, original_id)."""
|
|
120
|
+
if not request_id:
|
|
121
|
+
return None, request_id
|
|
122
|
+
rid = str(request_id)
|
|
123
|
+
if not rid.startswith(_RID_PREFIX):
|
|
124
|
+
return None, request_id
|
|
125
|
+
tenant, sep, original = rid[len(_RID_PREFIX):].partition(_RID_SEP)
|
|
126
|
+
if not sep or not tenant:
|
|
127
|
+
return None, request_id
|
|
128
|
+
return tenant, original
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
# Block -> tenant map
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
def tag_blocks(tenant_slug: str, block_ids) -> None:
|
|
135
|
+
if not tenant_slug or not block_ids:
|
|
136
|
+
return
|
|
137
|
+
with _block_lock:
|
|
138
|
+
for bid in block_ids:
|
|
139
|
+
_block_tenant[int(bid)] = tenant_slug
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def untag_blocks(block_ids) -> None:
|
|
143
|
+
if not block_ids:
|
|
144
|
+
return
|
|
145
|
+
with _block_lock:
|
|
146
|
+
for bid in block_ids:
|
|
147
|
+
_block_tenant.pop(int(bid), None)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def block_tenant(block_id: int) -> str | None:
|
|
151
|
+
with _block_lock:
|
|
152
|
+
return _block_tenant.get(int(block_id))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def block_tenant_count() -> int:
|
|
156
|
+
with _block_lock:
|
|
157
|
+
return len(_block_tenant)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Status
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
def get_isolation_status() -> dict[str, bool]:
|
|
164
|
+
_isolation_status["enabled"] = _ENABLED
|
|
165
|
+
_isolation_status["key_present"] = bool(_signing_key())
|
|
166
|
+
_isolation_status["verified"] = (
|
|
167
|
+
_isolation_status["enabled"]
|
|
168
|
+
and _isolation_status["serving_patched"]
|
|
169
|
+
and _isolation_status["engine_patched"]
|
|
170
|
+
and _isolation_status["block_manager_patched"]
|
|
171
|
+
and _isolation_status["key_present"]
|
|
172
|
+
)
|
|
173
|
+
return dict(_isolation_status)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Seam 1: serving-layer wrap (verified tenant -> ContextVar)
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
def _wrap_serving_method(cls: Any, method_name: str) -> bool:
|
|
180
|
+
orig = getattr(cls, method_name, None)
|
|
181
|
+
if orig is None:
|
|
182
|
+
return False
|
|
183
|
+
if getattr(orig, "_tq_tenant_wrapped", False):
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
async def _tenant_serving_wrapper(self, request, *args, **kwargs):
|
|
187
|
+
token = None
|
|
188
|
+
try:
|
|
189
|
+
user = getattr(request, "user", None)
|
|
190
|
+
tenant = verify_tenant_token(user) if user else None
|
|
191
|
+
if tenant:
|
|
192
|
+
token = _pending_tenant.set(tenant)
|
|
193
|
+
except (AttributeError, TypeError, ValueError) as e:
|
|
194
|
+
logger.debug("tenant capture skipped: %s", e)
|
|
195
|
+
token = None
|
|
196
|
+
try:
|
|
197
|
+
return await orig(self, request, *args, **kwargs)
|
|
198
|
+
finally:
|
|
199
|
+
if token is not None:
|
|
200
|
+
_pending_tenant.reset(token)
|
|
201
|
+
|
|
202
|
+
_tenant_serving_wrapper._tq_tenant_wrapped = True
|
|
203
|
+
setattr(cls, method_name, _tenant_serving_wrapper)
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Seam 2: engine generate wrap (embed tenant into positional request_id)
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
# vLLM 0.19.x: AsyncLLM.generate(self, prompt, sampling_params, request_id, *, ...)
|
|
211
|
+
# request_id is the 3rd positional arg -> index 2 in the wrapper's *args.
|
|
212
|
+
_GENERATE_RID_POS = 2
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _wrap_engine_generate(cls: Any, rid_pos: int = _GENERATE_RID_POS) -> bool:
|
|
216
|
+
orig = getattr(cls, "generate", None)
|
|
217
|
+
if orig is None:
|
|
218
|
+
return False
|
|
219
|
+
if getattr(orig, "_tq_tenant_wrapped", False):
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
async def _tenant_generate_wrapper(self, *args, **kwargs):
|
|
223
|
+
tenant = _pending_tenant.get(None)
|
|
224
|
+
if tenant:
|
|
225
|
+
rid = None
|
|
226
|
+
pos = None
|
|
227
|
+
if isinstance(kwargs.get("request_id"), str) and kwargs["request_id"]:
|
|
228
|
+
rid = kwargs["request_id"]
|
|
229
|
+
elif (len(args) > rid_pos and isinstance(args[rid_pos], str)
|
|
230
|
+
and args[rid_pos]):
|
|
231
|
+
pos = rid_pos
|
|
232
|
+
rid = args[pos]
|
|
233
|
+
if rid:
|
|
234
|
+
embedded = embed_tenant_in_request_id(rid, tenant)
|
|
235
|
+
if pos is not None:
|
|
236
|
+
args = args[:pos] + (embedded,) + args[pos + 1:]
|
|
237
|
+
else:
|
|
238
|
+
kwargs = dict(kwargs)
|
|
239
|
+
kwargs["request_id"] = embedded
|
|
240
|
+
async for out in orig(self, *args, **kwargs):
|
|
241
|
+
yield out
|
|
242
|
+
|
|
243
|
+
_tenant_generate_wrapper._tq_tenant_wrapped = True
|
|
244
|
+
cls.generate = _tenant_generate_wrapper
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
# Seam 3: block manager wrap (tag blocks by tenant from request.request_id)
|
|
250
|
+
# ---------------------------------------------------------------------------
|
|
251
|
+
def _wrap_block_manager(cls: Any) -> bool:
|
|
252
|
+
alloc = getattr(cls, "allocate_slots", None)
|
|
253
|
+
free = getattr(cls, "free", None)
|
|
254
|
+
if alloc is None:
|
|
255
|
+
return False
|
|
256
|
+
if getattr(alloc, "_tq_tenant_wrapped", False):
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
def _flatten_block_ids(mgr, request_id) -> list:
|
|
260
|
+
try:
|
|
261
|
+
groups = mgr.get_block_ids(request_id)
|
|
262
|
+
except Exception:
|
|
263
|
+
return []
|
|
264
|
+
ids: list = []
|
|
265
|
+
for g in groups or ():
|
|
266
|
+
try:
|
|
267
|
+
ids.extend(int(b) for b in g)
|
|
268
|
+
except TypeError:
|
|
269
|
+
ids.append(int(g))
|
|
270
|
+
return ids
|
|
271
|
+
|
|
272
|
+
def _tenant_allocate_slots(self, request, *args, **kwargs):
|
|
273
|
+
result = alloc(self, request, *args, **kwargs)
|
|
274
|
+
if result is not None:
|
|
275
|
+
try:
|
|
276
|
+
rid = getattr(request, "request_id", None)
|
|
277
|
+
tenant, _orig = extract_tenant_from_request_id(rid)
|
|
278
|
+
if tenant:
|
|
279
|
+
tag_blocks(tenant, _flatten_block_ids(self, rid))
|
|
280
|
+
except Exception as e: # never break allocation for tagging
|
|
281
|
+
logger.debug("tenant block-tag skipped: %s", e)
|
|
282
|
+
return result
|
|
283
|
+
|
|
284
|
+
_tenant_allocate_slots._tq_tenant_wrapped = True
|
|
285
|
+
cls.allocate_slots = _tenant_allocate_slots
|
|
286
|
+
|
|
287
|
+
if free is not None and not getattr(free, "_tq_tenant_wrapped", False):
|
|
288
|
+
def _tenant_free(self, request, *args, **kwargs):
|
|
289
|
+
try:
|
|
290
|
+
rid = getattr(request, "request_id", None)
|
|
291
|
+
if rid:
|
|
292
|
+
untag_blocks(_flatten_block_ids(self, rid))
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.debug("tenant block-untag skipped: %s", e)
|
|
295
|
+
return free(self, request, *args, **kwargs)
|
|
296
|
+
|
|
297
|
+
_tenant_free._tq_tenant_wrapped = True
|
|
298
|
+
cls.free = _tenant_free
|
|
299
|
+
|
|
300
|
+
return True
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ---------------------------------------------------------------------------
|
|
304
|
+
# Install
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
def install_tenant_isolation() -> bool:
|
|
307
|
+
"""Install all three seams. Called from plugin.register() when enabled.
|
|
308
|
+
|
|
309
|
+
Returns True iff verified (serving + engine + block manager + key). Every
|
|
310
|
+
failure is non-fatal and leaves the process serving with the instance tenant.
|
|
311
|
+
"""
|
|
312
|
+
if not _ENABLED:
|
|
313
|
+
logger.debug("tenant isolation disabled (AITHER_REQUEST_TENANT_ISOLATION!=1)")
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
serving_targets = [
|
|
317
|
+
("vllm.entrypoints.openai.chat_completion.serving", "OpenAIServingChat",
|
|
318
|
+
"create_chat_completion"),
|
|
319
|
+
("vllm.entrypoints.openai.serving_chat", "OpenAIServingChat",
|
|
320
|
+
"create_chat_completion"),
|
|
321
|
+
("vllm.entrypoints.openai.completion.serving", "OpenAIServingCompletion",
|
|
322
|
+
"create_completion"),
|
|
323
|
+
("vllm.entrypoints.openai.serving_completion", "OpenAIServingCompletion",
|
|
324
|
+
"create_completion"),
|
|
325
|
+
]
|
|
326
|
+
seen = set()
|
|
327
|
+
for modpath, clsname, method in serving_targets:
|
|
328
|
+
try:
|
|
329
|
+
mod = __import__(modpath, fromlist=[clsname])
|
|
330
|
+
cls = getattr(mod, clsname)
|
|
331
|
+
if cls in seen:
|
|
332
|
+
continue
|
|
333
|
+
if _wrap_serving_method(cls, method):
|
|
334
|
+
seen.add(cls)
|
|
335
|
+
_isolation_status["serving_patched"] = True
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.debug("serving wrap skipped %s.%s: %s", clsname, method, e)
|
|
338
|
+
|
|
339
|
+
for modpath, clsname in [
|
|
340
|
+
("vllm.v1.engine.async_llm", "AsyncLLM"),
|
|
341
|
+
("vllm.engine.async_llm_engine", "AsyncLLMEngine"),
|
|
342
|
+
]:
|
|
343
|
+
try:
|
|
344
|
+
mod = __import__(modpath, fromlist=[clsname])
|
|
345
|
+
if _wrap_engine_generate(getattr(mod, clsname)):
|
|
346
|
+
_isolation_status["engine_patched"] = True
|
|
347
|
+
except Exception as e:
|
|
348
|
+
logger.debug("engine wrap skipped %s: %s", clsname, e)
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
from vllm.v1.core.kv_cache_manager import KVCacheManager
|
|
352
|
+
if _wrap_block_manager(KVCacheManager):
|
|
353
|
+
_isolation_status["block_manager_patched"] = True
|
|
354
|
+
except Exception as e:
|
|
355
|
+
logger.debug("block-manager wrap skipped: %s", e)
|
|
356
|
+
|
|
357
|
+
st = get_isolation_status()
|
|
358
|
+
print(
|
|
359
|
+
f"[aither-kvcache] tenant isolation: serving={st['serving_patched']} "
|
|
360
|
+
f"engine={st['engine_patched']} block_mgr={st['block_manager_patched']} "
|
|
361
|
+
f"key={st['key_present']} VERIFIED={st['verified']}",
|
|
362
|
+
file=sys.stderr, flush=True,
|
|
363
|
+
)
|
|
364
|
+
return st["verified"]
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aither-kvcache"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Near-optimal KV cache compression for LLM inference — TurboQuant (vector quantization) + TriAttention (spectral compression)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "CC-BY-4.0"
|
|
@@ -22,6 +22,7 @@ Usage:
|
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
from .quantizer import TurboQuant, TurboQuantConfig
|
|
25
|
+
from .gb10_fp8_rescue import get_gb10_fp8_status, install_gb10_fp8_rescue
|
|
25
26
|
|
|
26
27
|
__all__ = [
|
|
27
28
|
"TurboQuant",
|
|
@@ -34,6 +35,8 @@ __all__ = [
|
|
|
34
35
|
"get_tier_cache_bridge",
|
|
35
36
|
"get_strata_cache_shadow",
|
|
36
37
|
"get_graph_eviction_advisor",
|
|
38
|
+
"install_gb10_fp8_rescue",
|
|
39
|
+
"get_gb10_fp8_status",
|
|
37
40
|
]
|
|
38
41
|
__version__ = "0.9.0"
|
|
39
42
|
|
|
@@ -82,6 +82,66 @@ def get_codebook(d: int, bits: int) -> Tuple[np.ndarray, np.ndarray, float]:
|
|
|
82
82
|
return centroids, boundaries_inner, mse_total
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def get_correction_factors(d: int, bits: int) -> np.ndarray:
|
|
86
|
+
"""
|
|
87
|
+
Compute per-centroid inner-product bias correction factors.
|
|
88
|
+
|
|
89
|
+
For Lloyd-Max quantization on N(0, 1/d), the reconstructed inner product
|
|
90
|
+
<q, x_hat> is a biased estimator of <q, x>. The correction factor for
|
|
91
|
+
centroid i is: alpha_i = E[x | x in bin_i] / c_i.
|
|
92
|
+
|
|
93
|
+
For a perfect Lloyd-Max codebook this is ~1.0, but finite-precision
|
|
94
|
+
centroids and boundary discretization introduce small biases. Computing
|
|
95
|
+
these analytically and applying them removes the systematic IP bias.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
correction: array of shape [2^bits], values close to 1.0.
|
|
99
|
+
"""
|
|
100
|
+
if bits not in _STANDARD_CODEBOOKS:
|
|
101
|
+
raise ValueError(f"Supported bit-widths: {list(_STANDARD_CODEBOOKS.keys())}. Got {bits}")
|
|
102
|
+
|
|
103
|
+
entry = _STANDARD_CODEBOOKS[bits]
|
|
104
|
+
std_centroids = entry["centroids"]
|
|
105
|
+
num_levels = 1 << bits
|
|
106
|
+
sigma = 1.0 / math.sqrt(d)
|
|
107
|
+
|
|
108
|
+
# Standard N(0,1) boundaries
|
|
109
|
+
boundaries = np.empty(num_levels + 1)
|
|
110
|
+
boundaries[0] = -10.0 # effectively -inf
|
|
111
|
+
boundaries[-1] = 10.0 # effectively +inf
|
|
112
|
+
for i in range(num_levels - 1):
|
|
113
|
+
boundaries[i + 1] = (std_centroids[i] + std_centroids[i + 1]) / 2
|
|
114
|
+
|
|
115
|
+
# Compute E[x | x in bin_i] for N(0,1)
|
|
116
|
+
# E[x | b_lo <= x < b_hi] = (phi(b_lo) - phi(b_hi)) / (Phi(b_hi) - Phi(b_lo))
|
|
117
|
+
# where phi = pdf, Phi = cdf of N(0,1)
|
|
118
|
+
from math import exp as _exp, pi as _pi
|
|
119
|
+
|
|
120
|
+
def _phi(x):
|
|
121
|
+
return (1.0 / math.sqrt(2 * _pi)) * _exp(-0.5 * x * x)
|
|
122
|
+
|
|
123
|
+
def _Phi(x):
|
|
124
|
+
# Abramowitz & Stegun approximation (accurate to 1e-7)
|
|
125
|
+
t = 1.0 / (1.0 + 0.2316419 * abs(x))
|
|
126
|
+
poly = t * (0.319381530 + t * (-0.356563782 + t * (
|
|
127
|
+
1.781477937 + t * (-1.821255978 + t * 1.330274429))))
|
|
128
|
+
val = 1.0 - _phi(x) * poly
|
|
129
|
+
return val if x >= 0 else 1.0 - val
|
|
130
|
+
|
|
131
|
+
corrections = np.ones(num_levels, dtype=np.float64)
|
|
132
|
+
for i in range(num_levels):
|
|
133
|
+
b_lo = boundaries[i]
|
|
134
|
+
b_hi = boundaries[i + 1]
|
|
135
|
+
prob = _Phi(b_hi) - _Phi(b_lo)
|
|
136
|
+
if prob < 1e-15 or abs(std_centroids[i]) < 1e-15:
|
|
137
|
+
corrections[i] = 1.0
|
|
138
|
+
continue
|
|
139
|
+
cond_mean = (_phi(b_lo) - _phi(b_hi)) / prob
|
|
140
|
+
corrections[i] = cond_mean / std_centroids[i]
|
|
141
|
+
|
|
142
|
+
return corrections.astype(np.float32)
|
|
143
|
+
|
|
144
|
+
|
|
85
145
|
def get_theory_bounds(bits: int) -> Tuple[float, float]:
|
|
86
146
|
"""Return (lower_bound, upper_bound) on MSE for b-bit quantization."""
|
|
87
147
|
lower = 1.0 / (4 ** bits)
|
|
@@ -66,9 +66,11 @@ class TQPagedAttentionRef:
|
|
|
66
66
|
head_dim: int,
|
|
67
67
|
block_size: int,
|
|
68
68
|
bits: int = 4,
|
|
69
|
+
k_centroids: Optional[torch.Tensor] = None,
|
|
69
70
|
):
|
|
70
71
|
self.rotation = rotation # [head_dim, head_dim]
|
|
71
|
-
self.centroids = centroids # [num_levels]
|
|
72
|
+
self.centroids = centroids # [num_levels] — used for V accumulation
|
|
73
|
+
self.k_centroids = k_centroids if k_centroids is not None else centroids # for K scoring
|
|
72
74
|
self.scale = scale
|
|
73
75
|
self.num_kv_heads = num_kv_heads
|
|
74
76
|
self.num_query_heads = num_query_heads
|
|
@@ -81,12 +83,14 @@ class TQPagedAttentionRef:
|
|
|
81
83
|
from .packing import unpack_4bit, unpack_3bit, unpack_2bit
|
|
82
84
|
self._unpack_fns = {4: unpack_4bit, 3: unpack_3bit, 2: unpack_2bit}
|
|
83
85
|
|
|
84
|
-
def _unpack_to_full(self, packed_vec: torch.Tensor
|
|
86
|
+
def _unpack_to_full(self, packed_vec: torch.Tensor,
|
|
87
|
+
codebook: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
85
88
|
"""Unpack a single packed vector to full head_dim via codebook lookup."""
|
|
86
89
|
# packed_vec: [packed_dim] uint8
|
|
90
|
+
cb = codebook if codebook is not None else self.centroids
|
|
87
91
|
unpack_fn = self._unpack_fns.get(self.bits, self._unpack_fns[4])
|
|
88
92
|
indices = unpack_fn(packed_vec.unsqueeze(0), self.head_dim).squeeze(0)
|
|
89
|
-
return
|
|
93
|
+
return cb[indices.long()]
|
|
90
94
|
|
|
91
95
|
def forward(
|
|
92
96
|
self,
|
|
@@ -136,9 +140,9 @@ class TQPagedAttentionRef:
|
|
|
136
140
|
n_valid = end - start
|
|
137
141
|
|
|
138
142
|
for pos in range(n_valid):
|
|
139
|
-
# Unpack K → full rotated vector via codebook
|
|
143
|
+
# Unpack K → full rotated vector via corrected codebook
|
|
140
144
|
packed_k = k_packed[phys_block, pos, kv_head]
|
|
141
|
-
k_full = self._unpack_to_full(packed_k)
|
|
145
|
+
k_full = self._unpack_to_full(packed_k, self.k_centroids)
|
|
142
146
|
k_even = k_full[0::2]
|
|
143
147
|
k_odd = k_full[1::2]
|
|
144
148
|
|
|
@@ -214,8 +218,9 @@ if HAS_TRITON:
|
|
|
214
218
|
# Paged block indirection
|
|
215
219
|
block_tables_ptr, # [num_seqs, max_blocks_per_seq] int32/int64
|
|
216
220
|
context_lens_ptr, # [num_seqs] int32/int64
|
|
217
|
-
#
|
|
218
|
-
|
|
221
|
+
# Codebooks (separate for K scoring and V accumulation)
|
|
222
|
+
k_centroids_ptr, # [16] float32 — corrected centroids for K scoring
|
|
223
|
+
v_centroids_ptr, # [16] float32 — raw centroids for V accumulation
|
|
219
224
|
# Output accumulators (rotated domain, even/odd split)
|
|
220
225
|
out_even_ptr, # [num_seqs, num_q_heads, HALF_D] float32
|
|
221
226
|
out_odd_ptr, # [num_seqs, num_q_heads, HALF_D] float32
|
|
@@ -300,8 +305,8 @@ if HAS_TRITON:
|
|
|
300
305
|
k_packed_ptr + kp_base + half_offs
|
|
301
306
|
).to(tl.int32)
|
|
302
307
|
|
|
303
|
-
k_even = tl.load(
|
|
304
|
-
k_odd = tl.load(
|
|
308
|
+
k_even = tl.load(k_centroids_ptr + ((kp >> 4) & 0xF))
|
|
309
|
+
k_odd = tl.load(k_centroids_ptr + (kp & 0xF))
|
|
305
310
|
|
|
306
311
|
# ── Score ──
|
|
307
312
|
k_norm = tl.load(
|
|
@@ -332,8 +337,8 @@ if HAS_TRITON:
|
|
|
332
337
|
v_packed_ptr + vp_base + half_offs
|
|
333
338
|
).to(tl.int32)
|
|
334
339
|
|
|
335
|
-
v_even = tl.load(
|
|
336
|
-
v_odd = tl.load(
|
|
340
|
+
v_even = tl.load(v_centroids_ptr + ((vp >> 4) & 0xF))
|
|
341
|
+
v_odd = tl.load(v_centroids_ptr + (vp & 0xF))
|
|
337
342
|
v_norm = tl.load(
|
|
338
343
|
v_norms_ptr
|
|
339
344
|
+ phys_block * stride_kn_block
|
|
@@ -371,7 +376,7 @@ if HAS_TRITON:
|
|
|
371
376
|
k_packed_ptr, k_norms_ptr,
|
|
372
377
|
v_packed_ptr, v_norms_ptr,
|
|
373
378
|
block_tables_ptr, context_lens_ptr,
|
|
374
|
-
|
|
379
|
+
k_centroids_ptr, v_centroids_ptr,
|
|
375
380
|
# Partial outputs: [num_q_heads, num_seqs, NUM_SPLITS, HALF_D]
|
|
376
381
|
part_even_ptr, part_odd_ptr,
|
|
377
382
|
# Partial softmax state: [num_q_heads, num_seqs, NUM_SPLITS]
|
|
@@ -431,8 +436,8 @@ if HAS_TRITON:
|
|
|
431
436
|
k_packed_ptr + kp_base + half_offs
|
|
432
437
|
).to(tl.int32)
|
|
433
438
|
|
|
434
|
-
k_even = tl.load(
|
|
435
|
-
k_odd = tl.load(
|
|
439
|
+
k_even = tl.load(k_centroids_ptr + ((kp >> 4) & 0xF))
|
|
440
|
+
k_odd = tl.load(k_centroids_ptr + (kp & 0xF))
|
|
436
441
|
|
|
437
442
|
k_norm = tl.load(
|
|
438
443
|
k_norms_ptr + phys_block * stride_kn_block
|
|
@@ -457,8 +462,8 @@ if HAS_TRITON:
|
|
|
457
462
|
v_packed_ptr + vp_base + half_offs
|
|
458
463
|
).to(tl.int32)
|
|
459
464
|
|
|
460
|
-
v_even = tl.load(
|
|
461
|
-
v_odd = tl.load(
|
|
465
|
+
v_even = tl.load(v_centroids_ptr + ((vp >> 4) & 0xF))
|
|
466
|
+
v_odd = tl.load(v_centroids_ptr + (vp & 0xF))
|
|
462
467
|
v_norm = tl.load(
|
|
463
468
|
v_norms_ptr + phys_block * stride_kn_block
|
|
464
469
|
+ pos * stride_kn_pos + kv_head)
|
|
@@ -560,6 +565,9 @@ class TQPagedAttention:
|
|
|
560
565
|
self.rotation = tq.rotation
|
|
561
566
|
self.rotation_T = tq.rotation.T.contiguous() # pre-transposed, contiguous
|
|
562
567
|
self.centroids = tq.centroids
|
|
568
|
+
# Corrected centroids for unbiased inner product scoring (K path only).
|
|
569
|
+
# V accumulation uses raw centroids (correction only affects score bias).
|
|
570
|
+
self.k_centroids = tq.centroids * tq.correction_factors
|
|
563
571
|
self.scale = 1.0 / math.sqrt(tq.head_dim)
|
|
564
572
|
self.head_dim = tq.head_dim
|
|
565
573
|
self.num_levels = tq.num_levels
|
|
@@ -577,6 +585,7 @@ class TQPagedAttention:
|
|
|
577
585
|
head_dim=tq.head_dim,
|
|
578
586
|
block_size=16,
|
|
579
587
|
bits=tq.bits,
|
|
588
|
+
k_centroids=self.k_centroids,
|
|
580
589
|
)
|
|
581
590
|
|
|
582
591
|
# Block selector for sparse attention (optional)
|
|
@@ -697,7 +706,7 @@ class TQPagedAttention:
|
|
|
697
706
|
q_even, q_odd,
|
|
698
707
|
k_packed, k_norms, v_packed, v_norms,
|
|
699
708
|
block_tables, context_lens,
|
|
700
|
-
self.centroids,
|
|
709
|
+
self.k_centroids, self.centroids,
|
|
701
710
|
part_even, part_odd, part_m, part_l,
|
|
702
711
|
q_even.stride(0), q_even.stride(1),
|
|
703
712
|
k_packed.stride(0), k_packed.stride(1), k_packed.stride(2),
|
|
@@ -724,7 +733,7 @@ class TQPagedAttention:
|
|
|
724
733
|
q_even, q_odd,
|
|
725
734
|
k_packed, k_norms, v_packed, v_norms,
|
|
726
735
|
block_tables, context_lens,
|
|
727
|
-
self.centroids,
|
|
736
|
+
self.k_centroids, self.centroids,
|
|
728
737
|
out_even, out_odd,
|
|
729
738
|
q_even.stride(0), q_even.stride(1),
|
|
730
739
|
k_packed.stride(0), k_packed.stride(1), k_packed.stride(2),
|
|
@@ -23,7 +23,7 @@ import torch
|
|
|
23
23
|
from dataclasses import dataclass
|
|
24
24
|
from typing import Optional, Tuple
|
|
25
25
|
|
|
26
|
-
from .codebook import get_codebook, get_theory_bounds
|
|
26
|
+
from .codebook import get_codebook, get_theory_bounds, get_correction_factors
|
|
27
27
|
from .rotation import random_orthogonal, randomized_hadamard_matrix
|
|
28
28
|
from .packing import (
|
|
29
29
|
pack_4bit, unpack_4bit,
|
|
@@ -102,6 +102,12 @@ class TurboQuant:
|
|
|
102
102
|
# Theory bounds
|
|
103
103
|
self.theory_lower, self.theory_upper = get_theory_bounds(config.bits)
|
|
104
104
|
|
|
105
|
+
# Per-centroid correction factors for unbiased inner product estimation
|
|
106
|
+
correction_np = get_correction_factors(config.head_dim, config.bits)
|
|
107
|
+
self.correction_factors = torch.tensor(
|
|
108
|
+
correction_np, dtype=torch.float32, device=config.device,
|
|
109
|
+
)
|
|
110
|
+
|
|
105
111
|
# Pack/unpack dispatch
|
|
106
112
|
self._pack_fn = {2: pack_2bit, 3: pack_3bit, 4: pack_4bit}[config.bits]
|
|
107
113
|
self._unpack_fn = {2: unpack_2bit, 3: unpack_3bit, 4: unpack_4bit}[config.bits]
|
|
@@ -211,6 +217,12 @@ class TurboQuant:
|
|
|
211
217
|
indices = self._unpack_fn(packed, self.head_dim)
|
|
212
218
|
return self.centroids[indices.long()]
|
|
213
219
|
|
|
220
|
+
def _pytorch_decode_corrected(self, packed: torch.Tensor) -> torch.Tensor:
|
|
221
|
+
"""Unpack + codebook lookup with per-centroid IP bias correction."""
|
|
222
|
+
indices = self._unpack_fn(packed, self.head_dim)
|
|
223
|
+
idx_long = indices.long()
|
|
224
|
+
return self.centroids[idx_long] * self.correction_factors[idx_long]
|
|
225
|
+
|
|
214
226
|
# ================================================================
|
|
215
227
|
# INTERNAL: Triton path (GPU only)
|
|
216
228
|
# ================================================================
|
|
@@ -295,6 +307,16 @@ class TurboQuant:
|
|
|
295
307
|
ip_mse = (true_ip - est_ip).pow(2).mean().item()
|
|
296
308
|
ip_bias = (est_ip - true_ip).mean().item()
|
|
297
309
|
|
|
310
|
+
# Corrected decode: apply per-centroid bias correction factors
|
|
311
|
+
packed_flat = packed.reshape(-1, packed.shape[-1])
|
|
312
|
+
norms_flat = norms.reshape(-1).float()
|
|
313
|
+
y_hat_corr = self._pytorch_decode_corrected(packed_flat)
|
|
314
|
+
x_hat_corr = torch.matmul(y_hat_corr, self.rotation)
|
|
315
|
+
x_hat_corr = x_hat_corr * norms_flat.unsqueeze(-1)
|
|
316
|
+
x_hat_corr = x_hat_corr.reshape(x.shape)
|
|
317
|
+
est_ip_corr = (x_hat_corr[idx_a] * x[idx_b]).sum(dim=-1)
|
|
318
|
+
ip_bias_corrected = (est_ip_corr - true_ip).mean().item()
|
|
319
|
+
|
|
298
320
|
# Max absolute error
|
|
299
321
|
max_err = (x - x_hat).abs().max().item()
|
|
300
322
|
|
|
@@ -308,6 +330,7 @@ class TurboQuant:
|
|
|
308
330
|
"mse_ratio_to_lower": mse / self.theory_lower if self.theory_lower > 0 else float("inf"),
|
|
309
331
|
"ip_mse": ip_mse,
|
|
310
332
|
"ip_bias": ip_bias,
|
|
333
|
+
"ip_bias_corrected": ip_bias_corrected,
|
|
311
334
|
"max_abs_error": max_err,
|
|
312
335
|
"compression_vs_fp16": self.compression_ratio(),
|
|
313
336
|
"compression_vs_fp8": self.compression_ratio_vs_fp8(),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|