auditkit 1.0.0__py3-none-any.whl
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.
- auditkit/README.md +99 -0
- auditkit/__init__.py +177 -0
- auditkit/__main__.py +3 -0
- auditkit/_bootstrap.py +77 -0
- auditkit/_identity_guard.py +99 -0
- auditkit/adapter.py +264 -0
- auditkit/annotator.py +339 -0
- auditkit/api.py +502 -0
- auditkit/assets/auditkit_logo.png +0 -0
- auditkit/cache.py +47 -0
- auditkit/cli.py +417 -0
- auditkit/comparison.py +563 -0
- auditkit/diff.py +265 -0
- auditkit/errors.py +54 -0
- auditkit/evaluator.py +20 -0
- auditkit/experiment.py +145 -0
- auditkit/hf_publish.py +262 -0
- auditkit/lmeval_engine.py +550 -0
- auditkit/loaders.py +121 -0
- auditkit/logs.py +18 -0
- auditkit/metric.py +199 -0
- auditkit/metrics/README.md +15 -0
- auditkit/metrics/__init__.py +0 -0
- auditkit/metrics/code.py +222 -0
- auditkit/metrics/embedding.py +131 -0
- auditkit/metrics/encoder_judge.py +423 -0
- auditkit/metrics/generation.py +331 -0
- auditkit/metrics/guard.py +412 -0
- auditkit/metrics/hallucination.py +45 -0
- auditkit/metrics/judge.py +547 -0
- auditkit/metrics/pairwise.py +153 -0
- auditkit/metrics/perf.py +53 -0
- auditkit/metrics/rag.py +149 -0
- auditkit/metrics/security.py +64 -0
- auditkit/metrics/toxicity.py +238 -0
- auditkit/model/README.md +16 -0
- auditkit/model/__init__.py +485 -0
- auditkit/model/anthropic.py +94 -0
- auditkit/model/api_gen.py +133 -0
- auditkit/model/groq_gen.py +121 -0
- auditkit/model/hf_gen.py +385 -0
- auditkit/model/lexsi.py +155 -0
- auditkit/model/litellm_gen.py +65 -0
- auditkit/model/openai.py +90 -0
- auditkit/model/openrouter_gen.py +152 -0
- auditkit/model/vllm_gen.py +316 -0
- auditkit/model_compare.py +655 -0
- auditkit/redteam/README.md +9 -0
- auditkit/redteam/__init__.py +26 -0
- auditkit/redteam/detector.py +37 -0
- auditkit/redteam/detectors/README.md +5 -0
- auditkit/redteam/detectors/builtin.py +126 -0
- auditkit/redteam/probe.py +39 -0
- auditkit/redteam/probes/README.md +5 -0
- auditkit/redteam/probes/builtin.py +85 -0
- auditkit/redteam/runner.py +206 -0
- auditkit/registry.py +65 -0
- auditkit/report.py +278 -0
- auditkit/report_format.py +52 -0
- auditkit/router.py +54 -0
- auditkit/runner.py +575 -0
- auditkit/runspec.py +159 -0
- auditkit/sample.py +40 -0
- auditkit/scenario.py +88 -0
- auditkit/scenarios/README.md +10 -0
- auditkit/scenarios/__init__.py +4 -0
- auditkit/scenarios/arc.py +33 -0
- auditkit/scenarios/gsm8k.py +32 -0
- auditkit/scenarios/hellaswag.py +33 -0
- auditkit/scenarios/humaneval.py +32 -0
- auditkit/scenarios/mmlu.py +34 -0
- auditkit/scenarios/truthfulqa.py +33 -0
- auditkit/score.py +165 -0
- auditkit/scorers.py +117 -0
- auditkit/scoring.py +79 -0
- auditkit/types.py +69 -0
- auditkit-1.0.0.dist-info/METADATA +396 -0
- auditkit-1.0.0.dist-info/RECORD +81 -0
- auditkit-1.0.0.dist-info/WHEEL +4 -0
- auditkit-1.0.0.dist-info/entry_points.txt +2 -0
- auditkit-1.0.0.dist-info/licenses/LICENSE.md +92 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""vLLM inference backend (optional extra: auditkit[vllm])."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import contextlib
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from . import (
|
|
10
|
+
Model, Request, Result_, Generated, resolve_params, DEFAULT_TEMPERATURE,
|
|
11
|
+
reject_generation_kwargs, _free_torch_memory,
|
|
12
|
+
)
|
|
13
|
+
from ..errors import ExtraNotInstalled
|
|
14
|
+
|
|
15
|
+
def _apply_environment_defaults() -> None:
|
|
16
|
+
"""Best-effort environment fixes for real, live-confirmed (on Colab)
|
|
17
|
+
vLLM issues, applied once at import time so ordinary users of this
|
|
18
|
+
backend never need to know about them, on any platform.
|
|
19
|
+
|
|
20
|
+
- ``VLLM_ENABLE_V1_MULTIPROCESSING=0``: vLLM's V1 engine forks a
|
|
21
|
+
separate worker process by default, which can't inherit an
|
|
22
|
+
already-initialized CUDA context from the parent -- a real,
|
|
23
|
+
reproducible crash ("Cannot re-initialize CUDA in forked
|
|
24
|
+
subprocess") in any process that touches CUDA before constructing
|
|
25
|
+
an ``LLM`` (nearly guaranteed in a notebook/REPL). Running the
|
|
26
|
+
engine in-process instead sidesteps this unconditionally.
|
|
27
|
+
- ``HF_HUB_DISABLE_XET=1``: ``huggingface_hub``'s newer "Xet Storage"
|
|
28
|
+
download path has 404'd in practice for some legacy repos
|
|
29
|
+
(confirmed live: bare ``"gpt2"``) that aren't Xet-enabled
|
|
30
|
+
server-side. Best-effort only: this only helps if this module is
|
|
31
|
+
imported before user code imports ``transformers``/
|
|
32
|
+
``huggingface_hub`` directly, since ``huggingface_hub`` caches its
|
|
33
|
+
Xet-availability check at first import -- setting this env var
|
|
34
|
+
later has no effect. If a caller hits a Xet 404 despite this, the
|
|
35
|
+
real fix is setting ``HF_HUB_DISABLE_XET=1`` at the very top of
|
|
36
|
+
their own script/notebook, before any other imports.
|
|
37
|
+
|
|
38
|
+
``setdefault`` throughout, so an explicit value the caller already
|
|
39
|
+
set (e.g. to force real multi-process throughput in a dedicated
|
|
40
|
+
script) always wins.
|
|
41
|
+
"""
|
|
42
|
+
os.environ.setdefault("VLLM_ENABLE_V1_MULTIPROCESSING", "0")
|
|
43
|
+
os.environ.setdefault("HF_HUB_DISABLE_XET", "1")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Must run at import time, not inside a method -- some other import path
|
|
47
|
+
# (e.g. `import auditkit` pulling in a different backend first) could
|
|
48
|
+
# otherwise import vllm/huggingface_hub before this class is ever touched.
|
|
49
|
+
_apply_environment_defaults()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@contextlib.contextmanager
|
|
53
|
+
def _stdout_fix():
|
|
54
|
+
"""vLLM internally redirects real OS file descriptors (via
|
|
55
|
+
``sys.stdout.fileno()``) to silence noisy distributed-backend init
|
|
56
|
+
logs during engine construction. Jupyter/Colab/most notebook kernels
|
|
57
|
+
replace ``sys.stdout`` with a stream that forwards over a socket
|
|
58
|
+
instead of a real fd, so that call raises ``UnsupportedOperation`` --
|
|
59
|
+
a real, reproducible crash confirmed live on Colab. Swapping to the
|
|
60
|
+
process's real underlying streams for just the construction call
|
|
61
|
+
fixes it; this is a no-op in a plain terminal, where ``sys.stdout``
|
|
62
|
+
already *is* ``sys.__stdout__``, so it's always safe to apply
|
|
63
|
+
unconditionally rather than only in a detected-notebook branch.
|
|
64
|
+
"""
|
|
65
|
+
real_stdout, real_stderr = sys.stdout, sys.stderr
|
|
66
|
+
sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__
|
|
67
|
+
try:
|
|
68
|
+
yield
|
|
69
|
+
finally:
|
|
70
|
+
sys.stdout, sys.stderr = real_stdout, real_stderr
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _call_shutdown(obj: Any) -> bool:
|
|
74
|
+
"""Call ``shutdown()`` if *obj* has one. Returns True if it ran."""
|
|
75
|
+
if obj is None:
|
|
76
|
+
return False
|
|
77
|
+
fn = getattr(obj, "shutdown", None)
|
|
78
|
+
if not callable(fn):
|
|
79
|
+
return False
|
|
80
|
+
with contextlib.suppress(Exception):
|
|
81
|
+
fn()
|
|
82
|
+
return True
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _drop_attr(obj: Any, *names: str) -> None:
|
|
87
|
+
"""Drop attributes that keep CUDA tensors reachable after teardown."""
|
|
88
|
+
if obj is None:
|
|
89
|
+
return
|
|
90
|
+
for name in names:
|
|
91
|
+
if hasattr(obj, name):
|
|
92
|
+
with contextlib.suppress(Exception):
|
|
93
|
+
setattr(obj, name, None)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _shutdown_vllm_engine(llm: Any) -> None:
|
|
97
|
+
"""Best-effort vLLM teardown across V0/V1 and in-process vs multiproc.
|
|
98
|
+
|
|
99
|
+
Confirmed live on Colab T4 (vLLM 0.19.1): EngineCore/UniProcExecutor
|
|
100
|
+
``shutdown()`` ends at ``WorkerBase.shutdown()``, which is a no-op, so
|
|
101
|
+
weights + KV cache stay allocated. After model A, 4.14/14.56 GiB was
|
|
102
|
+
free and model B died on the 0.7 utilization guard (needs 10.19 GiB).
|
|
103
|
+
``LLM.sleep(level=2)`` is the supported discard (needs
|
|
104
|
+
``enable_sleep_mode=True`` at construction). Then drop the worker/module
|
|
105
|
+
refs so CUDA tensors can actually be collected.
|
|
106
|
+
"""
|
|
107
|
+
with contextlib.suppress(Exception):
|
|
108
|
+
sleep = getattr(llm, "sleep", None)
|
|
109
|
+
if callable(sleep):
|
|
110
|
+
sleep(level=2)
|
|
111
|
+
|
|
112
|
+
engine = getattr(llm, "llm_engine", None) or getattr(llm, "engine", None)
|
|
113
|
+
client = getattr(engine, "engine_core", None)
|
|
114
|
+
core = getattr(client, "engine_core", None)
|
|
115
|
+
executor = (
|
|
116
|
+
getattr(engine, "model_executor", None)
|
|
117
|
+
or getattr(core, "model_executor", None)
|
|
118
|
+
)
|
|
119
|
+
worker = getattr(executor, "driver_worker", None)
|
|
120
|
+
runner = getattr(worker, "model_runner", None)
|
|
121
|
+
if runner is None:
|
|
122
|
+
runner = getattr(getattr(worker, "worker", None), "model_runner", None)
|
|
123
|
+
model = getattr(runner, "model", None)
|
|
124
|
+
if model is not None:
|
|
125
|
+
with contextlib.suppress(Exception):
|
|
126
|
+
model.to("cpu")
|
|
127
|
+
_drop_attr(runner, "model", "kv_caches", "kv_cache")
|
|
128
|
+
|
|
129
|
+
_call_shutdown(llm)
|
|
130
|
+
_call_shutdown(client)
|
|
131
|
+
_call_shutdown(core)
|
|
132
|
+
_call_shutdown(executor)
|
|
133
|
+
_call_shutdown(worker)
|
|
134
|
+
_drop_attr(executor, "driver_worker")
|
|
135
|
+
_drop_attr(engine, "model_executor")
|
|
136
|
+
_drop_attr(core, "model_executor", "scheduler")
|
|
137
|
+
_drop_attr(engine, "engine_core")
|
|
138
|
+
_drop_attr(llm, "llm_engine", "engine")
|
|
139
|
+
del llm
|
|
140
|
+
with contextlib.suppress(Exception):
|
|
141
|
+
import gc
|
|
142
|
+
gc.unfreeze()
|
|
143
|
+
with contextlib.suppress(Exception):
|
|
144
|
+
from vllm.distributed.parallel_state import cleanup_dist_env_and_memory
|
|
145
|
+
cleanup_dist_env_and_memory()
|
|
146
|
+
with contextlib.suppress(Exception):
|
|
147
|
+
from vllm.distributed.parallel_state import (
|
|
148
|
+
destroy_distributed_environment, destroy_model_parallel,
|
|
149
|
+
)
|
|
150
|
+
destroy_model_parallel()
|
|
151
|
+
destroy_distributed_environment()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# vLLM's SamplingParams supports most of the common sampling knobs directly.
|
|
155
|
+
_KEY_MAP = {
|
|
156
|
+
"temperature": "temperature",
|
|
157
|
+
"top_p": "top_p",
|
|
158
|
+
"top_k": "top_k",
|
|
159
|
+
"max_tokens": "max_tokens",
|
|
160
|
+
"stop_sequences": "stop",
|
|
161
|
+
"presence_penalty": "presence_penalty",
|
|
162
|
+
"frequency_penalty": "frequency_penalty",
|
|
163
|
+
"seed": "seed",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class VLLMModel(Model):
|
|
168
|
+
"""Run inference using a vLLM engine.
|
|
169
|
+
|
|
170
|
+
``is_local = True`` and ``model_info()`` attempts real parameter
|
|
171
|
+
count/size introspection (same approach as ``HFGenModel``), but vLLM's
|
|
172
|
+
internal engine structure has changed significantly between its V0/V1
|
|
173
|
+
architectures and varies across releases, with no single stable public
|
|
174
|
+
attribute for "the loaded nn.Module". ``model_info()`` tries the known
|
|
175
|
+
internal access paths in order and falls back to identity-only
|
|
176
|
+
reporting (this class's previous, unconditional behavior) if none of
|
|
177
|
+
them resolve on the installed vLLM version, rather than crashing.
|
|
178
|
+
**Not yet verified against a real vLLM install** -- vLLM is CUDA/Linux-
|
|
179
|
+
oriented and won't install in the environment this was written in.
|
|
180
|
+
Verify the numeric fields against a real install before trusting them,
|
|
181
|
+
the same way HFGenModel's introspection was verified against real gpt2.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
name = "vllm"
|
|
185
|
+
threadsafe = False
|
|
186
|
+
is_local = True
|
|
187
|
+
|
|
188
|
+
def __init__(self, model: str = "gpt2", name: str = "vllm", **kwargs: Any) -> None:
|
|
189
|
+
"""``**kwargs`` are real ``vllm.LLM()`` engine-construction arguments
|
|
190
|
+
(e.g. ``gpu_memory_utilization=0.3``, ``dtype="float16"``,
|
|
191
|
+
``tensor_parallel_size=2``, ``max_model_len=2048``,
|
|
192
|
+
``trust_remote_code=True``, ``quantization="awq"``) -- forwarded to
|
|
193
|
+
``LLM()`` at load time in :meth:`_ensure_llm`, not to
|
|
194
|
+
``SamplingParams`` (a previous version of this class forwarded them
|
|
195
|
+
to ``SamplingParams`` instead, which doesn't accept engine-level
|
|
196
|
+
arguments at all and would raise ``TypeError`` for any of the ones
|
|
197
|
+
above). Generation-time sampling knobs belong on the per-request
|
|
198
|
+
``RunConfig``/``Request.params`` instead (see ``_KEY_MAP``), not
|
|
199
|
+
here.
|
|
200
|
+
"""
|
|
201
|
+
reject_generation_kwargs(kwargs, "VLLMModel")
|
|
202
|
+
self._model_name = model
|
|
203
|
+
self._extra_kwargs = kwargs
|
|
204
|
+
self._llm = None
|
|
205
|
+
self.name = name
|
|
206
|
+
|
|
207
|
+
def unload(self) -> None:
|
|
208
|
+
"""Tear down the loaded vLLM engine and free the GPU, so
|
|
209
|
+
``compare_models()`` can load the next model without stacking VRAM.
|
|
210
|
+
|
|
211
|
+
Confirmed live on Colab T4 (vLLM 0.19.1, in-process V1 via
|
|
212
|
+
``VLLM_ENABLE_V1_MULTIPROCESSING=0``): dropping ``self._llm`` and
|
|
213
|
+
calling ``torch.cuda.empty_cache()`` leaves weights + KV cache
|
|
214
|
+
allocated. The next ``LLM(...)`` then fails the startup free-memory
|
|
215
|
+
guard (``Free memory on device cuda:0 (3.42/14.56 GiB) ... less than
|
|
216
|
+
desired GPU memory utilization (0.7, 10.19 GiB)``). ``LLM`` on
|
|
217
|
+
0.19.1 has no ``shutdown()``; teardown is
|
|
218
|
+
``llm.llm_engine.engine_core.shutdown()`` → ``EngineCore`` →
|
|
219
|
+
``model_executor.shutdown()``. Later vLLM added ``LLM.shutdown()``;
|
|
220
|
+
we prefer that when present, then walk the known attributes.
|
|
221
|
+
"""
|
|
222
|
+
llm = self._llm
|
|
223
|
+
self._llm = None
|
|
224
|
+
if llm is not None:
|
|
225
|
+
_shutdown_vllm_engine(llm)
|
|
226
|
+
_free_torch_memory()
|
|
227
|
+
|
|
228
|
+
def _find_underlying_model(self) -> Any:
|
|
229
|
+
"""Try each known vLLM-internal attribute path to the loaded
|
|
230
|
+
``nn.Module``, across engine versions/releases -- ``None`` if none
|
|
231
|
+
of them resolve on the installed vLLM version."""
|
|
232
|
+
candidates = (
|
|
233
|
+
lambda: self._llm.llm_engine.model_executor.driver_worker.model_runner.model,
|
|
234
|
+
lambda: self._llm.llm_engine.model_executor.driver_worker.worker.model_runner.model,
|
|
235
|
+
lambda: self._llm.llm_engine.engine_core.model_executor.driver_worker.model_runner.model,
|
|
236
|
+
)
|
|
237
|
+
for get in candidates:
|
|
238
|
+
try:
|
|
239
|
+
return get()
|
|
240
|
+
except AttributeError:
|
|
241
|
+
continue
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
def model_info(self) -> dict[str, Any]:
|
|
245
|
+
"""Real parameter count/sparsity/size if the installed vLLM
|
|
246
|
+
version's internal engine structure matches a known access path
|
|
247
|
+
(see :meth:`_find_underlying_model`); identity-only otherwise --
|
|
248
|
+
never a guessed number."""
|
|
249
|
+
self._ensure_llm()
|
|
250
|
+
model = self._find_underlying_model()
|
|
251
|
+
if model is None:
|
|
252
|
+
return {"is_local": True, "model_name": self._model_name}
|
|
253
|
+
params = list(model.parameters())
|
|
254
|
+
total_params = sum(p.numel() for p in params)
|
|
255
|
+
nonzero_params = sum(int((p != 0).sum().item()) for p in params)
|
|
256
|
+
size_bytes = sum(p.numel() * p.element_size() for p in params)
|
|
257
|
+
return {
|
|
258
|
+
"is_local": True,
|
|
259
|
+
"model_name": self._model_name,
|
|
260
|
+
"total_params": total_params,
|
|
261
|
+
"nonzero_params": nonzero_params,
|
|
262
|
+
"sparsity": 1.0 - (nonzero_params / total_params) if total_params else 0.0,
|
|
263
|
+
"size_mb": size_bytes / (1024 * 1024),
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
def _ensure_llm(self) -> None:
|
|
267
|
+
if self._llm is not None:
|
|
268
|
+
return
|
|
269
|
+
try:
|
|
270
|
+
from vllm import LLM, SamplingParams
|
|
271
|
+
except ImportError:
|
|
272
|
+
raise ExtraNotInstalled("vllm", "pip install auditkit[vllm]")
|
|
273
|
+
self._sampling_params = SamplingParams
|
|
274
|
+
with _stdout_fix():
|
|
275
|
+
self._llm = LLM(model=self._model_name, **self._extra_kwargs)
|
|
276
|
+
|
|
277
|
+
def _render_prompt(self, request: Request) -> str:
|
|
278
|
+
"""Same wire-format rule as HFGenModel: instruct models get a chat template.
|
|
279
|
+
|
|
280
|
+
``chat_template`` counts only as a non-empty str so MagicMock engines
|
|
281
|
+
in existing tests still send the raw prompt.
|
|
282
|
+
"""
|
|
283
|
+
text = request.prompt if isinstance(request.prompt, str) else str(request.prompt)
|
|
284
|
+
if request.params.get("apply_chat_template") is False:
|
|
285
|
+
return text
|
|
286
|
+
tokenizer = None
|
|
287
|
+
getter = getattr(self._llm, "get_tokenizer", None) if self._llm is not None else None
|
|
288
|
+
if callable(getter):
|
|
289
|
+
try:
|
|
290
|
+
tokenizer = getter()
|
|
291
|
+
except Exception:
|
|
292
|
+
tokenizer = None
|
|
293
|
+
chat_template = getattr(tokenizer, "chat_template", None) if tokenizer is not None else None
|
|
294
|
+
if not isinstance(chat_template, str) or not chat_template:
|
|
295
|
+
return text
|
|
296
|
+
apply = getattr(tokenizer, "apply_chat_template", None)
|
|
297
|
+
if not callable(apply):
|
|
298
|
+
return text
|
|
299
|
+
messages = request.params.get("messages") or [{"role": "user", "content": text}]
|
|
300
|
+
extra = request.params.get("chat_template_kwargs") or {}
|
|
301
|
+
return apply(messages, tokenize=False, add_generation_prompt=True, **extra)
|
|
302
|
+
|
|
303
|
+
def generate(self, requests: list[Request]) -> list[Result_]:
|
|
304
|
+
self._ensure_llm()
|
|
305
|
+
prompts = [self._render_prompt(r) for r in requests]
|
|
306
|
+
# One shared SamplingParams for the whole batch -- same caveat as
|
|
307
|
+
# HFGenModel: per-request variation isn't meaningful here.
|
|
308
|
+
defaults = {"temperature": DEFAULT_TEMPERATURE, "max_tokens": 128}
|
|
309
|
+
gen_kwargs = resolve_params(requests[0], defaults, _KEY_MAP) if requests else defaults
|
|
310
|
+
params = self._sampling_params(**gen_kwargs)
|
|
311
|
+
outputs = self._llm.generate(prompts, params)
|
|
312
|
+
results = []
|
|
313
|
+
for out in outputs:
|
|
314
|
+
text = out.outputs[0].text if out.outputs else ""
|
|
315
|
+
results.append(Result_(completions=[Generated(text=text)]))
|
|
316
|
+
return results
|