undercurrent 0.1.0__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.1.0 → undercurrent-0.2.0}/CHANGELOG.md +70 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/PKG-INFO +4 -4
- {undercurrent-0.1.0 → undercurrent-0.2.0}/README.md +2 -2
- {undercurrent-0.1.0 → undercurrent-0.2.0}/benchmarks/README.md +2 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/benchmarks/run.py +24 -2
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/intended-use.md +2 -2
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/compatibility.md +15 -4
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/execution-modes.md +1 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/interventions.md +1 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/guides/custom-probe.md +1 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/guides/vllm-deployment.md +52 -10
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/internals/vllm-adapter.md +76 -11
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/internals/vllm-parallelism.md +1 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/async-execution.md +19 -5
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/embedding.md +1 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/pyproject.toml +1 -1
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/_version.py +2 -2
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/activation_batch.py +90 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/adapter.py +112 -20
- undercurrent-0.2.0/src/undercurrent/adapters/vllm/graph_capture.py +133 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/plugin.py +27 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/worker_extension.py +322 -97
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/overflow.py +3 -1
- undercurrent-0.2.0/tests/adapters/vllm/test_activation_batch.py +92 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_adapter_contract.py +255 -0
- undercurrent-0.2.0/tests/adapters/vllm/test_graph_capture.py +216 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_worker_extension_bookkeeping.py +16 -14
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_worker_extension_capture.py +12 -15
- undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_staging.py +279 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/.gitignore +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/LICENSE +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/NOTICE +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/README.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_adapter_hf.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_adapter_vllm.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_content_safety.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_core.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_logging.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_router.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_spec.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/vllm_tp_pp_activation_extraction_notes.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/changelog.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/contributing.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/privacy.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/api-stability.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/social-preview.png +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-icon.svg +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo-dark.png +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo-dark.svg +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo.png +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo.svg +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-mark-dark.svg +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-mark.svg +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/benchmarks.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/comparison.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/architecture.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/extraction-points.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/position-selectors.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/probes.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/examples.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/getting-started/installation.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/getting-started/quickstart.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/guides/observation-sinks.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/index.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/intervention-policies.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/metrics.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/adapters.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/cli.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/core.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/index.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/model.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/router.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/sinks.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/spec.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/stylesheets/brand.css +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/Dockerfile +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/README.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/custom_mlp.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/models.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/single_token.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/spec_binding.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/tensors.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/trajectory.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_gpt2.yaml +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_llama.yaml +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/custom_mlp_llama31_8b.yaml +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/make_dummy_mlp_checkpoint.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/run_vllm_pipeline.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/function_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/openai_server/README.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/openai_server/response_schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/openai_server/serve_llama_mlp_pipeline.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/quickstart.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/classic_single_token.yaml +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/continuous_trajectory.yaml +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/offset_dual_position.yaml +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/sliced_range.yaml +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/README.md +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/collector.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/data_sources.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/linear_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/requirements.txt +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/train_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/use_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/notebooks/quickstart.ipynb +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/notebooks/train_probe.ipynb +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/schema/probe-spec.schema.json +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/scripts/generate_schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/scripts/gpu_check.sh +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/scripts/smoke_test_wheel.sh +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/__main__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/_optional.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/base.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/adapter.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/errors.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/introspect.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/stopping_criteria.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/introspection.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/seq_mapper.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/support.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/version_check.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/_errors.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/inspect_model.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/validate.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/activation.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/context.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/_util.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/mlp_classifier.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/trajectory_score.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/function_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/registry.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/result.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/signal.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/errors.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/model.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/py.typed +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/binding.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/errors.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/metrics.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/request.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/router.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/file_sink.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/records.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/redaction.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/serialize.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/sink.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/webhook_sink.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/wiring.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/activation_record.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/errors.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/json_schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/parser.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/position.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/resolved.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/serialize.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/_helpers.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/test_adapter_contract.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/test_intervention_e2e.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/test_introspect.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/test_optional.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/_helpers.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_adapter_shutdown.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_integration_vllm.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_plugin.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_residual_stream_capture.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_residual_stream_gpu.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_seq_mapper.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_support.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_version_check.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/benchmarks/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/benchmarks/test_harness.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_inspect_model.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_main.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_validate.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_function_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_probe_isolation.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_probe_result_always_returned.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_registry.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_trajectory_score_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/docs/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/docs/test_doc_snippets.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/e2e/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/e2e/test_hf_pipeline_cpu.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_custom_mlp_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_isolation.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_live_llama_integration.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_single_token_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_spec_binding.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_trajectory_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/test_response_schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/test_server_cli.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/test_function_probe_example.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/train_probe/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/train_probe/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/train_probe/test_train_probe.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/notebooks/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/notebooks/test_notebooks.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/_helpers.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_context_managers.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_dispatch.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_end_request.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_intervention.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_isolation.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_load.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_metrics.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_overflow.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_probe_args.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_probe_registry.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_registration.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_shutdown.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_stress_overflow.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/_helpers.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/conftest.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_file_sink.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_redaction.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_router_integration.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_webhook_sink.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/__init__.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_activation_record.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_intervention.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_json_schema.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_parser.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_position.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_roundtrip.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_vocab.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_errors.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_gpu_check.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_import_light.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_markers.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_metadata.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_model.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_model_vllm.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_public_api.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_release_workflow.py +0 -0
- {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_version.py +0 -0
|
@@ -18,6 +18,74 @@ how a release is cut.
|
|
|
18
18
|
|
|
19
19
|
## [Unreleased]
|
|
20
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
|
+
|
|
21
89
|
## [0.1.0] - 2026-10-02
|
|
22
90
|
|
|
23
91
|
The first public release of **Undercurrent**, Wrynx's activation-probing
|
|
@@ -181,5 +249,6 @@ for details and workarounds.
|
|
|
181
249
|
`state_dict`s, and refuses to load on torch older than 2.6 (the first
|
|
182
250
|
release with a fix for CVE-2025-32434).
|
|
183
251
|
|
|
184
|
-
[Unreleased]: https://github.com/wrynx/undercurrent/compare/v0.
|
|
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
|
|
185
254
|
[0.1.0]: https://github.com/wrynx/undercurrent/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: undercurrent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Undercurrent: production-grade LLM activation probing and intervention. Works with Hugging Face Transformers and vLLM.
|
|
5
5
|
Project-URL: Homepage, https://github.com/wrynx/undercurrent
|
|
6
6
|
Project-URL: Source, https://github.com/wrynx/undercurrent
|
|
@@ -43,7 +43,7 @@ Requires-Dist: pre-commit>=4; extra == 'dev'
|
|
|
43
43
|
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
44
44
|
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
|
|
45
45
|
Requires-Dist: pytest>=7; extra == 'dev'
|
|
46
|
-
Requires-Dist: ruff<0.
|
|
46
|
+
Requires-Dist: ruff<0.18,>=0.16; extra == 'dev'
|
|
47
47
|
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
48
48
|
Provides-Extra: docs
|
|
49
49
|
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
@@ -94,13 +94,13 @@ without adding latency to the response.
|
|
|
94
94
|
[Deploy with vLLM](https://wrynx.github.io/undercurrent/guides/vllm-deployment/)),
|
|
95
95
|
and with Hugging Face Transformers on a laptop CPU.
|
|
96
96
|
|
|
97
|
-
> **Known issues (v0.
|
|
97
|
+
> **Known issues (v0.2):** the vLLM adapter is experimental: its GPU tests
|
|
98
98
|
> pass on an NVIDIA L4 with vLLM 0.28.0, run by hand (there's no GPU CI yet),
|
|
99
99
|
> and only vLLM 0.28 is supported. Fixed in 0.1.0: on vLLM, `residual_stream`
|
|
100
100
|
> on Llama-family (fused-residual) models is captured as
|
|
101
101
|
> `hidden_states + residual`, the same residual stream as the HF backend
|
|
102
102
|
> (GPU-verified). See
|
|
103
|
-
> [Known issues](https://wrynx.github.io/undercurrent/compatibility/#known-issues-
|
|
103
|
+
> [Known issues](https://wrynx.github.io/undercurrent/compatibility/#known-issues-v02).
|
|
104
104
|
|
|
105
105
|
## How it works
|
|
106
106
|
|
|
@@ -37,13 +37,13 @@ without adding latency to the response.
|
|
|
37
37
|
[Deploy with vLLM](https://wrynx.github.io/undercurrent/guides/vllm-deployment/)),
|
|
38
38
|
and with Hugging Face Transformers on a laptop CPU.
|
|
39
39
|
|
|
40
|
-
> **Known issues (v0.
|
|
40
|
+
> **Known issues (v0.2):** the vLLM adapter is experimental: its GPU tests
|
|
41
41
|
> pass on an NVIDIA L4 with vLLM 0.28.0, run by hand (there's no GPU CI yet),
|
|
42
42
|
> and only vLLM 0.28 is supported. Fixed in 0.1.0: on vLLM, `residual_stream`
|
|
43
43
|
> on Llama-family (fused-residual) models is captured as
|
|
44
44
|
> `hidden_states + residual`, the same residual stream as the HF backend
|
|
45
45
|
> (GPU-verified). See
|
|
46
|
-
> [Known issues](https://wrynx.github.io/undercurrent/compatibility/#known-issues-
|
|
46
|
+
> [Known issues](https://wrynx.github.io/undercurrent/compatibility/#known-issues-v02).
|
|
47
47
|
|
|
48
48
|
## How it works
|
|
49
49
|
|
|
@@ -134,6 +134,7 @@ or surprising runs too: the page must show what was measured. Don't commit
|
|
|
134
134
|
| `--dtype {auto,float32,float16,bfloat16}` | `auto` | Model dtype (hf: `from_pretrained`; vllm: engine `dtype`) |
|
|
135
135
|
| `--vllm-arg KEY=VALUE` | none | vllm only, repeatable: engine arg passed to both the baseline `vllm.LLM` and the probed engine. VALUE is parsed as JSON when it can be |
|
|
136
136
|
| `--isolate {auto,process,none}` | `auto` | Run each scenario in its own subprocess (auto: yes for vllm, no for hf) |
|
|
137
|
+
| `--graph-capture` | off | vllm only: run probed scenarios in graph mode (CUDA graphs, no `enforce_eager`), capturing `residual_stream` at the scenario's layers via `load_model(graph_capture_layers=...)`. Passing `--vllm-arg enforce_eager=true` still applies to `baseline`, so run the graph baseline separately |
|
|
137
138
|
|
|
138
139
|
## Scenarios
|
|
139
140
|
|
|
@@ -268,7 +269,7 @@ One JSON object.
|
|
|
268
269
|
| `run_id` | string | The run directory's name |
|
|
269
270
|
| `created_utc` | string | ISO 8601 UTC start time |
|
|
270
271
|
| `cli_argv` | list of strings | The exact command-line arguments (after `run.py`) |
|
|
271
|
-
| `args` | object | Every resolved option (after `--dry-run` and backend defaults), keyed by argparse destination: `backend`, `model`, `num_prompts`, `batch_size`, `max_new_tokens`, `input_len`, `scenarios`, `warmup`, `repeats`, `out`, `dry_run`, `seed`, `probe_delay_ms`, `slow_queue_depth`, `flat_tolerance_pct`, `worker_pool_size`, `device`, `dtype`, `vllm_engine_kwargs`, `isolate` |
|
|
272
|
+
| `args` | object | Every resolved option (after `--dry-run` and backend defaults), keyed by argparse destination: `backend`, `model`, `num_prompts`, `batch_size`, `max_new_tokens`, `input_len`, `scenarios`, `warmup`, `repeats`, `out`, `dry_run`, `seed`, `probe_delay_ms`, `slow_queue_depth`, `flat_tolerance_pct`, `worker_pool_size`, `device`, `dtype`, `vllm_engine_kwargs`, `isolate`, `graph_capture` |
|
|
272
273
|
| `scenarios` | list of strings | Expanded scenario names, in run order |
|
|
273
274
|
| `dry_run` | bool | Whether `--dry-run` was given |
|
|
274
275
|
| `device` | string or null | hf: the torch device; vllm: null |
|
|
@@ -670,15 +670,22 @@ class VLLMRunner:
|
|
|
670
670
|
|
|
671
671
|
from undercurrent.model import ProbedModel
|
|
672
672
|
|
|
673
|
+
spec = build_spec(scenario, self.num_layers)
|
|
674
|
+
engine_kwargs = dict(self.engine_kwargs)
|
|
675
|
+
if self.args.graph_capture:
|
|
676
|
+
# Graph mode: the probed engine runs with CUDA graphs and captures residual_stream
|
|
677
|
+
# through the model's aux outputs at exactly the layers this scenario probes.
|
|
678
|
+
engine_kwargs.pop("enforce_eager", None)
|
|
679
|
+
engine_kwargs["graph_capture_layers"] = sorted({p["layers"] for p in spec["extraction_points"]})
|
|
673
680
|
pm = ProbedModel(
|
|
674
681
|
self.args.model,
|
|
675
682
|
backend="vllm",
|
|
676
|
-
spec=
|
|
683
|
+
spec=spec,
|
|
677
684
|
probes=probes,
|
|
678
685
|
router_kwargs=router_kwargs,
|
|
679
686
|
metrics_sink=sink,
|
|
680
687
|
max_concurrency=self.args.batch_size,
|
|
681
|
-
**
|
|
688
|
+
**engine_kwargs,
|
|
682
689
|
)
|
|
683
690
|
|
|
684
691
|
class Probed(Session):
|
|
@@ -1195,6 +1202,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1195
1202
|
default="auto",
|
|
1196
1203
|
help="run each scenario in its own subprocess (auto: yes for vllm, no for hf)",
|
|
1197
1204
|
)
|
|
1205
|
+
p.add_argument(
|
|
1206
|
+
"--graph-capture",
|
|
1207
|
+
action="store_true",
|
|
1208
|
+
help="vllm only: run probed scenarios in graph mode (CUDA graphs + torch.compile, no enforce_eager), "
|
|
1209
|
+
"capturing residual_stream via load_model(graph_capture_layers=...); the baseline is unaffected",
|
|
1210
|
+
)
|
|
1198
1211
|
p.add_argument("--worker-output", help=argparse.SUPPRESS)
|
|
1199
1212
|
return p
|
|
1200
1213
|
|
|
@@ -1226,6 +1239,8 @@ def resolve_args(args: argparse.Namespace) -> argparse.Namespace:
|
|
|
1226
1239
|
)
|
|
1227
1240
|
if args.vllm_engine_kwargs:
|
|
1228
1241
|
raise HarnessError("--vllm-arg only applies to --backend vllm")
|
|
1242
|
+
if args.graph_capture:
|
|
1243
|
+
raise HarnessError("--graph-capture only applies to --backend vllm")
|
|
1229
1244
|
if args.device is None:
|
|
1230
1245
|
import torch
|
|
1231
1246
|
|
|
@@ -1259,6 +1274,11 @@ def check_vllm_environment() -> None:
|
|
|
1259
1274
|
"--backend vllm needs vLLM, which undercurrent never installs by default. Run the harness in your "
|
|
1260
1275
|
'vLLM environment or image, or `pip install "undercurrent[vllm]"`.'
|
|
1261
1276
|
)
|
|
1277
|
+
# torch.cuda.is_available() below poisons fork() for CUDA in this process,
|
|
1278
|
+
# but doesn't mark CUDA initialised, so vLLM would still fork its engine
|
|
1279
|
+
# core and that child fails with "Cannot re-initialize CUDA in forked
|
|
1280
|
+
# subprocess". Make vLLM spawn it instead (unless the caller chose).
|
|
1281
|
+
os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
|
|
1262
1282
|
import torch
|
|
1263
1283
|
|
|
1264
1284
|
if not torch.cuda.is_available():
|
|
@@ -1303,6 +1323,8 @@ def _child_argv(args: argparse.Namespace, scenario: Scenario, output: str) -> li
|
|
|
1303
1323
|
argv += ["--device", args.device]
|
|
1304
1324
|
if args.worker_pool_size is not None:
|
|
1305
1325
|
argv += ["--worker-pool-size", str(args.worker_pool_size)]
|
|
1326
|
+
if args.graph_capture:
|
|
1327
|
+
argv.append("--graph-capture")
|
|
1306
1328
|
for key, value in args.vllm_engine_kwargs.items():
|
|
1307
1329
|
argv += ["--vllm-arg", f"{key}={json.dumps(value)}"]
|
|
1308
1330
|
return argv
|
|
@@ -98,7 +98,7 @@ What the project does:
|
|
|
98
98
|
What the project does not do:
|
|
99
99
|
|
|
100
100
|
- It doesn't modify activations or weights. There is no steering, ablation or
|
|
101
|
-
"refusal removal" feature, and none is planned for v0.
|
|
101
|
+
"refusal removal" feature, and none is planned for v0.2.
|
|
102
102
|
- It doesn't ship trained probes, jailbreaks or recipes for disabling a
|
|
103
103
|
model's safeguards.
|
|
104
104
|
- It doesn't give access to a model you couldn't already run.
|
|
@@ -133,4 +133,4 @@ would document a model (a "probe card"). At a minimum, include:
|
|
|
133
133
|
Undercurrent and engine versions you tested with.
|
|
134
134
|
|
|
135
135
|
Publishing pretrained probes on the Hugging Face Hub is on the roadmap. It is
|
|
136
|
-
not part of v0.
|
|
136
|
+
not part of v0.2.
|
|
@@ -38,10 +38,11 @@ Only combinations that have actually been run are listed here.
|
|
|
38
38
|
| Python | torch | transformers | vllm | CUDA | Status |
|
|
39
39
|
|--------|-------|--------------|------|------|--------|
|
|
40
40
|
| 3.14.4 | 2.13.0 (`+cu130` build) | 5.16.1 | 0.28.0 | As provided by the vLLM wheel; no GPU present | **Tested locally**: full unit test suite, CPU only. GPU and real vLLM generation tests were skipped. |
|
|
41
|
+
| 3.13.16 | 2.13.0 (`+cu130` build) | 5.21.0 | 0.28.0 | 13.0, NVIDIA L4 (driver 580.82.07) | **GPU-verified, 2026-11-01** (0.2.0, commit `5ded89e`): `scripts/gpu_check.sh` passed, covering every GPU test (vLLM integration, `ProbedModel` with the vLLM backend, and the `residual_stream` HF-vs-vLLM equivalence check) and the vLLM adapter suite against the real vLLM. Run on Google Colab. |
|
|
41
42
|
| 3.13.15 | 2.13.0 (`+cu130` build) | 5.17.0 | 0.28.0 | 13.0, NVIDIA L4 (driver 580.82.07) | **GPU-verified, 2026-10-02**: `scripts/gpu_check.sh` passed, covering every GPU test (vLLM integration, `ProbedModel` with the vLLM backend, and the `residual_stream` HF-vs-vLLM equivalence check) and the vLLM adapter suite against the real vLLM. Run on Google Colab. |
|
|
42
43
|
| 3.10.21 | 2.1.2 (`+cpu` build) | 4.40.2 | not installed | n/a | **The declared lower bounds** (with pydantic 2.0.3, PyYAML 6.0.3, numpy 1.23.5, packaging 22.0): CPU unit test suite passes locally. The CI `floors` job installs the same versions and runs the suite. Tests tied to newer releases skip themselves (the byte-for-byte JSON Schema comparisons need pydantic 2.11+; the content-safety demo needs torch 2.6+ to load checkpoints). |
|
|
43
44
|
|
|
44
|
-
There is no GPU CI for v0.
|
|
45
|
+
There is no GPU CI for v0.2. Instead, before every release a maintainer runs
|
|
45
46
|
`scripts/gpu_check.sh` on a GPU machine with vLLM installed. It runs the GPU
|
|
46
47
|
and real-vLLM tests and prints the combination it ran with, which is then
|
|
47
48
|
added to the table above (see [RELEASING.md](https://github.com/wrynx/undercurrent/blob/main/RELEASING.md)).
|
|
@@ -53,7 +54,7 @@ pip install -e ".[dev]" # from a checkout, into your vLLM environment
|
|
|
53
54
|
scripts/gpu_check.sh
|
|
54
55
|
```
|
|
55
56
|
|
|
56
|
-
## Known issues (v0.
|
|
57
|
+
## Known issues (v0.2)
|
|
57
58
|
|
|
58
59
|
These affect the vLLM adapter only. The Hugging Face backend is not affected.
|
|
59
60
|
|
|
@@ -104,15 +105,25 @@ torch 2.13.0+cu130) with `scripts/gpu_check.sh`.**
|
|
|
104
105
|
|
|
105
106
|
### No GPU CI for the vLLM adapter
|
|
106
107
|
|
|
107
|
-
There is no GPU CI for v0.
|
|
108
|
+
There is no GPU CI for v0.2. The GPU and real-vLLM tests run by hand: a
|
|
108
109
|
maintainer runs
|
|
109
110
|
[`scripts/gpu_check.sh`](https://github.com/wrynx/undercurrent/blob/main/scripts/gpu_check.sh)
|
|
110
111
|
on a GPU machine before each release, and the combination it ran with goes
|
|
111
|
-
into [Tested combinations](#tested-combinations). For 0.
|
|
112
|
+
into [Tested combinations](#tested-combinations). For 0.2.0 that's an NVIDIA
|
|
112
113
|
L4 with vLLM 0.28.0. Other GPUs, larger models and multi-GPU topologies
|
|
113
114
|
haven't been run, and the vLLM adapter is still experimental. You can run the
|
|
114
115
|
check on your own GPU machine too (see above).
|
|
115
116
|
|
|
117
|
+
### Graph-mode `residual_stream` capture is experimental
|
|
118
|
+
|
|
119
|
+
New in 0.2.0, `load_model(graph_capture_layers=[...])` captures
|
|
120
|
+
`residual_stream` without `enforce_eager=True` by reusing vLLM's EAGLE-3
|
|
121
|
+
auxiliary hidden-state outputs. It's limited to `residual_stream` at the layers
|
|
122
|
+
given at load time, on models that implement that interface, and it patches
|
|
123
|
+
more vLLM internals than hook mode does. Every other configuration still needs
|
|
124
|
+
`enforce_eager=True`. See
|
|
125
|
+
[Keep CUDA graphs](guides/vllm-deployment.md#keep-cuda-graphs-graph-mode-residual-capture-experimental).
|
|
126
|
+
|
|
116
127
|
### vLLM 0.29 and 0.30 aren't supported yet
|
|
117
128
|
|
|
118
129
|
The supported range is `vllm>=0.28,<0.29`. vLLM 0.30 is already out, so a
|
|
@@ -138,7 +138,7 @@ Each async extraction point of each request gets its own **binding**:
|
|
|
138
138
|
| --- | --- | --- |
|
|
139
139
|
| `drop_oldest` (default) | Evict the oldest waiting activation, accept the new one | Generation never waits; the probe loses older activations |
|
|
140
140
|
| `drop_newest` | Discard the incoming activation | Generation never waits; the probe loses the latest activations |
|
|
141
|
-
| `block` | `route()` waits until there is room | Nothing is dropped; generation slows down to the probe's pace |
|
|
141
|
+
| `block` | `route()` waits until there is room | Nothing is dropped; generation slows down to the probe's pace (on vLLM's default cross-process path, results arrive late instead; see [Async execution](../production/async-execution.md)) |
|
|
142
142
|
|
|
143
143
|
Every drop is counted in the router's [metrics](../production/metrics.md), so
|
|
144
144
|
you can see when a probe falls behind.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<!-- owner: p3-concepts -->
|
|
4
4
|
|
|
5
5
|
An **intervention** is an inline probe changing the course of generation. In
|
|
6
|
-
v0.
|
|
6
|
+
v0.2 there is one intervention: **abort**. When an inline probe returns
|
|
7
7
|
`ProbeSignal(action=ProbeAction.ABORT)`, the engine adapter stops generating
|
|
8
8
|
for that request. The router still finalizes every probe, so you get each
|
|
9
9
|
probe's `ProbeResult` for the truncated generation.
|
|
@@ -623,7 +623,7 @@ and probes; see [Deploy with vLLM](vllm-deployment.md).
|
|
|
623
623
|
### Probes with trained weights
|
|
624
624
|
|
|
625
625
|
A probe that runs a trained classifier needs its weights. There is no probe
|
|
626
|
-
save/load API in v0.
|
|
626
|
+
save/load API in v0.2. Load the weights yourself, with `safetensors`, which
|
|
627
627
|
`transformers` already depends on. Loading a file on every `spawn` would add
|
|
628
628
|
disk I/O to every request, so load once and share the read-only result, for
|
|
629
629
|
example:
|
|
@@ -15,13 +15,14 @@ server and want to drive the engine adapter and `Router` yourself, read
|
|
|
15
15
|
GPU, use the default Hugging Face backend from the
|
|
16
16
|
[Quickstart](../getting-started/quickstart.md).
|
|
17
17
|
|
|
18
|
-
!!! warning "Known issues (v0.
|
|
19
|
-
The vLLM adapter
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
residual
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
!!! warning "Known issues (v0.2)"
|
|
19
|
+
The vLLM adapter is experimental. Its GPU tests are run by hand before
|
|
20
|
+
each release (there's no GPU CI yet), and only vLLM 0.28 is supported.
|
|
21
|
+
Fixed in 0.1.0 and GPU-verified: on Llama-family models (and other
|
|
22
|
+
fused-residual architectures), `residual_stream` is captured as
|
|
23
|
+
`hidden_states + residual`, the same residual stream as the HF backend;
|
|
24
|
+
earlier builds captured the layer's MLP output. See
|
|
25
|
+
[Known issues](../compatibility.md#known-issues-v02) for details.
|
|
25
26
|
|
|
26
27
|
## Install into your vLLM environment
|
|
27
28
|
|
|
@@ -260,7 +261,7 @@ Everything below `ProbedModel` stays reachable:
|
|
|
260
261
|
|
|
261
262
|
## Serving over HTTP
|
|
262
263
|
|
|
263
|
-
Undercurrent v0.
|
|
264
|
+
Undercurrent v0.2 doesn't include an HTTP server. A supported, OpenAI-compatible
|
|
264
265
|
`serve` command is [on the roadmap](https://github.com/wrynx/undercurrent/blob/main/ROADMAP.md).
|
|
265
266
|
Until then, put `VLLMEngineAdapter` (or `ProbedModel`) behind the web server
|
|
266
267
|
you already run.
|
|
@@ -378,6 +379,9 @@ flagged stream (`choices[0].finish_reason == "content_flagged"`, empty
|
|
|
378
379
|
per-topology support status.
|
|
379
380
|
- **Use `enforce_eager=True`.** With CUDA graphs, replayed steps don't run
|
|
380
381
|
Python, so the forward hooks can't fire. The adapter doesn't set it for you.
|
|
382
|
+
Eager mode is expensive (on an A100, Qwen2.5-7B at batch 16 decodes at
|
|
383
|
+
about half its CUDA-graph throughput). If you only need `residual_stream`,
|
|
384
|
+
see [Keep CUDA graphs: graph-mode residual capture](#keep-cuda-graphs-graph-mode-residual-capture-experimental).
|
|
381
385
|
- **`block_until_signal` stalls the whole batch.** Under continuous batching,
|
|
382
386
|
a blocking inline probe holds the scheduler step that every co-batched
|
|
383
387
|
request shares, for up to `timeout_ms`. The adapter logs a one-time warning
|
|
@@ -395,6 +399,43 @@ flagged stream (`choices[0].finish_reason == "content_flagged"`, empty
|
|
|
395
399
|
point, but a bare `vllm serve` process doesn't drive probing. See
|
|
396
400
|
[Running under `vllm serve`](../internals/vllm-adapter.md#running-under-vllm-serve).
|
|
397
401
|
|
|
402
|
+
### Keep CUDA graphs: graph-mode residual capture (experimental)
|
|
403
|
+
|
|
404
|
+
*New in 0.2.0.* If every extraction point is a `residual_stream`, you can
|
|
405
|
+
leave `enforce_eager` off and keep vLLM's CUDA graphs and `torch.compile`.
|
|
406
|
+
Pass the decoder layers to capture as `graph_capture_layers`:
|
|
407
|
+
|
|
408
|
+
```py
|
|
409
|
+
with ProbedModel.from_pretrained(
|
|
410
|
+
"Qwen/Qwen2.5-0.5B-Instruct",
|
|
411
|
+
backend="vllm",
|
|
412
|
+
spec=SPEC, # residual_stream at layer 6 only
|
|
413
|
+
probes={"norm_gate": norm_gate},
|
|
414
|
+
graph_capture_layers=[6], # instead of enforce_eager=True
|
|
415
|
+
) as model:
|
|
416
|
+
outputs = model.generate(prompts, max_new_tokens=32)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
`VLLMEngineAdapter.load_model(..., graph_capture_layers=[...])` works the same
|
|
420
|
+
way. Instead of forward hooks, the adapter reads the auxiliary hidden-state
|
|
421
|
+
outputs vLLM already computes inside the graph for EAGLE-3 speculative
|
|
422
|
+
decoding. Probes see the same `residual_stream` tensor as in hook mode.
|
|
423
|
+
|
|
424
|
+
Limits, each enforced with an error rather than a silent miss:
|
|
425
|
+
|
|
426
|
+
- `residual_stream` only, and only at the layers given at load time. An
|
|
427
|
+
extraction point with another tensor type or layer is rejected when it's
|
|
428
|
+
registered.
|
|
429
|
+
- The model must implement vLLM's EAGLE-3 aux-output interface (Llama and
|
|
430
|
+
Qwen2, among others; GPT-2 doesn't).
|
|
431
|
+
- It can't be combined with speculative decoding that uses aux outputs
|
|
432
|
+
itself.
|
|
433
|
+
- vLLM's compile cache is disabled for the engine, so start-up recompiles.
|
|
434
|
+
|
|
435
|
+
Benchmark it on your own model with `benchmarks/run.py --graph-capture`. How
|
|
436
|
+
it works is covered in
|
|
437
|
+
[Graph-mode residual capture](../internals/vllm-adapter.md#graph-mode-residual-capture-experimental).
|
|
438
|
+
|
|
398
439
|
[The vLLM adapter](../internals/vllm-adapter.md) explains how capture,
|
|
399
440
|
position mapping and aborts work.
|
|
400
441
|
|
|
@@ -414,7 +455,7 @@ change in any minor release, so the range stops at the next one.
|
|
|
414
455
|
best-effort for one more Undercurrent release, then it's dropped.
|
|
415
456
|
|
|
416
457
|
!!! warning "GPU CI status"
|
|
417
|
-
There is no GPU CI for v0.
|
|
458
|
+
There is no GPU CI for v0.2: the GPU tests run by hand before each release
|
|
418
459
|
(`scripts/gpu_check.sh`). The vLLM rows on the
|
|
419
460
|
[Compatibility](../compatibility.md#tested-combinations) page say which
|
|
420
461
|
combinations have had a GPU run.
|
|
@@ -489,7 +530,8 @@ doesn't look (it checks `model.model.layers`, `model.layers` and
|
|
|
489
530
|
|
|
490
531
|
**No probe results, or `aborted` is `False` although a probe asked to abort**
|
|
491
532
|
|
|
492
|
-
Check that you passed `enforce_eager=True
|
|
533
|
+
Check that you passed `enforce_eager=True` (or, in graph mode, that the
|
|
534
|
+
extraction point is a `residual_stream` at one of the `graph_capture_layers`), that the extraction point's
|
|
493
535
|
`tensor_type` and `position` are captured on your model (see
|
|
494
536
|
[Known limitations](../internals/vllm-adapter.md#known-limitations)), and
|
|
495
537
|
that a class probe puts its `ABORT` signals in `signal_history` (see
|
|
@@ -228,6 +228,64 @@ wants that (layer, tensor type), so unused hooks cost little.
|
|
|
228
228
|
Which tensor each hook sees under tensor and pipeline parallelism is covered in
|
|
229
229
|
[Tensor & pipeline parallelism](vllm-parallelism.md).
|
|
230
230
|
|
|
231
|
+
### Graph-mode residual capture (experimental)
|
|
232
|
+
|
|
233
|
+
Forward hooks are Python, and neither CUDA-graph replay nor vLLM's
|
|
234
|
+
`torch.compile`d forward runs them, so hook-based capture needs
|
|
235
|
+
`enforce_eager=True`. That is expensive: on an A100, eager decoding of
|
|
236
|
+
Qwen2.5-7B at batch 16 runs at about half the CUDA-graph throughput, and
|
|
237
|
+
Qwen2.5-0.5B at about an eighth, before any probe runs.
|
|
238
|
+
|
|
239
|
+
For `residual_stream`, `load_model(graph_capture_layers=[...])` avoids eager
|
|
240
|
+
mode. It reuses the auxiliary hidden-state outputs vLLM already has for EAGLE-3
|
|
241
|
+
speculative decoding: models implementing that interface (Llama and Qwen2 among
|
|
242
|
+
them) output `hidden_states + residual` after the chosen decoder layers, inside
|
|
243
|
+
the compiled and captured graph. Aux index `k + 1` is decoder layer `k`'s
|
|
244
|
+
output, the same tensor hook mode captures at layer `k`.
|
|
245
|
+
|
|
246
|
+
- The adapter sets `UNDERCURRENT_VLLM_GRAPH_CAPTURE_LAYERS` for the engine
|
|
247
|
+
process and disables vLLM's compile cache (its key doesn't cover the extra
|
|
248
|
+
outputs).
|
|
249
|
+
- In the engine process, the `vllm.general_plugins` entry point patches
|
|
250
|
+
`GPUModelRunner.load_model`: after loading and before compilation, it calls
|
|
251
|
+
`set_aux_hidden_state_layers` and sets `use_aux_hidden_state_outputs`, as
|
|
252
|
+
vLLM does for EAGLE-3, and wraps `_model_forward` to hand the aux outputs to
|
|
253
|
+
the worker extension after each forward pass.
|
|
254
|
+
- The worker extension installs no forward hooks. It stages each probed
|
|
255
|
+
layer's matched rows from the aux outputs with the same batched,
|
|
256
|
+
non-blocking copy as hook mode (see below), before the next replay overwrites
|
|
257
|
+
them.
|
|
258
|
+
|
|
259
|
+
Limits, each enforced with an error rather than silently capturing nothing:
|
|
260
|
+
`residual_stream` only, at the layers given at load time; the model must
|
|
261
|
+
implement vLLM's EAGLE-3 aux-output interface; speculative decoding that uses
|
|
262
|
+
aux outputs itself can't be combined with it. `graph_capture_layers=[]` runs a
|
|
263
|
+
graph-mode engine with nothing captured (the benchmark's `probed-0`).
|
|
264
|
+
|
|
265
|
+
### Copying captures to the host
|
|
266
|
+
|
|
267
|
+
Probes run on the host, so every captured row is copied from the GPU. A forward
|
|
268
|
+
hook runs while the GPU is still working through earlier kernels, and a
|
|
269
|
+
blocking copy inside the hook makes the host wait for the GPU. While the host
|
|
270
|
+
waits, it can't queue the next layers' kernels, so the GPU then idles while the
|
|
271
|
+
host catches up. Doing that once per matched row (as before) cost one stall per
|
|
272
|
+
(row, layer) per step.
|
|
273
|
+
|
|
274
|
+
For CUDA tensors, each hook instead gathers all of its matched rows on the GPU
|
|
275
|
+
and starts one non-blocking copy into pinned host memory, recording a CUDA
|
|
276
|
+
event after it. The records are built after one wait on the latest event, once
|
|
277
|
+
per step:
|
|
278
|
+
|
|
279
|
+
- in-process (`bind_router()` succeeded): at the end of the `execute_model`
|
|
280
|
+
call, before the driver polls for aborts, so inline aborts take effect at the
|
|
281
|
+
same step as before;
|
|
282
|
+
- cross-process polling: when the driver calls `pop_pending_activations()`, or
|
|
283
|
+
at the start of the next step, whichever comes first. The copies have
|
|
284
|
+
finished by then, so the wait costs nothing.
|
|
285
|
+
|
|
286
|
+
Records still arrive in capture order (hook order, then row order). On CPU each
|
|
287
|
+
row is copied synchronously, since there's no device to overlap with.
|
|
288
|
+
|
|
231
289
|
### Engine settings the adapter forces
|
|
232
290
|
|
|
233
291
|
`load_model()` sets two environment variables before it builds the engine. Each
|
|
@@ -262,14 +320,21 @@ the `bind_router` call fails. The adapter logs this and falls back to polling:
|
|
|
262
320
|
|
|
263
321
|
- `_emit()` buffers each `ActivationRecord` in the worker.
|
|
264
322
|
- After every `RequestOutput`, the adapter calls `pop_pending_activations` over
|
|
265
|
-
`collective_rpc`.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
323
|
+
`collective_rpc`. Each worker returns one packed batch
|
|
324
|
+
(`activation_batch.py`): the tensors stacked into a float32 `bytes` buffer
|
|
325
|
+
per tensor width, and the other fields as parallel lists. vLLM's RPC encoder
|
|
326
|
+
carries `bytes` and lists natively, but not arbitrary dataclasses or tensors
|
|
327
|
+
nested in ad-hoc structures. (Earlier versions sent each tensor as a nested
|
|
328
|
+
Python list, which cost a few hundred microseconds per record at a
|
|
329
|
+
3,584-wide hidden size; a batch costs a few microseconds per record.)
|
|
330
|
+
- `_drain_pending_activations()` unpacks the batches, rebuilds the
|
|
331
|
+
`ActivationRecord`s in the driver and routes them **in capture order**, one
|
|
332
|
+
at a time, in a single `run_in_executor` call per drain (one thread hand-off
|
|
333
|
+
per record cost about as much again as the old encoding). Order matters for
|
|
334
|
+
trajectory probes, and running routing off the event loop stops a slow probe
|
|
335
|
+
from stalling every other in-flight request's traffic with the engine.
|
|
336
|
+
Aborts requested by those records are sent to the engine after the batch is
|
|
337
|
+
routed.
|
|
273
338
|
|
|
274
339
|
Capture, position matching and row mapping are the same on both paths; only the
|
|
275
340
|
place where `route()` runs differs. On the polling path, activations (and the
|
|
@@ -367,7 +432,7 @@ engine core and each worker). Here's what the current code does on that route:
|
|
|
367
432
|
requests registered through its `register_extraction` RPC, and only hands
|
|
368
433
|
records over when someone calls `pop_pending_activations`.
|
|
369
434
|
`VLLMEngineAdapter.generate()` makes those calls; a bare `vllm serve` process
|
|
370
|
-
has nothing that does. In Undercurrent v0.
|
|
435
|
+
has nothing that does. In Undercurrent v0.2 this route gives you a
|
|
371
436
|
correctly configured engine with the extension loaded, but not end-to-end
|
|
372
437
|
probing.
|
|
373
438
|
|
|
@@ -382,9 +447,9 @@ These all fail loudly unless the table says otherwise.
|
|
|
382
447
|
| Limitation | Impact | Workaround | Tracked in |
|
|
383
448
|
| --- | --- | --- | --- |
|
|
384
449
|
| More than one worker (tensor or pipeline parallelism, Ray) isn't validated. | `load_model()` raises `VLLMAdapterLimitationError`. | `allow_unsupported_executor=True`. Results from all workers are merged and deduplicated, but this is unvalidated. | [Tensor & pipeline parallelism](vllm-parallelism.md) |
|
|
385
|
-
| `residual_stream` needs to recognise the decoder layer's output: a single tensor, `(hidden_states, None)`, or a fused-residual `(hidden_states, residual)` pair of matching shape and dtype (captured as their sum). | Any other output (for example HunYuan's 3-tuple) raises `VLLMAdapterLimitationError` when a `residual_stream` extraction point fires on it.
|
|
450
|
+
| `residual_stream` needs to recognise the decoder layer's output: a single tensor, `(hidden_states, None)`, or a fused-residual `(hidden_states, residual)` pair of matching shape and dtype (captured as their sum). | Any other output (for example HunYuan's 3-tuple) raises `VLLMAdapterLimitationError` when a `residual_stream` extraction point fires on it. The fused-residual sum is GPU-verified against the HF backend (`scripts/gpu_check.sh`). | Use `attn_out`, `mlp_out` or `final_norm`, or the HF backend, on such models. | [Compatibility](../compatibility.md#fixed-in-010-residual_stream-on-fused-residual-vllm-layers) |
|
|
386
451
|
| `attn_out` is only hooked when the layer has a `self_attn` submodule. vLLM's GPT-2 implementation names it `attn`. | **Silent.** `attn_out` extraction points never fire on GPT-2-style models. | Use `residual_stream` or `mlp_out` on those models. | Future work |
|
|
387
|
-
|
|
|
452
|
+
| Hook-based capture needs `enforce_eager=True`. The adapter doesn't set it. | With CUDA graphs, replayed steps run recorded GPU kernels without executing Python, so forward hooks can't fire during them. | Pass `enforce_eager=True` to `load_model()`, as the tests and examples do; or, for `residual_stream` only, use `graph_capture_layers=[...]` (experimental, see [Graph-mode residual capture](#graph-mode-residual-capture-experimental)). | Other tensor types: future work |
|
|
388
453
|
| `tensor: kv` is rejected. The KV cache lives in paged blocks addressed through a slot mapping, not in a per-step tensor with one row per token. | `register_extraction` raises `WorkerExtractionError`. | Use `residual_stream`, `attn_out`, `mlp_out` or `final_norm`. | Future work |
|
|
389
454
|
| Negative-indexed single-point selectors (`generated[-1]`) never match. Resolving them needs the request's final length, which vLLM only knows once the request finishes, and by then the step's activations are gone. | **Silent.** No records for those extraction points. | `generated[*]` and slices (`generated[5:]`, `generated[2:8]`) work. Keep the last record from a trajectory probe yourself. | Future work |
|
|
390
455
|
| `block_until_signal` stalls every request in the batch, not only the triggering one. | Higher latency for co-batched requests, up to `timeout_ms` per blocked step. | Use `mode: reject` or `execution_mode: async` under concurrency. | By design (see [above](#block_until_signal-and-continuous-batching)) |
|
|
@@ -10,7 +10,7 @@ The vLLM source details below were checked against the vLLM version listed in
|
|
|
10
10
|
|
|
11
11
|
## Summary
|
|
12
12
|
|
|
13
|
-
Multi-worker topologies are **not validated** in Undercurrent v0.
|
|
13
|
+
Multi-worker topologies are **not validated** in Undercurrent v0.2:
|
|
14
14
|
`VLLMEngineAdapter.load_model()` raises `VLLMAdapterLimitationError` when vLLM
|
|
15
15
|
starts more than one worker, unless you pass `allow_unsupported_executor=True`.
|
|
16
16
|
The capture side is sound for stock Llama-style models. Under TP, every rank's
|
|
@@ -67,7 +67,7 @@ When `route()` finds a binding's queue full, the overflow policy decides:
|
|
|
67
67
|
| --- | --- | --- | --- |
|
|
68
68
|
| `drop_oldest` (default) | Evict the oldest queued activation, accept the new one | Older activations; the probe sees the most recent window | Nothing on the generation path |
|
|
69
69
|
| `drop_newest` | Reject the incoming activation; the queue is unchanged | Newer activations; the probe sees the earliest ones | Nothing on the generation path |
|
|
70
|
-
| `block` | `route()` waits until the worker frees a slot | Nothing is dropped | Generation slows to the probe's speed |
|
|
70
|
+
| `block` | `route()` waits until the worker frees a slot | Nothing is dropped | Generation slows to the probe's speed (HF, and vLLM in-process); on vLLM's cross-process path, time to results instead |
|
|
71
71
|
|
|
72
72
|
How to choose:
|
|
73
73
|
|
|
@@ -79,10 +79,24 @@ How to choose:
|
|
|
79
79
|
- **`block`** suits offline evaluation and data collection, where every
|
|
80
80
|
activation must be processed and throughput matters less than completeness.
|
|
81
81
|
Avoid it in latency-sensitive serving: a slow probe becomes slow generation.
|
|
82
|
-
On vLLM,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
On vLLM, what waits depends on where `route()` runs:
|
|
83
|
+
- **In-process** (the router reached the worker): the blocked `route()` call
|
|
84
|
+
runs on the shared engine thread, so it stalls every request in the batch,
|
|
85
|
+
not just this one.
|
|
86
|
+
- **Cross-process polling** (the default with vLLM 0.28, whose engine core
|
|
87
|
+
runs in its own process): `route()` runs in the driver, after the engine
|
|
88
|
+
has already produced the tokens. The engine is **not** slowed; it keeps
|
|
89
|
+
generating while the driver falls behind, captured activations accumulate
|
|
90
|
+
in the engine process until the driver catches up, and each `generate()`
|
|
91
|
+
call returns only once all of its activations have been processed. So
|
|
92
|
+
`block` there means "lossless, results delayed", not backpressure. The
|
|
93
|
+
adapter routes each drained backlog round-robin across requests, so all
|
|
94
|
+
bindings work in parallel and the delay is about the probe's own pace;
|
|
95
|
+
measured on an A100 with Qwen2.5-7B, 16 requests and a 76 ms probe, routing
|
|
96
|
+
one request's backlog at a time had made it about 13x slower than that.
|
|
97
|
+
|
|
98
|
+
And if the binding is waiting for a pool thread (see above), `block` can
|
|
99
|
+
stall until another request ends.
|
|
86
100
|
|
|
87
101
|
The policy is set router-wide:
|
|
88
102
|
|
|
@@ -341,7 +341,7 @@ don't set one. When a queue is full, the router-wide
|
|
|
341
341
|
| --- | --- | --- |
|
|
342
342
|
| `OverflowPolicy.DROP_OLDEST` (default) | Unaffected | The oldest queued activations; the probe sees the most recent ones |
|
|
343
343
|
| `OverflowPolicy.DROP_NEWEST` | Unaffected | The incoming activations; the probe sees the earliest ones |
|
|
344
|
-
| `OverflowPolicy.BLOCK` | `route()` waits for space, so generation slows to the probe's pace (on vLLM, the whole batch does) | Nothing |
|
|
344
|
+
| `OverflowPolicy.BLOCK` | `route()` waits for space, so generation slows to the probe's pace (on vLLM in-process, the whole batch does; on vLLM's cross-process path the engine isn't slowed and results arrive late instead) | Nothing |
|
|
345
345
|
|
|
346
346
|
Use a drop policy for live serving and watch the drop count; use `BLOCK` for
|
|
347
347
|
offline evaluation where every activation must be processed. A bigger queue
|
|
@@ -98,7 +98,7 @@ dev = [
|
|
|
98
98
|
# Hard per-test time limit for `gpu` tests (tests/conftest.py): a stuck vLLM
|
|
99
99
|
# engine dumps every thread's stack and fails instead of hanging the run.
|
|
100
100
|
"pytest-timeout>=2.3",
|
|
101
|
-
"ruff>=0.16,<0.
|
|
101
|
+
"ruff>=0.16,<0.18",
|
|
102
102
|
"pre-commit>=4",
|
|
103
103
|
# Validates example specs against schema/probe-spec.schema.json in tests.
|
|
104
104
|
"jsonschema>=4.18,<5",
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|