undercurrent 0.0.0a0__tar.gz → 0.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.
- undercurrent-0.2.0/.gitignore +58 -0
- undercurrent-0.2.0/CHANGELOG.md +254 -0
- undercurrent-0.2.0/NOTICE +4 -0
- undercurrent-0.2.0/PKG-INFO +229 -0
- undercurrent-0.2.0/README.md +172 -0
- undercurrent-0.2.0/benchmarks/README.md +390 -0
- undercurrent-0.2.0/benchmarks/run.py +1413 -0
- undercurrent-0.2.0/docs/README.md +50 -0
- undercurrent-0.2.0/docs/_legacy/probing_adapter_hf.md +68 -0
- undercurrent-0.2.0/docs/_legacy/probing_adapter_vllm.md +389 -0
- undercurrent-0.2.0/docs/_legacy/probing_content_safety.md +394 -0
- undercurrent-0.2.0/docs/_legacy/probing_core.md +136 -0
- undercurrent-0.2.0/docs/_legacy/probing_logging.md +177 -0
- undercurrent-0.2.0/docs/_legacy/probing_router.md +247 -0
- undercurrent-0.2.0/docs/_legacy/probing_spec.md +227 -0
- undercurrent-0.2.0/docs/_legacy/vllm_tp_pp_activation_extraction_notes.md +443 -0
- undercurrent-0.2.0/docs/about/changelog.md +6 -0
- undercurrent-0.2.0/docs/about/contributing.md +12 -0
- undercurrent-0.2.0/docs/about/intended-use.md +136 -0
- undercurrent-0.2.0/docs/about/privacy.md +203 -0
- undercurrent-0.2.0/docs/api-stability.md +156 -0
- undercurrent-0.2.0/docs/assets/brand/social-preview.png +0 -0
- undercurrent-0.2.0/docs/assets/brand/undercurrent-icon.svg +8 -0
- undercurrent-0.2.0/docs/assets/brand/undercurrent-logo-dark.png +0 -0
- undercurrent-0.2.0/docs/assets/brand/undercurrent-logo-dark.svg +8 -0
- undercurrent-0.2.0/docs/assets/brand/undercurrent-logo.png +0 -0
- undercurrent-0.2.0/docs/assets/brand/undercurrent-logo.svg +8 -0
- undercurrent-0.2.0/docs/assets/brand/undercurrent-mark-dark.svg +8 -0
- undercurrent-0.2.0/docs/assets/brand/undercurrent-mark.svg +8 -0
- undercurrent-0.2.0/docs/benchmarks.md +6 -0
- undercurrent-0.2.0/docs/comparison.md +264 -0
- undercurrent-0.2.0/docs/compatibility.md +183 -0
- undercurrent-0.2.0/docs/concepts/architecture.md +233 -0
- undercurrent-0.2.0/docs/concepts/execution-modes.md +194 -0
- undercurrent-0.2.0/docs/concepts/extraction-points.md +256 -0
- undercurrent-0.2.0/docs/concepts/interventions.md +180 -0
- undercurrent-0.2.0/docs/concepts/position-selectors.md +142 -0
- undercurrent-0.2.0/docs/concepts/probes.md +276 -0
- undercurrent-0.2.0/docs/examples.md +61 -0
- undercurrent-0.2.0/docs/getting-started/installation.md +89 -0
- undercurrent-0.2.0/docs/getting-started/quickstart.md +271 -0
- undercurrent-0.2.0/docs/guides/custom-probe.md +813 -0
- undercurrent-0.2.0/docs/guides/observation-sinks.md +714 -0
- undercurrent-0.2.0/docs/guides/vllm-deployment.md +538 -0
- undercurrent-0.2.0/docs/index.md +88 -0
- undercurrent-0.2.0/docs/internals/vllm-adapter.md +489 -0
- undercurrent-0.2.0/docs/internals/vllm-parallelism.md +440 -0
- undercurrent-0.2.0/docs/production/async-execution.md +314 -0
- undercurrent-0.2.0/docs/production/embedding.md +447 -0
- undercurrent-0.2.0/docs/production/intervention-policies.md +308 -0
- undercurrent-0.2.0/docs/production/metrics.md +299 -0
- undercurrent-0.2.0/docs/reference/adapters.md +70 -0
- undercurrent-0.2.0/docs/reference/cli.md +120 -0
- undercurrent-0.2.0/docs/reference/core.md +140 -0
- undercurrent-0.2.0/docs/reference/index.md +270 -0
- undercurrent-0.2.0/docs/reference/model.md +52 -0
- undercurrent-0.2.0/docs/reference/router.md +102 -0
- undercurrent-0.2.0/docs/reference/sinks.md +86 -0
- undercurrent-0.2.0/docs/reference/spec.md +202 -0
- undercurrent-0.2.0/docs/stylesheets/brand.css +31 -0
- undercurrent-0.2.0/examples/content_safety/Dockerfile +73 -0
- undercurrent-0.2.0/examples/content_safety/README.md +36 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_demo/__init__.py +45 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_demo/custom_mlp.py +296 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_demo/models.py +123 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_demo/single_token.py +136 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_demo/spec_binding.py +61 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_demo/tensors.py +25 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_demo/trajectory.py +144 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_gpt2.yaml +28 -0
- undercurrent-0.2.0/examples/content_safety/content_safety_llama.yaml +31 -0
- undercurrent-0.2.0/examples/content_safety/custom_mlp_llama31_8b.yaml +26 -0
- undercurrent-0.2.0/examples/content_safety/make_dummy_mlp_checkpoint.py +65 -0
- undercurrent-0.2.0/examples/content_safety/run_vllm_pipeline.py +166 -0
- undercurrent-0.2.0/examples/function_probe.py +18 -0
- undercurrent-0.2.0/examples/openai_server/README.md +5 -0
- undercurrent-0.2.0/examples/openai_server/response_schema.py +404 -0
- undercurrent-0.2.0/examples/openai_server/serve_llama_mlp_pipeline.py +339 -0
- undercurrent-0.2.0/examples/quickstart.py +24 -0
- undercurrent-0.2.0/examples/specs/classic_single_token.yaml +13 -0
- undercurrent-0.2.0/examples/specs/continuous_trajectory.yaml +16 -0
- undercurrent-0.2.0/examples/specs/offset_dual_position.yaml +14 -0
- undercurrent-0.2.0/examples/specs/sliced_range.yaml +13 -0
- undercurrent-0.2.0/examples/train_probe/README.md +188 -0
- undercurrent-0.2.0/examples/train_probe/collector.py +79 -0
- undercurrent-0.2.0/examples/train_probe/data_sources.py +132 -0
- undercurrent-0.2.0/examples/train_probe/linear_probe.py +262 -0
- undercurrent-0.2.0/examples/train_probe/requirements.txt +3 -0
- undercurrent-0.2.0/examples/train_probe/train_probe.py +286 -0
- undercurrent-0.2.0/examples/train_probe/use_probe.py +19 -0
- undercurrent-0.2.0/notebooks/quickstart.ipynb +357 -0
- undercurrent-0.2.0/notebooks/train_probe.ipynb +415 -0
- undercurrent-0.2.0/pyproject.toml +297 -0
- undercurrent-0.2.0/schema/probe-spec.schema.json +481 -0
- undercurrent-0.2.0/scripts/generate_schema.py +34 -0
- undercurrent-0.2.0/scripts/gpu_check.sh +203 -0
- undercurrent-0.2.0/scripts/smoke_test_wheel.sh +452 -0
- undercurrent-0.2.0/src/undercurrent/__init__.py +191 -0
- undercurrent-0.2.0/src/undercurrent/__main__.py +8 -0
- undercurrent-0.2.0/src/undercurrent/_version.py +24 -0
- undercurrent-0.2.0/src/undercurrent/adapters/__init__.py +26 -0
- undercurrent-0.2.0/src/undercurrent/adapters/_optional.py +89 -0
- undercurrent-0.2.0/src/undercurrent/adapters/base.py +141 -0
- undercurrent-0.2.0/src/undercurrent/adapters/hf/__init__.py +38 -0
- undercurrent-0.2.0/src/undercurrent/adapters/hf/adapter.py +459 -0
- undercurrent-0.2.0/src/undercurrent/adapters/hf/errors.py +15 -0
- undercurrent-0.2.0/src/undercurrent/adapters/hf/introspect.py +107 -0
- undercurrent-0.2.0/src/undercurrent/adapters/hf/stopping_criteria.py +72 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/__init__.py +46 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/activation_batch.py +90 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/adapter.py +794 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/graph_capture.py +133 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/introspection.py +153 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/plugin.py +181 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/seq_mapper.py +236 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/support.py +89 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/version_check.py +100 -0
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/worker_extension.py +998 -0
- undercurrent-0.2.0/src/undercurrent/cli/__init__.py +115 -0
- undercurrent-0.2.0/src/undercurrent/cli/_errors.py +13 -0
- undercurrent-0.2.0/src/undercurrent/cli/inspect_model.py +308 -0
- undercurrent-0.2.0/src/undercurrent/cli/schema.py +40 -0
- undercurrent-0.2.0/src/undercurrent/cli/validate.py +164 -0
- undercurrent-0.2.0/src/undercurrent/core/__init__.py +69 -0
- undercurrent-0.2.0/src/undercurrent/core/activation.py +11 -0
- undercurrent-0.2.0/src/undercurrent/core/context.py +29 -0
- undercurrent-0.2.0/src/undercurrent/core/examples/__init__.py +15 -0
- undercurrent-0.2.0/src/undercurrent/core/examples/_util.py +23 -0
- undercurrent-0.2.0/src/undercurrent/core/examples/mlp_classifier.py +73 -0
- undercurrent-0.2.0/src/undercurrent/core/examples/trajectory_score.py +102 -0
- undercurrent-0.2.0/src/undercurrent/core/function_probe.py +364 -0
- undercurrent-0.2.0/src/undercurrent/core/probe.py +207 -0
- undercurrent-0.2.0/src/undercurrent/core/registry.py +393 -0
- undercurrent-0.2.0/src/undercurrent/core/result.py +33 -0
- undercurrent-0.2.0/src/undercurrent/core/signal.py +41 -0
- undercurrent-0.2.0/src/undercurrent/errors.py +97 -0
- undercurrent-0.2.0/src/undercurrent/model.py +955 -0
- undercurrent-0.2.0/src/undercurrent/py.typed +0 -0
- undercurrent-0.2.0/src/undercurrent/router/__init__.py +85 -0
- undercurrent-0.2.0/src/undercurrent/router/binding.py +246 -0
- undercurrent-0.2.0/src/undercurrent/router/errors.py +14 -0
- undercurrent-0.2.0/src/undercurrent/router/metrics.py +233 -0
- undercurrent-0.2.0/src/undercurrent/router/overflow.py +165 -0
- undercurrent-0.2.0/src/undercurrent/router/request.py +134 -0
- undercurrent-0.2.0/src/undercurrent/router/router.py +888 -0
- undercurrent-0.2.0/src/undercurrent/sinks/__init__.py +55 -0
- undercurrent-0.2.0/src/undercurrent/sinks/file_sink.py +69 -0
- undercurrent-0.2.0/src/undercurrent/sinks/records.py +32 -0
- undercurrent-0.2.0/src/undercurrent/sinks/redaction.py +179 -0
- undercurrent-0.2.0/src/undercurrent/sinks/serialize.py +70 -0
- undercurrent-0.2.0/src/undercurrent/sinks/sink.py +97 -0
- undercurrent-0.2.0/src/undercurrent/sinks/webhook_sink.py +217 -0
- undercurrent-0.2.0/src/undercurrent/sinks/wiring.py +16 -0
- undercurrent-0.2.0/src/undercurrent/spec/__init__.py +80 -0
- undercurrent-0.2.0/src/undercurrent/spec/activation_record.py +86 -0
- undercurrent-0.2.0/src/undercurrent/spec/errors.py +41 -0
- undercurrent-0.2.0/src/undercurrent/spec/json_schema.py +248 -0
- undercurrent-0.2.0/src/undercurrent/spec/parser.py +476 -0
- undercurrent-0.2.0/src/undercurrent/spec/position.py +285 -0
- undercurrent-0.2.0/src/undercurrent/spec/resolved.py +223 -0
- undercurrent-0.2.0/src/undercurrent/spec/schema.py +379 -0
- undercurrent-0.2.0/src/undercurrent/spec/serialize.py +58 -0
- undercurrent-0.2.0/tests/__init__.py +0 -0
- undercurrent-0.2.0/tests/adapters/__init__.py +0 -0
- undercurrent-0.2.0/tests/adapters/hf/__init__.py +0 -0
- undercurrent-0.2.0/tests/adapters/hf/_helpers.py +86 -0
- undercurrent-0.2.0/tests/adapters/hf/conftest.py +55 -0
- undercurrent-0.2.0/tests/adapters/hf/test_adapter_contract.py +92 -0
- undercurrent-0.2.0/tests/adapters/hf/test_intervention_e2e.py +127 -0
- undercurrent-0.2.0/tests/adapters/hf/test_introspect.py +69 -0
- undercurrent-0.2.0/tests/adapters/test_optional.py +42 -0
- undercurrent-0.2.0/tests/adapters/vllm/__init__.py +0 -0
- undercurrent-0.2.0/tests/adapters/vllm/_helpers.py +88 -0
- undercurrent-0.2.0/tests/adapters/vllm/conftest.py +65 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_activation_batch.py +92 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_adapter_contract.py +795 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_adapter_shutdown.py +90 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_graph_capture.py +216 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_integration_vllm.py +213 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_plugin.py +90 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_residual_stream_capture.py +354 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_residual_stream_gpu.py +199 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_seq_mapper.py +225 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_support.py +54 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_version_check.py +111 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_bookkeeping.py +237 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_capture.py +221 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_staging.py +279 -0
- undercurrent-0.2.0/tests/benchmarks/__init__.py +0 -0
- undercurrent-0.2.0/tests/benchmarks/test_harness.py +353 -0
- undercurrent-0.2.0/tests/cli/__init__.py +0 -0
- undercurrent-0.2.0/tests/cli/conftest.py +35 -0
- undercurrent-0.2.0/tests/cli/test_inspect_model.py +162 -0
- undercurrent-0.2.0/tests/cli/test_main.py +76 -0
- undercurrent-0.2.0/tests/cli/test_schema.py +34 -0
- undercurrent-0.2.0/tests/cli/test_validate.py +238 -0
- undercurrent-0.2.0/tests/conftest.py +72 -0
- undercurrent-0.2.0/tests/core/__init__.py +0 -0
- undercurrent-0.2.0/tests/core/conftest.py +27 -0
- undercurrent-0.2.0/tests/core/test_function_probe.py +515 -0
- undercurrent-0.2.0/tests/core/test_probe_isolation.py +100 -0
- undercurrent-0.2.0/tests/core/test_probe_result_always_returned.py +38 -0
- undercurrent-0.2.0/tests/core/test_registry.py +347 -0
- undercurrent-0.2.0/tests/core/test_trajectory_score_probe.py +59 -0
- undercurrent-0.2.0/tests/docs/__init__.py +0 -0
- undercurrent-0.2.0/tests/docs/test_doc_snippets.py +40 -0
- undercurrent-0.2.0/tests/e2e/__init__.py +0 -0
- undercurrent-0.2.0/tests/e2e/test_hf_pipeline_cpu.py +199 -0
- undercurrent-0.2.0/tests/examples/__init__.py +0 -0
- undercurrent-0.2.0/tests/examples/content_safety/__init__.py +0 -0
- undercurrent-0.2.0/tests/examples/content_safety/conftest.py +37 -0
- undercurrent-0.2.0/tests/examples/content_safety/test_custom_mlp_probe.py +200 -0
- undercurrent-0.2.0/tests/examples/content_safety/test_isolation.py +62 -0
- undercurrent-0.2.0/tests/examples/content_safety/test_live_llama_integration.py +98 -0
- undercurrent-0.2.0/tests/examples/content_safety/test_single_token_probe.py +83 -0
- undercurrent-0.2.0/tests/examples/content_safety/test_spec_binding.py +112 -0
- undercurrent-0.2.0/tests/examples/content_safety/test_trajectory_probe.py +118 -0
- undercurrent-0.2.0/tests/examples/openai_server/__init__.py +0 -0
- undercurrent-0.2.0/tests/examples/openai_server/conftest.py +6 -0
- undercurrent-0.2.0/tests/examples/openai_server/test_response_schema.py +324 -0
- undercurrent-0.2.0/tests/examples/openai_server/test_server_cli.py +13 -0
- undercurrent-0.2.0/tests/examples/test_function_probe_example.py +24 -0
- undercurrent-0.2.0/tests/examples/train_probe/__init__.py +0 -0
- undercurrent-0.2.0/tests/examples/train_probe/conftest.py +6 -0
- undercurrent-0.2.0/tests/examples/train_probe/test_train_probe.py +331 -0
- undercurrent-0.2.0/tests/notebooks/__init__.py +0 -0
- undercurrent-0.2.0/tests/notebooks/test_notebooks.py +77 -0
- undercurrent-0.2.0/tests/router/__init__.py +0 -0
- undercurrent-0.2.0/tests/router/_helpers.py +210 -0
- undercurrent-0.2.0/tests/router/conftest.py +47 -0
- undercurrent-0.2.0/tests/router/test_context_managers.py +268 -0
- undercurrent-0.2.0/tests/router/test_dispatch.py +118 -0
- undercurrent-0.2.0/tests/router/test_end_request.py +95 -0
- undercurrent-0.2.0/tests/router/test_intervention.py +288 -0
- undercurrent-0.2.0/tests/router/test_isolation.py +82 -0
- undercurrent-0.2.0/tests/router/test_load.py +129 -0
- undercurrent-0.2.0/tests/router/test_metrics.py +294 -0
- undercurrent-0.2.0/tests/router/test_overflow.py +110 -0
- undercurrent-0.2.0/tests/router/test_probe_args.py +48 -0
- undercurrent-0.2.0/tests/router/test_probe_registry.py +122 -0
- undercurrent-0.2.0/tests/router/test_registration.py +111 -0
- undercurrent-0.2.0/tests/router/test_shutdown.py +129 -0
- undercurrent-0.2.0/tests/router/test_stress_overflow.py +177 -0
- undercurrent-0.2.0/tests/sinks/__init__.py +0 -0
- undercurrent-0.2.0/tests/sinks/_helpers.py +125 -0
- undercurrent-0.2.0/tests/sinks/conftest.py +28 -0
- undercurrent-0.2.0/tests/sinks/test_file_sink.py +105 -0
- undercurrent-0.2.0/tests/sinks/test_redaction.py +455 -0
- undercurrent-0.2.0/tests/sinks/test_router_integration.py +167 -0
- undercurrent-0.2.0/tests/sinks/test_webhook_sink.py +178 -0
- undercurrent-0.2.0/tests/spec/__init__.py +0 -0
- undercurrent-0.2.0/tests/spec/test_activation_record.py +116 -0
- undercurrent-0.2.0/tests/spec/test_intervention.py +117 -0
- undercurrent-0.2.0/tests/spec/test_json_schema.py +323 -0
- undercurrent-0.2.0/tests/spec/test_parser.py +171 -0
- undercurrent-0.2.0/tests/spec/test_position.py +140 -0
- undercurrent-0.2.0/tests/spec/test_roundtrip.py +100 -0
- undercurrent-0.2.0/tests/spec/test_vocab.py +250 -0
- undercurrent-0.2.0/tests/test_errors.py +571 -0
- undercurrent-0.2.0/tests/test_gpu_check.py +91 -0
- undercurrent-0.2.0/tests/test_import_light.py +17 -0
- undercurrent-0.2.0/tests/test_markers.py +18 -0
- undercurrent-0.2.0/tests/test_metadata.py +20 -0
- undercurrent-0.2.0/tests/test_model.py +642 -0
- undercurrent-0.2.0/tests/test_model_vllm.py +478 -0
- undercurrent-0.2.0/tests/test_public_api.py +397 -0
- undercurrent-0.2.0/tests/test_release_workflow.py +67 -0
- undercurrent-0.2.0/tests/test_version.py +11 -0
- undercurrent-0.0.0a0/PKG-INFO +0 -28
- undercurrent-0.0.0a0/README.md +0 -14
- undercurrent-0.0.0a0/pyproject.toml +0 -24
- undercurrent-0.0.0a0/src/undercurrent/__init__.py +0 -15
- {undercurrent-0.0.0a0 → undercurrent-0.2.0}/LICENSE +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Root ignore rules for the whole repository. Keep .dockerignore in sync:
|
|
2
|
+
# anything that must never be committed should also stay out of image
|
|
3
|
+
# build contexts.
|
|
4
|
+
|
|
5
|
+
# --- Python bytecode and caches ---------------------------------------------
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
|
|
12
|
+
# --- Build output ------------------------------------------------------------
|
|
13
|
+
build/
|
|
14
|
+
dist/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
# Generated by hatch-vcs at build/install time
|
|
17
|
+
src/undercurrent/_version.py
|
|
18
|
+
# mkdocs build output
|
|
19
|
+
site/
|
|
20
|
+
|
|
21
|
+
# --- Environments ------------------------------------------------------------
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
.env
|
|
25
|
+
.env.*
|
|
26
|
+
!.env.example
|
|
27
|
+
|
|
28
|
+
# --- Coverage ----------------------------------------------------------------
|
|
29
|
+
.coverage*
|
|
30
|
+
htmlcov/
|
|
31
|
+
coverage.xml
|
|
32
|
+
|
|
33
|
+
# --- Model weights and checkpoints -------------------------------------------
|
|
34
|
+
# Trained checkpoints are private, per-user artefacts and can be tens of MB.
|
|
35
|
+
# Never commit them; publish weights through the Hugging Face Hub instead.
|
|
36
|
+
*.pt
|
|
37
|
+
*.pth
|
|
38
|
+
*.ckpt
|
|
39
|
+
*.safetensors
|
|
40
|
+
*.bin
|
|
41
|
+
*.gguf
|
|
42
|
+
|
|
43
|
+
# --- Notebooks ---------------------------------------------------------------
|
|
44
|
+
.ipynb_checkpoints/
|
|
45
|
+
|
|
46
|
+
# --- Local tooling -----------------------------------------------------------
|
|
47
|
+
.claude/worktrees/
|
|
48
|
+
.claude/settings.local.json
|
|
49
|
+
.idea/
|
|
50
|
+
.vscode/
|
|
51
|
+
.DS_Store
|
|
52
|
+
|
|
53
|
+
# --- Run artefacts -----------------------------------------------------------
|
|
54
|
+
# Observation / metrics sink output
|
|
55
|
+
*.ndjson
|
|
56
|
+
benchmarks/results/
|
|
57
|
+
# examples/train_probe output (probe weights, metrics), wherever it's run from
|
|
58
|
+
**/outputs/train_probe/
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Undercurrent are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
While Undercurrent is on 0.x:
|
|
9
|
+
|
|
10
|
+
- A **minor** release (0.1 → 0.2) may contain breaking changes. Each one is
|
|
11
|
+
listed under **Changed** or **Removed** with what to do about it.
|
|
12
|
+
- A **patch** release (0.1.0 → 0.1.1) contains only backwards-compatible fixes.
|
|
13
|
+
- A deprecated API keeps working and emits a `DeprecationWarning` for at least
|
|
14
|
+
one minor release before it is removed.
|
|
15
|
+
|
|
16
|
+
Versions come from git tags (`vX.Y.Z`). See [RELEASING.md](RELEASING.md) for
|
|
17
|
+
how a release is cut.
|
|
18
|
+
|
|
19
|
+
## [Unreleased]
|
|
20
|
+
|
|
21
|
+
## [0.2.0] - 2026-11-01
|
|
22
|
+
|
|
23
|
+
Performance and correctness work on the vLLM adapter, and experimental
|
|
24
|
+
graph-mode `residual_stream` capture that keeps CUDA graphs and
|
|
25
|
+
`torch.compile`. There are no breaking changes: code written for 0.1.0 runs
|
|
26
|
+
unchanged.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- vLLM adapter (experimental): graph-mode `residual_stream` capture.
|
|
31
|
+
`load_model(graph_capture_layers=[...])` (also accepted by
|
|
32
|
+
`ProbedModel(..., backend="vllm")`) captures the residual stream at the given
|
|
33
|
+
decoder layers without `enforce_eager=True`, so the engine keeps CUDA graphs
|
|
34
|
+
and `torch.compile`. It reuses vLLM's EAGLE-3 auxiliary hidden-state outputs,
|
|
35
|
+
enabled by the `vllm.general_plugins` entry point before compilation. Other
|
|
36
|
+
tensor types, other layers, models without that interface, and speculative
|
|
37
|
+
decoding that uses aux outputs are rejected with an error. The benchmark
|
|
38
|
+
harness gains `--graph-capture`.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- vLLM adapter: activation capture on CUDA no longer blocks the host once per
|
|
43
|
+
captured row. Each forward hook gathers its matched rows on the GPU and
|
|
44
|
+
starts one non-blocking copy into pinned host memory; records are built after
|
|
45
|
+
one wait per step (at the end of the step in-process, or when the driver
|
|
46
|
+
polls on the cross-process path). Probes see the same records, in the same
|
|
47
|
+
order, with inline aborts taking effect at the same step as before. See
|
|
48
|
+
"Copying captures to the host" in the vLLM adapter internals docs.
|
|
49
|
+
- vLLM adapter: on the cross-process polling path (vLLM 0.28's default), the
|
|
50
|
+
worker sends each poll's activations as one packed batch (float32 `bytes`
|
|
51
|
+
plus per-field lists) instead of one dict per record with the tensor as a
|
|
52
|
+
nested Python list, and the driver routes each drain in one executor call
|
|
53
|
+
instead of one per record. In a CPU microbenchmark at a 3,584-wide hidden
|
|
54
|
+
size, transport fell from about 350 µs to under 15 µs per record. Probes see
|
|
55
|
+
the same records in the same order; aborts are sent after the drain is
|
|
56
|
+
routed.
|
|
57
|
+
- vLLM adapter: forward hooks skip (layer, tensor type) pairs no request
|
|
58
|
+
captures with one set lookup, and position matching runs once per step
|
|
59
|
+
instead of once per hooked layer; the captured tensor is only extracted when
|
|
60
|
+
something matched. Hook-path CPU time per step fell by about a third in a
|
|
61
|
+
microbenchmark (16 requests x 8 probes, 28 layers).
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- vLLM adapter: `ActivationRecord.timestamp` is now the capture time, as
|
|
66
|
+
documented, on every path. On the cross-process polling path the record was
|
|
67
|
+
rebuilt in the driver with a fresh timestamp, so a probe (or a latency
|
|
68
|
+
measurement) saw the time of routing, not of capture, and couldn't tell how
|
|
69
|
+
stale an activation was. The worker now stamps each capture when its hook
|
|
70
|
+
runs and the packed batch carries it to the driver.
|
|
71
|
+
- vLLM adapter: with `OverflowPolicy.BLOCK` on the cross-process polling path,
|
|
72
|
+
async probes ran one at a time instead of in parallel. Each drained backlog
|
|
73
|
+
was routed one request at a time, so `route()` waited on one request's full
|
|
74
|
+
queue while every other request's worker sat idle. Drained records are now
|
|
75
|
+
interleaved round-robin across requests, keeping each request's forward-pass
|
|
76
|
+
order. On an A100 (Qwen2.5-7B, 16 requests, 76 ms probe) the old order made
|
|
77
|
+
`block` about 13x slower than the probe's own pace. The docs now also say
|
|
78
|
+
that on this path `block` delays results rather than slowing the engine.
|
|
79
|
+
- vLLM adapter: activations could be silently dropped under concurrency on the
|
|
80
|
+
polling path (engine core in a subprocess, the vLLM 0.28 default). One
|
|
81
|
+
request's drain of the shared worker buffer could still be routing another
|
|
82
|
+
request's records when that request received its final output and ended, so
|
|
83
|
+
those records were discarded as late. A request that finishes in the step that
|
|
84
|
+
captured its only extraction point (e.g. `prompt[-1]` with one new token) lost
|
|
85
|
+
its capture entirely; longer generations could lose their last activations.
|
|
86
|
+
Drains are now serialised, so every record captured up to a request's final
|
|
87
|
+
step is routed before the request ends.
|
|
88
|
+
|
|
89
|
+
## [0.1.0] - 2026-10-02
|
|
90
|
+
|
|
91
|
+
The first public release of **Undercurrent**, Wrynx's activation-probing
|
|
92
|
+
platform for LLM inference. Earlier versions were internal development builds
|
|
93
|
+
and were never published.
|
|
94
|
+
|
|
95
|
+
Planned work that is not part of this release (an OpenAI-compatible
|
|
96
|
+
`undercurrent serve` server, Hugging Face Hub integration, a published
|
|
97
|
+
container image, and more) is tracked in the
|
|
98
|
+
[roadmap](https://github.com/wrynx/undercurrent/blob/main/ROADMAP.md).
|
|
99
|
+
|
|
100
|
+
### Added
|
|
101
|
+
|
|
102
|
+
- **One installable package.** `pip install undercurrent` installs the
|
|
103
|
+
`undercurrent` package for Python 3.10+, with the Hugging Face
|
|
104
|
+
`transformers` backend in the base install. vLLM is never installed by
|
|
105
|
+
default. Tested on Python 3.10–3.13 with torch 2.1+ and transformers
|
|
106
|
+
4.40–5.x; see [Compatibility](https://wrynx.github.io/undercurrent/compatibility/).
|
|
107
|
+
- **`ProbedModel`**, the high-level API (`undercurrent.ProbedModel`):
|
|
108
|
+
`ProbedModel.from_pretrained(model, spec=..., probes=...)`, then
|
|
109
|
+
`generate()`, which returns a `GenerationOutput` with the text and each
|
|
110
|
+
extraction point's `ProbeResult`. Backends `"hf"` (default) and `"vllm"`.
|
|
111
|
+
Pass `on_result=callback` for a simple result hook alongside the sink API.
|
|
112
|
+
- **Extraction-point specs** (`undercurrent.spec`). Declare what to capture
|
|
113
|
+
in YAML (`load_yaml_file`, `parse_yaml`, `parse_dict`) or construct
|
|
114
|
+
`ExtractionPoint`s in Python. Specs are validated by pydantic models and
|
|
115
|
+
round-trip back to YAML (`to_yaml`).
|
|
116
|
+
- Tensor types `residual_stream`, `attn_out`, `mlp_out` and `final_norm`,
|
|
117
|
+
selected per layer.
|
|
118
|
+
- A position-selector grammar: absolute indices (`5`), prompt and generated
|
|
119
|
+
indices (`prompt[-1]`, `generated[0]`), every generated token
|
|
120
|
+
(`generated[*]`), generated slices (`generated[5:]`, `generated[2:8]`),
|
|
121
|
+
and offsets (`prompt[-1]+1`). Malformed selectors raise
|
|
122
|
+
`PositionSyntaxError` with the accepted forms.
|
|
123
|
+
- `until` conditions for continuous selectors (`generation_end`,
|
|
124
|
+
`fixed_count`, `stop_token`).
|
|
125
|
+
- Example specs in `examples/specs/`.
|
|
126
|
+
- **Probe interface** (`undercurrent.core`). Probes implement the lifecycle
|
|
127
|
+
`spawn` → `on_start` → `on_activation`* → `on_end` and return
|
|
128
|
+
`ProbeSignal`s and a final `ProbeResult`. There are two probe kinds,
|
|
129
|
+
`single_shot` and `trajectory`. A fresh instance is spawned for each
|
|
130
|
+
request and extraction point, and probe classes that declare mutable
|
|
131
|
+
class-level attributes are rejected when the class is defined, so instances
|
|
132
|
+
can't share state across requests. Reference probes are in
|
|
133
|
+
`undercurrent.core.examples`: `MLPClassifierProbe` (single-shot) and
|
|
134
|
+
`TrajectoryScoreProbe` (trajectory).
|
|
135
|
+
- **Function probes and a probe registry.** Decorate a plain function
|
|
136
|
+
`(record) -> float | bool | ProbeSignal | None` with `@probe("name", ...)`
|
|
137
|
+
for stateless single-shot probes, and register probe classes by name with
|
|
138
|
+
`@register_probe("name")`. Third-party packages can expose probes through
|
|
139
|
+
the `undercurrent.probes` entry-point group.
|
|
140
|
+
- **Router** (`undercurrent.router`). Dispatches activation records to
|
|
141
|
+
per-request probe instances. Each extraction point picks an execution mode:
|
|
142
|
+
- `inline`: the probe runs on the generation path and its signal is
|
|
143
|
+
returned to the adapter, so it can abort generation.
|
|
144
|
+
- `async`: the probe runs out of band and doesn't block the response. Each
|
|
145
|
+
binding has a bounded queue (`DEFAULT_QUEUE_DEPTH`), served by a shared
|
|
146
|
+
worker pool, with a configurable `OverflowPolicy` (`drop_oldest`,
|
|
147
|
+
`drop_newest` or `block`).
|
|
148
|
+
- **Interventions.** An inline probe can return `ProbeAction.ABORT` to stop
|
|
149
|
+
generation. With the `block_until_signal` intervention policy, the router
|
|
150
|
+
waits up to `timeout_ms` for the probe, then applies `on_timeout`
|
|
151
|
+
(`continue` or `abort`). A circuit breaker downgrades an extraction point
|
|
152
|
+
to observe-only after `circuit_breaker_threshold` consecutive timeouts or
|
|
153
|
+
probe errors, so a failing probe can't keep stalling generation.
|
|
154
|
+
- **Metrics.** Per-binding queue depth, drops, activation latency and probe
|
|
155
|
+
errors go to a pluggable `MetricsSink`. `InMemoryMetricsRegistry` provides
|
|
156
|
+
snapshots.
|
|
157
|
+
- **Observation sinks** (`undercurrent.sinks`) for async probe output:
|
|
158
|
+
- `FileLogSink` writes NDJSON.
|
|
159
|
+
- `WebhookLogSink` POSTs JSON from a background thread, so a slow endpoint
|
|
160
|
+
never adds latency to the caller. Failed deliveries are retried with
|
|
161
|
+
exponential backoff, records that exhaust their retries are
|
|
162
|
+
dead-lettered to a file, and the send queue is bounded.
|
|
163
|
+
- A `LogSink` base class for writing your own sink.
|
|
164
|
+
- **Sink redaction.** `redact_keys()`, `drop_keys()` and `chain()` control
|
|
165
|
+
what a sink writes. `WebhookLogSink` redacts prompt text by default.
|
|
166
|
+
- **Router context managers.** `Router` and `router.request(...)` can be used
|
|
167
|
+
in `with` blocks so requests end and workers shut down cleanly.
|
|
168
|
+
- **Hugging Face adapter** (`undercurrent.adapters.hf`, in the base install).
|
|
169
|
+
`HFEngineAdapter` captures activations from `transformers` models with
|
|
170
|
+
forward hooks during `generate()`, and aborts generation through a
|
|
171
|
+
`StoppingCriteria` (`ProbingStoppingCriteria`).
|
|
172
|
+
- **vLLM adapter** (`undercurrent.adapters.vllm`). Install Undercurrent into
|
|
173
|
+
your existing vLLM environment or image (the recommended production path),
|
|
174
|
+
or use the `undercurrent[vllm]` extra for a vLLM from the tested range.
|
|
175
|
+
- Capture is aware of continuous batching: a worker extension installs the
|
|
176
|
+
hooks, and `SeqIdMapper` translates batch rows to
|
|
177
|
+
`(request_id, token_pos)`. Abort signals are wired back to the engine.
|
|
178
|
+
- A `vllm.general_plugins` entry point registers the worker extension.
|
|
179
|
+
- Constructing the adapter checks the installed vLLM version against the
|
|
180
|
+
supported range and raises `VLLMAdapterLimitationError` with the
|
|
181
|
+
installed version and the supported range. Set
|
|
182
|
+
`UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1` to downgrade the error to a
|
|
183
|
+
warning. See
|
|
184
|
+
[docs/compatibility.md](https://github.com/wrynx/undercurrent/blob/main/docs/compatibility.md).
|
|
185
|
+
- Tensor and pipeline parallelism: results from every worker are merged,
|
|
186
|
+
and events that tensor-parallel ranks duplicate are deduplicated.
|
|
187
|
+
Multi-worker topologies are refused unless you pass
|
|
188
|
+
`allow_unsupported_executor=True`, because cross-rank ordering under
|
|
189
|
+
pipeline parallelism and Ray/distributed executors haven't been
|
|
190
|
+
validated. See the
|
|
191
|
+
[tensor and pipeline parallelism notes](https://wrynx.github.io/undercurrent/internals/vllm-parallelism/).
|
|
192
|
+
- **Command-line tool** `undercurrent`: `inspect-model` lists a model's
|
|
193
|
+
hookable layers and supported tensor types, `validate` checks spec files,
|
|
194
|
+
and `schema` prints the spec JSON Schema (also in
|
|
195
|
+
`schema/probe-spec.schema.json` and `undercurrent.spec.json_schema()`).
|
|
196
|
+
- **Curated top-level API.** The common names import from `undercurrent`
|
|
197
|
+
directly; see [API stability](https://wrynx.github.io/undercurrent/api-stability/)
|
|
198
|
+
for what is public. `load_spec()`
|
|
199
|
+
loads a spec from a path, YAML string or dict. The package ships `py.typed`.
|
|
200
|
+
- **Error hierarchy.** Errors Undercurrent raises on purpose derive from
|
|
201
|
+
`ProbingError` (and the matching built-in type), with messages that say
|
|
202
|
+
how to fix the problem.
|
|
203
|
+
- **Clear errors for missing backends.** Using an adapter whose backend isn't
|
|
204
|
+
installed raises `MissingDependencyError`, which says how to install it.
|
|
205
|
+
- **Examples** (in the repository, not installed with the package, and covered
|
|
206
|
+
by the test suite):
|
|
207
|
+
- [`examples/content_safety/`](https://github.com/wrynx/undercurrent/tree/main/examples/content_safety):
|
|
208
|
+
a **demo** content-safety probe in both `single_shot` and `trajectory`
|
|
209
|
+
variants, with YAML specs, a dummy-checkpoint maker, a vLLM pipeline
|
|
210
|
+
script and a Dockerfile. It is a demonstration of the probe API, not a
|
|
211
|
+
trained safety classifier.
|
|
212
|
+
- [`examples/openai_server/`](https://github.com/wrynx/undercurrent/tree/main/examples/openai_server):
|
|
213
|
+
a reference OpenAI-compatible wire format for probe verdicts (completion
|
|
214
|
+
bodies and SSE chunks) and a reference HTTP server built on it.
|
|
215
|
+
- **Probe-training example** (`examples/train_probe/`) and Colab notebooks
|
|
216
|
+
(`notebooks/quickstart.ipynb`, `notebooks/train_probe.ipynb`).
|
|
217
|
+
|
|
218
|
+
### Fixed
|
|
219
|
+
|
|
220
|
+
- **vLLM `residual_stream` on fused-residual layers.** On Llama-family models
|
|
221
|
+
(and the other vLLM architectures whose decoder layer returns
|
|
222
|
+
`(hidden_states, residual)`), a `residual_stream` extraction point captured
|
|
223
|
+
the layer's MLP output. It now captures `hidden_states + residual`, the
|
|
224
|
+
residual stream after the layer, matching the HF backend. Decoder-layer
|
|
225
|
+
outputs the adapter can't classify raise `VLLMAdapterLimitationError`
|
|
226
|
+
instead of being captured silently. Verified on GPU (NVIDIA L4, vLLM 0.28.0)
|
|
227
|
+
by `tests/adapters/vllm/test_residual_stream_gpu.py`, which compares the
|
|
228
|
+
vLLM and HF captures token by token.
|
|
229
|
+
|
|
230
|
+
### Known issues
|
|
231
|
+
|
|
232
|
+
See [Known issues (v0.1)](https://github.com/wrynx/undercurrent/blob/main/docs/compatibility.md#known-issues-v01)
|
|
233
|
+
for details and workarounds.
|
|
234
|
+
|
|
235
|
+
- **No GPU CI.** The GPU tests run by hand with `scripts/gpu_check.sh` before
|
|
236
|
+
each release. 0.1.0 passed on an NVIDIA L4 with vLLM 0.28.0, torch 2.13.0
|
|
237
|
+
and CUDA 13.0; other GPUs and multi-GPU topologies haven't been run.
|
|
238
|
+
- **Only vLLM 0.28 is supported** (`vllm>=0.28,<0.29`), and vLLM 0.30 is
|
|
239
|
+
already out. `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1` lets you try a newer
|
|
240
|
+
vLLM; the range widens only after a GPU validation run.
|
|
241
|
+
|
|
242
|
+
### Security
|
|
243
|
+
|
|
244
|
+
- Vulnerabilities can be reported privately to security@wrynx.com or through
|
|
245
|
+
GitHub private vulnerability reporting. See
|
|
246
|
+
[SECURITY.md](https://github.com/wrynx/undercurrent/blob/main/SECURITY.md).
|
|
247
|
+
- The content-safety example loads classifier checkpoints only with
|
|
248
|
+
`torch.load(..., weights_only=True)`, accepts only plain tensor
|
|
249
|
+
`state_dict`s, and refuses to load on torch older than 2.6 (the first
|
|
250
|
+
release with a fix for CVE-2025-32434).
|
|
251
|
+
|
|
252
|
+
[Unreleased]: https://github.com/wrynx/undercurrent/compare/v0.2.0...HEAD
|
|
253
|
+
[0.2.0]: https://github.com/wrynx/undercurrent/compare/v0.1.0...v0.2.0
|
|
254
|
+
[0.1.0]: https://github.com/wrynx/undercurrent/releases/tag/v0.1.0
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: undercurrent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Undercurrent: production-grade LLM activation probing and intervention. Works with Hugging Face Transformers and vLLM.
|
|
5
|
+
Project-URL: Homepage, https://github.com/wrynx/undercurrent
|
|
6
|
+
Project-URL: Source, https://github.com/wrynx/undercurrent
|
|
7
|
+
Project-URL: Issues, https://github.com/wrynx/undercurrent/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/wrynx/undercurrent/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Documentation, https://wrynx.github.io/undercurrent/
|
|
10
|
+
Author: Wrynx
|
|
11
|
+
Maintainer: Wrynx
|
|
12
|
+
License-Expression: Apache-2.0
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
License-File: NOTICE
|
|
15
|
+
Keywords: activation-probing,ai-safety,guardrails,interpretability,llm,llm-inference,mechanistic-interpretability,probes,transformers,undercurrent,vllm
|
|
16
|
+
Classifier: Development Status :: 3 - Alpha
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: Science/Research
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Requires-Dist: numpy>=1.23
|
|
31
|
+
Requires-Dist: packaging>=22
|
|
32
|
+
Requires-Dist: pydantic<3,>=2
|
|
33
|
+
Requires-Dist: pyyaml<7,>=6
|
|
34
|
+
Requires-Dist: torch>=2.1
|
|
35
|
+
Requires-Dist: transformers<6,>=4.40
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: ipykernel>=6; extra == 'dev'
|
|
38
|
+
Requires-Dist: jsonschema<5,>=4.18; extra == 'dev'
|
|
39
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
40
|
+
Requires-Dist: nbclient>=0.10; extra == 'dev'
|
|
41
|
+
Requires-Dist: nbformat>=5.10; extra == 'dev'
|
|
42
|
+
Requires-Dist: pre-commit>=4; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
46
|
+
Requires-Dist: ruff<0.18,>=0.16; extra == 'dev'
|
|
47
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
48
|
+
Provides-Extra: docs
|
|
49
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
50
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
|
|
51
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
52
|
+
Requires-Dist: mktestdocs; extra == 'docs'
|
|
53
|
+
Requires-Dist: pymdown-extensions; extra == 'docs'
|
|
54
|
+
Provides-Extra: vllm
|
|
55
|
+
Requires-Dist: vllm<0.29,>=0.28; extra == 'vllm'
|
|
56
|
+
Description-Content-Type: text/markdown
|
|
57
|
+
|
|
58
|
+
<p align="center">
|
|
59
|
+
<picture>
|
|
60
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/wrynx/undercurrent/main/docs/assets/brand/undercurrent-logo-dark.png">
|
|
61
|
+
<img alt="Undercurrent" src="https://raw.githubusercontent.com/wrynx/undercurrent/main/docs/assets/brand/undercurrent-logo.png" width="440">
|
|
62
|
+
</picture>
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<p align="center"><em>See the undercurrent before it surfaces.</em></p>
|
|
66
|
+
|
|
67
|
+
[](https://pypi.org/project/undercurrent/)
|
|
68
|
+
[](https://pypi.org/project/undercurrent/)
|
|
69
|
+
[](https://github.com/wrynx/undercurrent/actions/workflows/ci.yml)
|
|
70
|
+
[](https://github.com/wrynx/undercurrent/blob/main/LICENSE)
|
|
71
|
+
[](https://wrynx.github.io/undercurrent/)
|
|
72
|
+
[](https://colab.research.google.com/github/wrynx/undercurrent/blob/main/notebooks/quickstart.ipynb)
|
|
73
|
+
|
|
74
|
+
<sub>An open-source project by [Wrynx](https://github.com/wrynx).</sub>
|
|
75
|
+
|
|
76
|
+
Undercurrent is production-grade activation probing for live LLM inference.
|
|
77
|
+
It runs your probes on a model's internal activations *while it generates*,
|
|
78
|
+
so a probe can stop a generation mid-flight, or observe asynchronously
|
|
79
|
+
without adding latency to the response.
|
|
80
|
+
|
|
81
|
+
## Why Undercurrent
|
|
82
|
+
|
|
83
|
+
- **Declarative YAML specs.** A few lines say which tensor to capture, at
|
|
84
|
+
which layers and token positions, and which probe receives it. No model
|
|
85
|
+
surgery, no forked serving code.
|
|
86
|
+
- **Inline abort mid-generation.** An inline probe runs on the generation path
|
|
87
|
+
and can stop a request before the next token is produced.
|
|
88
|
+
- **Production-ready async execution.** Observe-only probes run off the hot
|
|
89
|
+
path on bounded per-binding queues with overflow policies, and report to
|
|
90
|
+
metrics and to file or webhook sinks (with retries, dead-lettering and
|
|
91
|
+
redaction).
|
|
92
|
+
- **Runs where your model runs.** Inside your existing vLLM deployment, with
|
|
93
|
+
continuous batching (the vLLM adapter is experimental, see
|
|
94
|
+
[Deploy with vLLM](https://wrynx.github.io/undercurrent/guides/vllm-deployment/)),
|
|
95
|
+
and with Hugging Face Transformers on a laptop CPU.
|
|
96
|
+
|
|
97
|
+
> **Known issues (v0.2):** the vLLM adapter is experimental: its GPU tests
|
|
98
|
+
> pass on an NVIDIA L4 with vLLM 0.28.0, run by hand (there's no GPU CI yet),
|
|
99
|
+
> and only vLLM 0.28 is supported. Fixed in 0.1.0: on vLLM, `residual_stream`
|
|
100
|
+
> on Llama-family (fused-residual) models is captured as
|
|
101
|
+
> `hidden_states + residual`, the same residual stream as the HF backend
|
|
102
|
+
> (GPU-verified). See
|
|
103
|
+
> [Known issues](https://wrynx.github.io/undercurrent/compatibility/#known-issues-v02).
|
|
104
|
+
|
|
105
|
+
## How it works
|
|
106
|
+
|
|
107
|
+
In short: an engine adapter (Hugging Face or vLLM) captures the activations your
|
|
108
|
+
YAML spec asks for, and the router hands them to your probes, which either
|
|
109
|
+
abort the generation (inline) or report to sinks and metrics (async).
|
|
110
|
+
|
|
111
|
+
```mermaid
|
|
112
|
+
flowchart LR
|
|
113
|
+
spec["YAML spec"] --> adapter
|
|
114
|
+
subgraph engine["Inference engine (HF Transformers or vLLM)"]
|
|
115
|
+
model["LLM generating tokens"] -- forward hooks --> adapter["Engine adapter"]
|
|
116
|
+
end
|
|
117
|
+
adapter -- ActivationRecords --> router["Router"]
|
|
118
|
+
router -- inline --> inline["Inline probes"]
|
|
119
|
+
inline -- ABORT --> model
|
|
120
|
+
router -- async, bounded queues --> async["Async probes"]
|
|
121
|
+
async --> sinks["Sinks: file / webhook / metrics"]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Install
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install undercurrent
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Python 3.10+. The Hugging Face Transformers backend is included; there are no
|
|
131
|
+
extras to choose. For vLLM, install Undercurrent into your existing vLLM
|
|
132
|
+
environment or image, or use `pip install "undercurrent[vllm]"` to get a vLLM
|
|
133
|
+
from the tested range (currently `vllm>=0.28,<0.29`). The adapter checks the
|
|
134
|
+
installed vLLM version at runtime; set `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1`
|
|
135
|
+
to try an untested vLLM anyway.
|
|
136
|
+
|
|
137
|
+
Supported versions, tested combinations and known issues are on the
|
|
138
|
+
[Compatibility](https://wrynx.github.io/undercurrent/compatibility/) page.
|
|
139
|
+
|
|
140
|
+
## Quickstart
|
|
141
|
+
|
|
142
|
+
Attach a probe to GPT-2 and read its result. This runs on CPU:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from undercurrent.core import ActivationRecord, probe
|
|
146
|
+
from undercurrent.model import ProbedModel
|
|
147
|
+
|
|
148
|
+
SPEC = """
|
|
149
|
+
version: "1"
|
|
150
|
+
extraction_points:
|
|
151
|
+
- name: last_prompt_token
|
|
152
|
+
layers: 6
|
|
153
|
+
tensor_type: residual_stream
|
|
154
|
+
position: "prompt[-1]"
|
|
155
|
+
probe_type: norm_gate
|
|
156
|
+
probe_kind: single_shot
|
|
157
|
+
execution_mode: inline
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@probe("norm_gate", threshold=500.0)
|
|
162
|
+
def norm_gate(record: ActivationRecord) -> float:
|
|
163
|
+
"""L2 norm of the residual stream at this token."""
|
|
164
|
+
return float(record.tensor.norm())
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
with ProbedModel.from_pretrained("openai-community/gpt2", spec=SPEC, probes={"norm_gate": norm_gate}) as model:
|
|
168
|
+
out = model.generate("The quick brown fox", max_new_tokens=20, temperature=0)
|
|
169
|
+
|
|
170
|
+
print(repr(out.text))
|
|
171
|
+
print(out.probe_results["last_prompt_token"].verdict)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A score at or above `threshold` would abort the generation. The
|
|
175
|
+
[Quickstart](https://wrynx.github.io/undercurrent/getting-started/quickstart/)
|
|
176
|
+
continues with a trajectory probe that stops a generation part-way through,
|
|
177
|
+
and an async probe that logs to a file without touching the response.
|
|
178
|
+
|
|
179
|
+
## How it compares
|
|
180
|
+
|
|
181
|
+
| | Undercurrent | TransformerLens / pyvene / baukit | nnsight (+ NDIF) | Llama Guard / NeMo Guardrails |
|
|
182
|
+
|---|---|---|---|---|
|
|
183
|
+
| Purpose | Production activation probing | Interpretability research | Interpretability research, local or remote | Text-level safety classification and rails |
|
|
184
|
+
| Runs inside vLLM serving | partial (single worker validated) | ✗ | ✓ | n/a (separate model / wraps the LLM call) |
|
|
185
|
+
| Abort a request mid-generation from a probe | ✓ | partial (edit activations; no built-in abort) | partial (edit activations; no built-in abort) | Llama Guard: n/a (application decides); NeMo: partial (streaming rails end the stream, in text chunks) |
|
|
186
|
+
| Declarative spec | ✓ YAML | pyvene: partial (dict configs); others ✗ | ✗ | NeMo: ✓ (YAML + Colang) |
|
|
187
|
+
| License | Apache-2.0 | MIT / Apache-2.0 / MIT | MIT | Llama 4 Community License / Apache-2.0 |
|
|
188
|
+
|
|
189
|
+
Last reviewed: 2026-10-01. Footnotes, sources and when to pick something else:
|
|
190
|
+
[full comparison](https://wrynx.github.io/undercurrent/comparison/).
|
|
191
|
+
|
|
192
|
+
## Documentation
|
|
193
|
+
|
|
194
|
+
- [Quickstart](https://wrynx.github.io/undercurrent/getting-started/quickstart/): your first probe on GPT-2, in five minutes.
|
|
195
|
+
- [Concepts](https://wrynx.github.io/undercurrent/concepts/architecture/): extraction points, position selectors, probes, inline vs async.
|
|
196
|
+
- [Guides](https://wrynx.github.io/undercurrent/guides/custom-probe/): write, test and package your own probe.
|
|
197
|
+
- [Production](https://wrynx.github.io/undercurrent/guides/vllm-deployment/): vLLM deployment, embedding the router, backpressure, sinks and metrics.
|
|
198
|
+
- [Examples](https://wrynx.github.io/undercurrent/examples/): complete, runnable projects.
|
|
199
|
+
- [API reference](https://wrynx.github.io/undercurrent/reference/): every public class and function, and the CLI.
|
|
200
|
+
- [Intended use and limitations](https://wrynx.github.io/undercurrent/about/intended-use/) and [privacy and data handling](https://wrynx.github.io/undercurrent/about/privacy/): what Undercurrent is and isn't for, and what data it captures and writes.
|
|
201
|
+
- [Colab notebook](https://colab.research.google.com/github/wrynx/undercurrent/blob/main/notebooks/quickstart.ipynb): the quickstart in your browser.
|
|
202
|
+
|
|
203
|
+
[`examples/content_safety/`](https://github.com/wrynx/undercurrent/tree/main/examples/content_safety)
|
|
204
|
+
is a demo of the wiring (probes, specs and a vLLM pipeline), not a safety
|
|
205
|
+
product. Its probes use random-initialised or dummy weights.
|
|
206
|
+
|
|
207
|
+
## Project status
|
|
208
|
+
|
|
209
|
+
Undercurrent is on 0.x: the API may change between minor releases. The
|
|
210
|
+
[API stability policy](https://github.com/wrynx/undercurrent/blob/main/docs/api-stability.md)
|
|
211
|
+
says what is public, what is experimental, and how deprecations work. Planned
|
|
212
|
+
work is in the [roadmap](https://github.com/wrynx/undercurrent/blob/main/ROADMAP.md).
|
|
213
|
+
|
|
214
|
+
## Contributing and community
|
|
215
|
+
|
|
216
|
+
- [Contributing guide](https://github.com/wrynx/undercurrent/blob/main/CONTRIBUTING.md)
|
|
217
|
+
- [Security policy](https://github.com/wrynx/undercurrent/blob/main/SECURITY.md): report vulnerabilities privately to security@wrynx.com.
|
|
218
|
+
- [Code of Conduct](https://github.com/wrynx/undercurrent/blob/main/CODE_OF_CONDUCT.md)
|
|
219
|
+
|
|
220
|
+
## Citation
|
|
221
|
+
|
|
222
|
+
If you use Undercurrent in research, please cite it using
|
|
223
|
+
[`CITATION.cff`](https://github.com/wrynx/undercurrent/blob/main/CITATION.cff)
|
|
224
|
+
(GitHub's "Cite this repository" button reads it).
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
Apache-2.0. See [LICENSE](https://github.com/wrynx/undercurrent/blob/main/LICENSE)
|
|
229
|
+
and [NOTICE](https://github.com/wrynx/undercurrent/blob/main/NOTICE). Copyright Wrynx.
|