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.
Files changed (269) hide show
  1. {undercurrent-0.1.0 → undercurrent-0.2.0}/CHANGELOG.md +70 -1
  2. {undercurrent-0.1.0 → undercurrent-0.2.0}/PKG-INFO +4 -4
  3. {undercurrent-0.1.0 → undercurrent-0.2.0}/README.md +2 -2
  4. {undercurrent-0.1.0 → undercurrent-0.2.0}/benchmarks/README.md +2 -1
  5. {undercurrent-0.1.0 → undercurrent-0.2.0}/benchmarks/run.py +24 -2
  6. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/intended-use.md +2 -2
  7. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/compatibility.md +15 -4
  8. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/execution-modes.md +1 -1
  9. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/interventions.md +1 -1
  10. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/guides/custom-probe.md +1 -1
  11. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/guides/vllm-deployment.md +52 -10
  12. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/internals/vllm-adapter.md +76 -11
  13. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/internals/vllm-parallelism.md +1 -1
  14. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/async-execution.md +19 -5
  15. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/embedding.md +1 -1
  16. {undercurrent-0.1.0 → undercurrent-0.2.0}/pyproject.toml +1 -1
  17. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/_version.py +2 -2
  18. undercurrent-0.2.0/src/undercurrent/adapters/vllm/activation_batch.py +90 -0
  19. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/adapter.py +112 -20
  20. undercurrent-0.2.0/src/undercurrent/adapters/vllm/graph_capture.py +133 -0
  21. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/plugin.py +27 -0
  22. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/worker_extension.py +322 -97
  23. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/overflow.py +3 -1
  24. undercurrent-0.2.0/tests/adapters/vllm/test_activation_batch.py +92 -0
  25. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_adapter_contract.py +255 -0
  26. undercurrent-0.2.0/tests/adapters/vllm/test_graph_capture.py +216 -0
  27. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_worker_extension_bookkeeping.py +16 -14
  28. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_worker_extension_capture.py +12 -15
  29. undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_staging.py +279 -0
  30. {undercurrent-0.1.0 → undercurrent-0.2.0}/.gitignore +0 -0
  31. {undercurrent-0.1.0 → undercurrent-0.2.0}/LICENSE +0 -0
  32. {undercurrent-0.1.0 → undercurrent-0.2.0}/NOTICE +0 -0
  33. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/README.md +0 -0
  34. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_adapter_hf.md +0 -0
  35. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_adapter_vllm.md +0 -0
  36. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_content_safety.md +0 -0
  37. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_core.md +0 -0
  38. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_logging.md +0 -0
  39. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_router.md +0 -0
  40. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/probing_spec.md +0 -0
  41. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/_legacy/vllm_tp_pp_activation_extraction_notes.md +0 -0
  42. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/changelog.md +0 -0
  43. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/contributing.md +0 -0
  44. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/about/privacy.md +0 -0
  45. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/api-stability.md +0 -0
  46. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/social-preview.png +0 -0
  47. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-icon.svg +0 -0
  48. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo-dark.png +0 -0
  49. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo-dark.svg +0 -0
  50. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo.png +0 -0
  51. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-logo.svg +0 -0
  52. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-mark-dark.svg +0 -0
  53. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/assets/brand/undercurrent-mark.svg +0 -0
  54. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/benchmarks.md +0 -0
  55. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/comparison.md +0 -0
  56. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/architecture.md +0 -0
  57. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/extraction-points.md +0 -0
  58. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/position-selectors.md +0 -0
  59. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/concepts/probes.md +0 -0
  60. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/examples.md +0 -0
  61. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/getting-started/installation.md +0 -0
  62. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/getting-started/quickstart.md +0 -0
  63. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/guides/observation-sinks.md +0 -0
  64. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/index.md +0 -0
  65. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/intervention-policies.md +0 -0
  66. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/production/metrics.md +0 -0
  67. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/adapters.md +0 -0
  68. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/cli.md +0 -0
  69. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/core.md +0 -0
  70. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/index.md +0 -0
  71. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/model.md +0 -0
  72. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/router.md +0 -0
  73. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/sinks.md +0 -0
  74. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/reference/spec.md +0 -0
  75. {undercurrent-0.1.0 → undercurrent-0.2.0}/docs/stylesheets/brand.css +0 -0
  76. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/Dockerfile +0 -0
  77. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/README.md +0 -0
  78. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/__init__.py +0 -0
  79. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/custom_mlp.py +0 -0
  80. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/models.py +0 -0
  81. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/single_token.py +0 -0
  82. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/spec_binding.py +0 -0
  83. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/tensors.py +0 -0
  84. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_demo/trajectory.py +0 -0
  85. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_gpt2.yaml +0 -0
  86. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/content_safety_llama.yaml +0 -0
  87. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/custom_mlp_llama31_8b.yaml +0 -0
  88. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/make_dummy_mlp_checkpoint.py +0 -0
  89. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/content_safety/run_vllm_pipeline.py +0 -0
  90. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/function_probe.py +0 -0
  91. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/openai_server/README.md +0 -0
  92. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/openai_server/response_schema.py +0 -0
  93. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/openai_server/serve_llama_mlp_pipeline.py +0 -0
  94. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/quickstart.py +0 -0
  95. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/classic_single_token.yaml +0 -0
  96. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/continuous_trajectory.yaml +0 -0
  97. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/offset_dual_position.yaml +0 -0
  98. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/specs/sliced_range.yaml +0 -0
  99. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/README.md +0 -0
  100. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/collector.py +0 -0
  101. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/data_sources.py +0 -0
  102. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/linear_probe.py +0 -0
  103. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/requirements.txt +0 -0
  104. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/train_probe.py +0 -0
  105. {undercurrent-0.1.0 → undercurrent-0.2.0}/examples/train_probe/use_probe.py +0 -0
  106. {undercurrent-0.1.0 → undercurrent-0.2.0}/notebooks/quickstart.ipynb +0 -0
  107. {undercurrent-0.1.0 → undercurrent-0.2.0}/notebooks/train_probe.ipynb +0 -0
  108. {undercurrent-0.1.0 → undercurrent-0.2.0}/schema/probe-spec.schema.json +0 -0
  109. {undercurrent-0.1.0 → undercurrent-0.2.0}/scripts/generate_schema.py +0 -0
  110. {undercurrent-0.1.0 → undercurrent-0.2.0}/scripts/gpu_check.sh +0 -0
  111. {undercurrent-0.1.0 → undercurrent-0.2.0}/scripts/smoke_test_wheel.sh +0 -0
  112. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/__init__.py +0 -0
  113. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/__main__.py +0 -0
  114. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/__init__.py +0 -0
  115. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/_optional.py +0 -0
  116. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/base.py +0 -0
  117. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/__init__.py +0 -0
  118. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/adapter.py +0 -0
  119. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/errors.py +0 -0
  120. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/introspect.py +0 -0
  121. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/hf/stopping_criteria.py +0 -0
  122. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/__init__.py +0 -0
  123. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/introspection.py +0 -0
  124. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/seq_mapper.py +0 -0
  125. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/support.py +0 -0
  126. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/adapters/vllm/version_check.py +0 -0
  127. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/__init__.py +0 -0
  128. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/_errors.py +0 -0
  129. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/inspect_model.py +0 -0
  130. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/schema.py +0 -0
  131. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/cli/validate.py +0 -0
  132. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/__init__.py +0 -0
  133. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/activation.py +0 -0
  134. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/context.py +0 -0
  135. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/__init__.py +0 -0
  136. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/_util.py +0 -0
  137. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/mlp_classifier.py +0 -0
  138. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/examples/trajectory_score.py +0 -0
  139. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/function_probe.py +0 -0
  140. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/probe.py +0 -0
  141. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/registry.py +0 -0
  142. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/result.py +0 -0
  143. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/core/signal.py +0 -0
  144. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/errors.py +0 -0
  145. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/model.py +0 -0
  146. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/py.typed +0 -0
  147. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/__init__.py +0 -0
  148. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/binding.py +0 -0
  149. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/errors.py +0 -0
  150. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/metrics.py +0 -0
  151. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/request.py +0 -0
  152. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/router/router.py +0 -0
  153. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/__init__.py +0 -0
  154. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/file_sink.py +0 -0
  155. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/records.py +0 -0
  156. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/redaction.py +0 -0
  157. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/serialize.py +0 -0
  158. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/sink.py +0 -0
  159. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/webhook_sink.py +0 -0
  160. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/sinks/wiring.py +0 -0
  161. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/__init__.py +0 -0
  162. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/activation_record.py +0 -0
  163. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/errors.py +0 -0
  164. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/json_schema.py +0 -0
  165. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/parser.py +0 -0
  166. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/position.py +0 -0
  167. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/resolved.py +0 -0
  168. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/schema.py +0 -0
  169. {undercurrent-0.1.0 → undercurrent-0.2.0}/src/undercurrent/spec/serialize.py +0 -0
  170. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/__init__.py +0 -0
  171. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/__init__.py +0 -0
  172. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/__init__.py +0 -0
  173. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/_helpers.py +0 -0
  174. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/conftest.py +0 -0
  175. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/test_adapter_contract.py +0 -0
  176. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/test_intervention_e2e.py +0 -0
  177. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/hf/test_introspect.py +0 -0
  178. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/test_optional.py +0 -0
  179. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/__init__.py +0 -0
  180. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/_helpers.py +0 -0
  181. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/conftest.py +0 -0
  182. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_adapter_shutdown.py +0 -0
  183. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_integration_vllm.py +0 -0
  184. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_plugin.py +0 -0
  185. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_residual_stream_capture.py +0 -0
  186. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_residual_stream_gpu.py +0 -0
  187. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_seq_mapper.py +0 -0
  188. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_support.py +0 -0
  189. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/adapters/vllm/test_version_check.py +0 -0
  190. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/benchmarks/__init__.py +0 -0
  191. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/benchmarks/test_harness.py +0 -0
  192. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/__init__.py +0 -0
  193. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/conftest.py +0 -0
  194. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_inspect_model.py +0 -0
  195. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_main.py +0 -0
  196. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_schema.py +0 -0
  197. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/cli/test_validate.py +0 -0
  198. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/conftest.py +0 -0
  199. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/__init__.py +0 -0
  200. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/conftest.py +0 -0
  201. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_function_probe.py +0 -0
  202. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_probe_isolation.py +0 -0
  203. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_probe_result_always_returned.py +0 -0
  204. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_registry.py +0 -0
  205. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/core/test_trajectory_score_probe.py +0 -0
  206. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/docs/__init__.py +0 -0
  207. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/docs/test_doc_snippets.py +0 -0
  208. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/e2e/__init__.py +0 -0
  209. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/e2e/test_hf_pipeline_cpu.py +0 -0
  210. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/__init__.py +0 -0
  211. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/__init__.py +0 -0
  212. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/conftest.py +0 -0
  213. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_custom_mlp_probe.py +0 -0
  214. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_isolation.py +0 -0
  215. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_live_llama_integration.py +0 -0
  216. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_single_token_probe.py +0 -0
  217. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_spec_binding.py +0 -0
  218. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/content_safety/test_trajectory_probe.py +0 -0
  219. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/__init__.py +0 -0
  220. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/conftest.py +0 -0
  221. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/test_response_schema.py +0 -0
  222. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/openai_server/test_server_cli.py +0 -0
  223. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/test_function_probe_example.py +0 -0
  224. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/train_probe/__init__.py +0 -0
  225. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/train_probe/conftest.py +0 -0
  226. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/examples/train_probe/test_train_probe.py +0 -0
  227. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/notebooks/__init__.py +0 -0
  228. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/notebooks/test_notebooks.py +0 -0
  229. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/__init__.py +0 -0
  230. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/_helpers.py +0 -0
  231. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/conftest.py +0 -0
  232. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_context_managers.py +0 -0
  233. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_dispatch.py +0 -0
  234. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_end_request.py +0 -0
  235. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_intervention.py +0 -0
  236. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_isolation.py +0 -0
  237. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_load.py +0 -0
  238. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_metrics.py +0 -0
  239. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_overflow.py +0 -0
  240. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_probe_args.py +0 -0
  241. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_probe_registry.py +0 -0
  242. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_registration.py +0 -0
  243. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_shutdown.py +0 -0
  244. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/router/test_stress_overflow.py +0 -0
  245. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/__init__.py +0 -0
  246. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/_helpers.py +0 -0
  247. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/conftest.py +0 -0
  248. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_file_sink.py +0 -0
  249. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_redaction.py +0 -0
  250. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_router_integration.py +0 -0
  251. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/sinks/test_webhook_sink.py +0 -0
  252. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/__init__.py +0 -0
  253. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_activation_record.py +0 -0
  254. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_intervention.py +0 -0
  255. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_json_schema.py +0 -0
  256. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_parser.py +0 -0
  257. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_position.py +0 -0
  258. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_roundtrip.py +0 -0
  259. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/spec/test_vocab.py +0 -0
  260. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_errors.py +0 -0
  261. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_gpu_check.py +0 -0
  262. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_import_light.py +0 -0
  263. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_markers.py +0 -0
  264. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_metadata.py +0 -0
  265. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_model.py +0 -0
  266. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_model_vllm.py +0 -0
  267. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_public_api.py +0 -0
  268. {undercurrent-0.1.0 → undercurrent-0.2.0}/tests/test_release_workflow.py +0 -0
  269. {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.1.0...HEAD
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.1.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.17,>=0.16; extra == 'dev'
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.1):** the vLLM adapter is experimental: its GPU tests
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-v01).
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.1):** the vLLM adapter is experimental: its GPU tests
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-v01).
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=build_spec(scenario, self.num_layers),
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
- **self.engine_kwargs,
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.1.
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.1.
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.1. Instead, before every release a maintainer runs
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.1)
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.1. The GPU and real-vLLM tests run by hand: a
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.1.0 that's an NVIDIA
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.1 there is one intervention: **abort**. When an inline probe returns
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.1. Load the weights yourself, with `safetensors`, which
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.1)"
19
- The vLLM adapter hasn't been verified on a GPU yet. Fixed in 0.1.0: on
20
- Llama-family models (and other fused-residual architectures),
21
- `residual_stream` is captured as `hidden_states + residual`, the same
22
- residual stream as the HF backend; earlier builds captured the layer's
23
- MLP output. GPU verification of the fix is pending. See
24
- [Known issues](../compatibility.md#known-issues-v01) for details.
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.1 doesn't include an HTTP server. A supported, OpenAI-compatible
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.1: the GPU tests run by hand before each release
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`, that the extraction point's
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`. Records travel as plain dicts with the tensor as a nested
266
- list, because vLLM's RPC encoder doesn't handle arbitrary dataclasses or
267
- tensors nested in ad-hoc structures.
268
- - `_drain_pending_activations()` rebuilds the `ActivationRecord`s in the driver
269
- and routes them **in capture order**, one at a time, through
270
- `run_in_executor`. Order matters for trajectory probes, and running routing
271
- off the event loop stops a slow probe from stalling every other in-flight
272
- request's traffic with the engine.
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.1 this route gives you a
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. GPU verification of the fused-residual sum against the HF backend is pending `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) |
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
- | Only tested with `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. | Future work |
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.1:
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, the blocked `route()` call runs on the shared engine thread, so it
83
- stalls every request in the batch, not just this one. And if the binding is
84
- waiting for a pool thread (see above), `block` can stall generation until
85
- another request ends.
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.17",
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.1.0'
22
- __version_tuple__ = version_tuple = (0, 1, 0)
21
+ __version__ = version = '0.2.0'
22
+ __version_tuple__ = version_tuple = (0, 2, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None