interp-engine 1.12.0__tar.gz → 1.13.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.
- {interp_engine-1.12.0 → interp_engine-1.13.0}/PKG-INFO +23 -18
- {interp_engine-1.12.0 → interp_engine-1.13.0}/README.md +17 -17
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/bench_spec.py +4 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/publish.py +38 -8
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/report_bench.py +4 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results-latest.md +7 -7
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/AGENT_INTEGRATION.md +44 -9
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/ARCHITECTURE_QUIRKS.md +6 -5
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/ENGINE_HOOK_MAPPINGS.md +10 -9
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/GRADIENTS.md +1 -1
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/INTERNALS.md +7 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/PERFORMANCE.md +8 -6
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/USAGE.md +53 -8
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/__init__.py +27 -1
- interp_engine-1.13.0/interp_engine/_conformance.py +21 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/_loop.py +51 -4
- interp_engine-1.13.0/interp_engine/api.py +267 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/arch.py +14 -10
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/capture.py +69 -56
- interp_engine-1.13.0/interp_engine/describe.py +64 -0
- interp_engine-1.13.0/interp_engine/directions.py +79 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/dispatch.py +22 -8
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/facts.py +67 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/hooks.py +1 -1
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/lens.py +4 -4
- interp_engine-1.13.0/interp_engine/lens_stream.py +488 -0
- interp_engine-1.13.0/interp_engine/lens_topk.py +59 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/load.py +3 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/mappers.py +1 -1
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/memory.py +47 -3
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/model.py +414 -20
- interp_engine-1.13.0/interp_engine/oracle.py +614 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/protocol.py +297 -10
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/select.py +49 -19
- interp_engine-1.13.0/interp_engine/server/__init__.py +26 -0
- interp_engine-1.13.0/interp_engine/server/__main__.py +101 -0
- interp_engine-1.13.0/interp_engine/server/app.py +473 -0
- interp_engine-1.13.0/interp_engine/server/config.py +74 -0
- interp_engine-1.13.0/interp_engine/server/lens.py +87 -0
- interp_engine-1.13.0/interp_engine/server/runner.py +105 -0
- interp_engine-1.13.0/interp_engine/server/schemas.py +324 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/steer.py +325 -47
- interp_engine-1.13.0/interp_engine/steer_specs.py +318 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/sync.py +137 -3
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/tokenize.py +32 -21
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_backend.py +827 -331
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/__init__.py +4 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/_demux.py +1 -7
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/_tp.py +16 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/_tree.py +42 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/lens/__init__.py +7 -5
- interp_engine-1.13.0/interp_engine/vllm_capture/lens/intervene.py +111 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/lens/readout.py +80 -93
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/lens/unembed.py +17 -0
- interp_engine-1.13.0/interp_engine/vllm_capture/project.py +37 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/requests.py +36 -76
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/static.py +147 -58
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/steering.py +47 -7
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_plugin.py +23 -14
- {interp_engine-1.12.0 → interp_engine-1.13.0}/pyproject.toml +12 -1
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/conftest.py +3 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/harness.py +2 -2
- interp_engine-1.13.0/tests/test_api_fixtures.py +112 -0
- interp_engine-1.13.0/tests/test_api_generated.py +146 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_attn_probs_indexing.py +5 -5
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_attn_scores.py +11 -11
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_attn_z_gqa.py +2 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_capability_refusals.py +6 -6
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_capture_addressing.py +8 -8
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_core.py +51 -6
- interp_engine-1.13.0/tests/test_describe.py +80 -0
- interp_engine-1.13.0/tests/test_directions.py +202 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_eager_autograd.py +6 -6
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_facts.py +56 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_family_points.py +61 -8
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_gated_attn_out.py +5 -5
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_head_contributions.py +3 -3
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_hook_call_conventions.py +2 -2
- interp_engine-1.13.0/tests/test_lens_stream.py +246 -0
- interp_engine-1.13.0/tests/test_lens_stream_pairing.py +390 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_load.py +7 -1
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_memory.py +23 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_mlp_internals.py +5 -5
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_model_expectations.py +2 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_moe.py +13 -13
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_multimodal_arch.py +93 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_new_models_gpu.py +6 -6
- interp_engine-1.13.0/tests/test_oracle.py +257 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_packaging.py +5 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_padded_positions.py +59 -13
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_parity_gpt2.py +10 -10
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_per_layer_attn_dims.py +8 -8
- interp_engine-1.13.0/tests/test_point_capability.py +235 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_protocol.py +17 -4
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_published_benchmarks.py +24 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_qk_norm.py +2 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_qkv_layout.py +5 -5
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_resid_mid.py +3 -3
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_residual_basis.py +7 -7
- interp_engine-1.13.0/tests/test_sample_from_embeds.py +145 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_sandwich_norms.py +2 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_select.py +27 -0
- interp_engine-1.13.0/tests/test_server.py +413 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_sliding_window_attn.py +2 -2
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_small_models_gpu.py +3 -3
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_static_dsv4_gpu.py +20 -1
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_static_set.py +133 -4
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_steer_context.py +32 -5
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_steer_math_parity.py +103 -0
- interp_engine-1.13.0/tests/test_steer_reaches_generate.py +169 -0
- interp_engine-1.13.0/tests/test_steer_scope.py +166 -0
- interp_engine-1.13.0/tests/test_steer_specs.py +181 -0
- interp_engine-1.13.0/tests/test_steer_stream_stack.py +120 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_sync_loop.py +35 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_sync_parity.py +18 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_unified_free_functions.py +15 -4
- interp_engine-1.13.0/tests/test_userless_prefix_spans.py +89 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_capture_gpu.py +123 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_hook_availability.py +6 -10
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_hyper_connections.py +16 -14
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_kv_isolation.py +147 -6
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_new_points.py +1 -1
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_padded_gpu.py +4 -4
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_worker_lens_capture_readout.py +93 -0
- interp_engine-1.12.0/interp_engine/steer_specs.py +0 -166
- interp_engine-1.12.0/interp_engine/vllm_capture/lens/intervene.py +0 -142
- {interp_engine-1.12.0 → interp_engine-1.13.0}/.gitignore +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/LICENSE +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/README.md +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/__init__.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/cells.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/probe.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/probe_lens_stream.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/deepseek-v4-flash-0731__eager.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/deepseek-v4-flash-0731__vllm-cudagraph.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/deepseek-v4-flash-0731__vllm-static.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/deepseek-v4-flash-0731__vllm.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/gemma-2-2b__eager.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/gemma-2-2b__vllm-cudagraph.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/gemma-2-2b__vllm-static.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/gemma-2-2b__vllm.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/llama-3.1-8b__eager.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/llama-3.1-8b__vllm-cudagraph.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/llama-3.1-8b__vllm-static.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/llama-3.1-8b__vllm.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3-4b__eager.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3-4b__vllm-cudagraph.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3-4b__vllm-static.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3-4b__vllm.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3.8-27b__eager.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3.8-27b__vllm-cudagraph.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3.8-27b__vllm-static.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/results/qwen3.8-27b__vllm.json +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/run_all.sh +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/run_bench.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/benchmarks/workloads.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/COMPATIBILITY.md +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/PORTING.md +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/README.md +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/SUPPORTED_POINTS.md +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/docs/VALIDATING_A_NEW_MODEL.md +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/address.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/attn_config.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/attn_scores.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/autograd_support.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/chat_compose.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/chat_conventions.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/chat_formatters.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/cuda_preflight.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/moe_routing.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/notebook_stdout.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/points.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/residual_basis.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/sampling.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/_hooks.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/_payload.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/attn.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/capture.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/graphs.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/mhc.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/interp_engine/vllm_capture/native.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/model_expectations.yaml +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/synthetic_families.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_address.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_attn_config_tripwire.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_autograd_support.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_bench_workloads.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_chat_compose.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_chat_formatters.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_chat_templates.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_cuda_preflight.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_doc_code_fences.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_gpu_sizer.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_injected_system_spans.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_layer_kinds.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_logit_transform.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_mappers.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_multigpu.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_no_chat_template.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_normalized_hook.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_notebook_stdout.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_points_registry.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_reasoning_spans.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_release.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_sampling.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_static_parity_gpu.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_static_warmup.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_steer_ops_per_site_gpu.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_unresolved_families.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_capture_scales.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_engine_loop.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_graph_path.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_graphs_on_gpu.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_only_families.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_plugin.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vllm_wire_grammar.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_vocabulary_boundary.py +0 -0
- {interp_engine-1.12.0 → interp_engine-1.13.0}/tests/test_worker_lens_readout.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: interp-engine
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.13.0
|
|
4
4
|
Summary: A fast, standardized, and easy to use interpretability engine.
|
|
5
5
|
Project-URL: Homepage, https://github.com/decoderesearch/interp-engine
|
|
6
6
|
Project-URL: Repository, https://github.com/decoderesearch/interp-engine
|
|
@@ -8,6 +8,7 @@ Project-URL: Issues, https://github.com/decoderesearch/interp-engine/issues
|
|
|
8
8
|
License-Expression: Apache-2.0
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Python: <3.14,>=3.11
|
|
11
|
+
Requires-Dist: accelerate>=1.0
|
|
11
12
|
Requires-Dist: einops
|
|
12
13
|
Requires-Dist: numpy>=1.24
|
|
13
14
|
Requires-Dist: torch>=1.10
|
|
@@ -25,6 +26,10 @@ Requires-Dist: transformer-lens<4,>=3.0; extra == 'parity'
|
|
|
25
26
|
Provides-Extra: quant
|
|
26
27
|
Requires-Dist: accelerate>=1.0; extra == 'quant'
|
|
27
28
|
Requires-Dist: kernels<0.17.0,>=0.15.2; extra == 'quant'
|
|
29
|
+
Provides-Extra: server
|
|
30
|
+
Requires-Dist: fastapi>=0.115; extra == 'server'
|
|
31
|
+
Requires-Dist: httpx>=0.27; extra == 'server'
|
|
32
|
+
Requires-Dist: uvicorn>=0.30; extra == 'server'
|
|
28
33
|
Provides-Extra: vllm
|
|
29
34
|
Requires-Dist: vllm==0.28.0; (sys_platform == 'linux') and extra == 'vllm'
|
|
30
35
|
Description-Content-Type: text/markdown
|
|
@@ -72,7 +77,7 @@ pip install interp-engine # eager backend only
|
|
|
72
77
|
## Simple Usage
|
|
73
78
|
|
|
74
79
|
```python
|
|
75
|
-
from interp_engine import Address,
|
|
80
|
+
from interp_engine import Address, capture, load_model
|
|
76
81
|
|
|
77
82
|
# VLLM (default): low VRAM, medium speed, every point, chosen per request
|
|
78
83
|
model = load_model("Qwen/Qwen3-8B")
|
|
@@ -87,7 +92,7 @@ model = load_model("Qwen/Qwen3-8B")
|
|
|
87
92
|
# model = load_model("Qwen/Qwen3-8B", backend="eager")
|
|
88
93
|
|
|
89
94
|
point = Address("resid_post", 10) # or string: "resid_post.10"
|
|
90
|
-
cache =
|
|
95
|
+
cache = capture(model, model.to_tokens("Hello, world"), [point])
|
|
91
96
|
cache[point] # [batch, pos, ...]
|
|
92
97
|
```
|
|
93
98
|
|
|
@@ -107,27 +112,27 @@ vLLM gives `interp-engine` high throughput via concurrency, and `backend="vllm-s
|
|
|
107
112
|
|
|
108
113
|
<!-- Generated by `python -m benchmarks.report_bench`. Do not edit: rerun the sweep. -->
|
|
109
114
|
|
|
110
|
-
Measured on NVIDIA B200,
|
|
115
|
+
Measured on NVIDIA B200, 512-token prompt, 128 new tokens.
|
|
111
116
|
|
|
112
117
|
One stream (tok/s):
|
|
113
118
|
|
|
114
|
-
| model | eager | vLLM | vLLM + static taps |
|
|
115
|
-
| ------------------------ | ----- | ---------- | ------------------ |
|
|
116
|
-
| `gemma-2-2b` | 75 | 88 (1.2x) | **220 (2.9x)** |
|
|
117
|
-
| `qwen3-4b` | 60 | 136 (2.3x) | **313 (5.2x)** |
|
|
118
|
-
| `llama-3.1-8b` | 84 | 163 (1.9x) | **258 (3.1x)** |
|
|
119
|
-
| `qwen3.8-27b` | 25 | 35 (1.4x) | **63 (2.5x)** |
|
|
120
|
-
| `deepseek-v4-flash-0731` | 5.3 | 15 (2.9x) | **132 (25x)** |
|
|
119
|
+
| model | weights | eager | vLLM | vLLM + static taps |
|
|
120
|
+
| ------------------------ | ------- | ----- | ---------- | ------------------ |
|
|
121
|
+
| `gemma-2-2b` | bf16 | 75 | 88 (1.2x) | **220 (2.9x)** |
|
|
122
|
+
| `qwen3-4b` | bf16 | 60 | 136 (2.3x) | **313 (5.2x)** |
|
|
123
|
+
| `llama-3.1-8b` | bf16 | 84 | 163 (1.9x) | **258 (3.1x)** |
|
|
124
|
+
| `qwen3.8-27b` | bf16 | 25 | 35 (1.4x) | **63 (2.5x)** |
|
|
125
|
+
| `deepseek-v4-flash-0731` | fp8 | 5.3 | 15 (2.9x) | **132 (25x)** |
|
|
121
126
|
|
|
122
127
|
8 concurrent requests (aggregate tok/s):
|
|
123
128
|
|
|
124
|
-
| model | eager | vLLM | vLLM + static taps |
|
|
125
|
-
| ------------------------ | ----- | ------------- | ------------------ |
|
|
126
|
-
| `gemma-2-2b` | 75 | 675 (9.0x) | **1,436 (19.2x)** |
|
|
127
|
-
| `qwen3-4b` | 61 | 973 (15.9x) | **2,219 (36x)** |
|
|
128
|
-
| `llama-3.1-8b` | 81 | 1,187 (14.6x) | **1,755 (22x)** |
|
|
129
|
-
| `qwen3.8-27b` | 24 | 246 (10.3x) | **408 (17.0x)** |
|
|
130
|
-
| `deepseek-v4-flash-0731` | 5.4 | 117 (22x) | **631 (116x)** |
|
|
129
|
+
| model | weights | eager | vLLM | vLLM + static taps |
|
|
130
|
+
| ------------------------ | ------- | ----- | ------------- | ------------------ |
|
|
131
|
+
| `gemma-2-2b` | bf16 | 75 | 675 (9.0x) | **1,436 (19.2x)** |
|
|
132
|
+
| `qwen3-4b` | bf16 | 61 | 973 (15.9x) | **2,219 (36x)** |
|
|
133
|
+
| `llama-3.1-8b` | bf16 | 81 | 1,187 (14.6x) | **1,755 (22x)** |
|
|
134
|
+
| `qwen3.8-27b` | bf16 | 24 | 246 (10.3x) | **408 (17.0x)** |
|
|
135
|
+
| `deepseek-v4-flash-0731` | fp8 | 5.4 | 117 (22x) | **631 (116x)** |
|
|
131
136
|
|
|
132
137
|
<!-- THROUGHPUT:END -->
|
|
133
138
|
|
|
@@ -41,7 +41,7 @@ pip install interp-engine # eager backend only
|
|
|
41
41
|
## Simple Usage
|
|
42
42
|
|
|
43
43
|
```python
|
|
44
|
-
from interp_engine import Address,
|
|
44
|
+
from interp_engine import Address, capture, load_model
|
|
45
45
|
|
|
46
46
|
# VLLM (default): low VRAM, medium speed, every point, chosen per request
|
|
47
47
|
model = load_model("Qwen/Qwen3-8B")
|
|
@@ -56,7 +56,7 @@ model = load_model("Qwen/Qwen3-8B")
|
|
|
56
56
|
# model = load_model("Qwen/Qwen3-8B", backend="eager")
|
|
57
57
|
|
|
58
58
|
point = Address("resid_post", 10) # or string: "resid_post.10"
|
|
59
|
-
cache =
|
|
59
|
+
cache = capture(model, model.to_tokens("Hello, world"), [point])
|
|
60
60
|
cache[point] # [batch, pos, ...]
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -76,27 +76,27 @@ vLLM gives `interp-engine` high throughput via concurrency, and `backend="vllm-s
|
|
|
76
76
|
|
|
77
77
|
<!-- Generated by `python -m benchmarks.report_bench`. Do not edit: rerun the sweep. -->
|
|
78
78
|
|
|
79
|
-
Measured on NVIDIA B200,
|
|
79
|
+
Measured on NVIDIA B200, 512-token prompt, 128 new tokens.
|
|
80
80
|
|
|
81
81
|
One stream (tok/s):
|
|
82
82
|
|
|
83
|
-
| model | eager | vLLM | vLLM + static taps |
|
|
84
|
-
| ------------------------ | ----- | ---------- | ------------------ |
|
|
85
|
-
| `gemma-2-2b` | 75 | 88 (1.2x) | **220 (2.9x)** |
|
|
86
|
-
| `qwen3-4b` | 60 | 136 (2.3x) | **313 (5.2x)** |
|
|
87
|
-
| `llama-3.1-8b` | 84 | 163 (1.9x) | **258 (3.1x)** |
|
|
88
|
-
| `qwen3.8-27b` | 25 | 35 (1.4x) | **63 (2.5x)** |
|
|
89
|
-
| `deepseek-v4-flash-0731` | 5.3 | 15 (2.9x) | **132 (25x)** |
|
|
83
|
+
| model | weights | eager | vLLM | vLLM + static taps |
|
|
84
|
+
| ------------------------ | ------- | ----- | ---------- | ------------------ |
|
|
85
|
+
| `gemma-2-2b` | bf16 | 75 | 88 (1.2x) | **220 (2.9x)** |
|
|
86
|
+
| `qwen3-4b` | bf16 | 60 | 136 (2.3x) | **313 (5.2x)** |
|
|
87
|
+
| `llama-3.1-8b` | bf16 | 84 | 163 (1.9x) | **258 (3.1x)** |
|
|
88
|
+
| `qwen3.8-27b` | bf16 | 25 | 35 (1.4x) | **63 (2.5x)** |
|
|
89
|
+
| `deepseek-v4-flash-0731` | fp8 | 5.3 | 15 (2.9x) | **132 (25x)** |
|
|
90
90
|
|
|
91
91
|
8 concurrent requests (aggregate tok/s):
|
|
92
92
|
|
|
93
|
-
| model | eager | vLLM | vLLM + static taps |
|
|
94
|
-
| ------------------------ | ----- | ------------- | ------------------ |
|
|
95
|
-
| `gemma-2-2b` | 75 | 675 (9.0x) | **1,436 (19.2x)** |
|
|
96
|
-
| `qwen3-4b` | 61 | 973 (15.9x) | **2,219 (36x)** |
|
|
97
|
-
| `llama-3.1-8b` | 81 | 1,187 (14.6x) | **1,755 (22x)** |
|
|
98
|
-
| `qwen3.8-27b` | 24 | 246 (10.3x) | **408 (17.0x)** |
|
|
99
|
-
| `deepseek-v4-flash-0731` | 5.4 | 117 (22x) | **631 (116x)** |
|
|
93
|
+
| model | weights | eager | vLLM | vLLM + static taps |
|
|
94
|
+
| ------------------------ | ------- | ----- | ------------- | ------------------ |
|
|
95
|
+
| `gemma-2-2b` | bf16 | 75 | 675 (9.0x) | **1,436 (19.2x)** |
|
|
96
|
+
| `qwen3-4b` | bf16 | 61 | 973 (15.9x) | **2,219 (36x)** |
|
|
97
|
+
| `llama-3.1-8b` | bf16 | 81 | 1,187 (14.6x) | **1,755 (22x)** |
|
|
98
|
+
| `qwen3.8-27b` | bf16 | 24 | 246 (10.3x) | **408 (17.0x)** |
|
|
99
|
+
| `deepseek-v4-flash-0731` | fp8 | 5.4 | 117 (22x) | **631 (116x)** |
|
|
100
100
|
|
|
101
101
|
<!-- THROUGHPUT:END -->
|
|
102
102
|
|
|
@@ -27,6 +27,9 @@ class ModelSpec:
|
|
|
27
27
|
precision and does not resolve to the same thing on both backends: eager honors a float32
|
|
28
28
|
checkpoint while vLLM's "auto" downcasts it to bfloat16, which would make that model's row a
|
|
29
29
|
precision comparison rather than a backend one."""
|
|
30
|
+
weights: str = ""
|
|
31
|
+
"""What the checkpoint's weights stay in when that is not ``dtype`` and its cells do not record
|
|
32
|
+
it, for the README's weights column. Empty: the cell's quantization, else its dtype."""
|
|
30
33
|
min_gpu_gib: float = 0.0
|
|
31
34
|
"""The smallest card this row fits on, in GiB of total VRAM. A sweep that was not given an
|
|
32
35
|
explicit ``--models`` drops the rows the card cannot hold and says which (:func:`default_models`).
|
|
@@ -123,6 +126,7 @@ MODELS: tuple[ModelSpec, ...] = (
|
|
|
123
126
|
"deepseek-ai/DeepSeek-V4-Flash-0731",
|
|
124
127
|
"DeepseekV4",
|
|
125
128
|
"291B (14B active)",
|
|
129
|
+
weights="fp8",
|
|
126
130
|
min_gpu_gib=170.0,
|
|
127
131
|
# `resid_post` does not exist on this trunk: the block carries `hc_mult` (4) parallel residual
|
|
128
132
|
# streams, and the engine refuses the name rather than silently returning stream 0. `mlp_out`
|
|
@@ -26,7 +26,7 @@ The display form differs from the report's, deliberately:
|
|
|
26
26
|
rounding, which is the worse of the two.
|
|
27
27
|
- **the card gets the machine, the README gets the conditions.** Both need the reader to know these
|
|
28
28
|
are one box's numbers, but the card is a hover card that links ``results-latest.md`` for the rest,
|
|
29
|
-
and the
|
|
29
|
+
and the weights and the two lengths are four more things to read before reaching a figure. So it is
|
|
30
30
|
handed ``BENCHMARK_GPU`` where the README prints the whole line -- the difference is the room each
|
|
31
31
|
has, not a difference of opinion about what a figure means without them.
|
|
32
32
|
"""
|
|
@@ -34,11 +34,12 @@ The display form differs from the report's, deliberately:
|
|
|
34
34
|
from __future__ import annotations
|
|
35
35
|
|
|
36
36
|
import argparse
|
|
37
|
+
import re
|
|
37
38
|
from dataclasses import dataclass
|
|
38
39
|
from pathlib import Path
|
|
39
40
|
from typing import Any
|
|
40
41
|
|
|
41
|
-
from benchmarks.bench_spec import workload
|
|
42
|
+
from benchmarks.bench_spec import MODELS, workload
|
|
42
43
|
from benchmarks.cells import cell_metric, cells_by_key, load_cells, model_order, nonuniform, row_spec
|
|
43
44
|
from benchmarks.run_bench import RESULTS_DIR
|
|
44
45
|
|
|
@@ -110,7 +111,9 @@ class Conditions:
|
|
|
110
111
|
concurrency: int
|
|
111
112
|
|
|
112
113
|
def __str__(self) -> str:
|
|
113
|
-
|
|
114
|
+
# The dtype is per row, in the tables' `weights` column: a quantized row runs its matmuls in
|
|
115
|
+
# 4 or 8 bits whatever the activations are in.
|
|
116
|
+
return f"{self.gpu}, {self.prompt_tokens}-token prompt, {self.new_tokens} new tokens"
|
|
114
117
|
|
|
115
118
|
|
|
116
119
|
def _only(values: set[Any], what: str) -> Any:
|
|
@@ -136,6 +139,28 @@ def conditions(cells: list[dict[str, Any]]) -> Conditions:
|
|
|
136
139
|
)
|
|
137
140
|
|
|
138
141
|
|
|
142
|
+
def weights(cells: list[dict[str, Any]]) -> dict[str, str]:
|
|
143
|
+
"""``{model: what its weights ran in}``: `ModelSpec.weights` where the spec states it, else the
|
|
144
|
+
cell's quantization as a bit width, else its dtype.
|
|
145
|
+
|
|
146
|
+
One value per row: a 4-bit figure beside a bf16 one in the same row is not a speedup.
|
|
147
|
+
"""
|
|
148
|
+
stated = {m.key: m.weights for m in MODELS if m.weights}
|
|
149
|
+
labels: dict[str, set[str]] = {}
|
|
150
|
+
for cell in cells:
|
|
151
|
+
key = cell["model"]["key"]
|
|
152
|
+
load = cell.get("load") or {}
|
|
153
|
+
label = stated.get(key, "")
|
|
154
|
+
if not label and (quantization := load.get("quantization")):
|
|
155
|
+
bits = re.search(r"(\d+)-bit", str(quantization))
|
|
156
|
+
label = f"{bits.group(1)}-bit" if bits else str(quantization)
|
|
157
|
+
if not label and (resolved := load.get("resolved_dtype")):
|
|
158
|
+
label = DTYPE_SHORT.get(resolved) or resolved
|
|
159
|
+
if label:
|
|
160
|
+
labels.setdefault(key, set()).add(label)
|
|
161
|
+
return {key: str(_only(found, f"what {key}'s weights ran in")) for key, found in labels.items()}
|
|
162
|
+
|
|
163
|
+
|
|
139
164
|
def rates(cells: list[dict[str, Any]]) -> dict[str, dict[str, dict[str, float | None]]]:
|
|
140
165
|
"""``{model: {variant: {regime: tok/s or None}}}``, in the sweep's row order."""
|
|
141
166
|
index = cells_by_key(cells)
|
|
@@ -164,11 +189,15 @@ def _padded(header: list[str], rows: list[list[str]]) -> list[str]:
|
|
|
164
189
|
return out
|
|
165
190
|
|
|
166
191
|
|
|
167
|
-
def _table(
|
|
192
|
+
def _table(
|
|
193
|
+
measured: dict[str, dict[str, dict[str, float | None]]],
|
|
194
|
+
regime: str,
|
|
195
|
+
precision: dict[str, str] | None = None,
|
|
196
|
+
) -> list[str]:
|
|
168
197
|
rows: list[list[str]] = []
|
|
169
198
|
for model_key, per_variant in measured.items():
|
|
170
199
|
baseline = per_variant[BASELINE][regime]
|
|
171
|
-
row = [f"`{model_key}`"]
|
|
200
|
+
row = [f"`{model_key}`", (precision or {}).get(model_key, MISSING_CELL)]
|
|
172
201
|
for variant_key, _, _ in COLUMNS:
|
|
173
202
|
value = per_variant[variant_key][regime]
|
|
174
203
|
if value is None:
|
|
@@ -179,12 +208,13 @@ def _table(measured: dict[str, dict[str, dict[str, float | None]]], regime: str)
|
|
|
179
208
|
text = f"{text} ({fmt_multiplier(value / baseline)})"
|
|
180
209
|
row.append(f"**{text}**" if variant_key == HIGHLIGHT else text)
|
|
181
210
|
rows.append(row)
|
|
182
|
-
return _padded(["model", *(heading for _, heading, _ in COLUMNS)], rows)
|
|
211
|
+
return _padded(["model", "weights", *(heading for _, heading, _ in COLUMNS)], rows)
|
|
183
212
|
|
|
184
213
|
|
|
185
214
|
def readme_block(cells: list[dict[str, Any]]) -> str:
|
|
186
215
|
where = conditions(cells)
|
|
187
216
|
measured = rates(cells)
|
|
217
|
+
precision = weights(cells)
|
|
188
218
|
return "\n".join(
|
|
189
219
|
[
|
|
190
220
|
"<!-- Generated by `python -m benchmarks.report_bench`. Do not edit: rerun the sweep. -->",
|
|
@@ -193,11 +223,11 @@ def readme_block(cells: list[dict[str, Any]]) -> str:
|
|
|
193
223
|
"",
|
|
194
224
|
"One stream (tok/s):",
|
|
195
225
|
"",
|
|
196
|
-
*_table(measured, "single"),
|
|
226
|
+
*_table(measured, "single", precision),
|
|
197
227
|
"",
|
|
198
228
|
f"{where.concurrency} concurrent requests (aggregate tok/s):",
|
|
199
229
|
"",
|
|
200
|
-
*_table(measured, "concurrent"),
|
|
230
|
+
*_table(measured, "concurrent", precision),
|
|
201
231
|
]
|
|
202
232
|
)
|
|
203
233
|
|
|
@@ -214,8 +214,9 @@ def _env_section(cells: list[dict[str, Any]]) -> list[str]:
|
|
|
214
214
|
|
|
215
215
|
def _models_section(cells: list[dict[str, Any]]) -> list[str]:
|
|
216
216
|
index = cells_by_key(cells)
|
|
217
|
+
precision = publish.weights(cells)
|
|
217
218
|
lines = [
|
|
218
|
-
"| model | HuggingFace id | family | params |
|
|
219
|
+
"| model | HuggingFace id | family | params | weights | layers | d_model |",
|
|
219
220
|
"| --- | --- | --- | --- | --- | --- | --- |",
|
|
220
221
|
]
|
|
221
222
|
for model_key in model_order(cells):
|
|
@@ -230,7 +231,8 @@ def _models_section(cells: list[dict[str, Any]]) -> list[str]:
|
|
|
230
231
|
load = cell.get("load", {})
|
|
231
232
|
lines.append(
|
|
232
233
|
f"| `{model_key}` | `{m['hf_id']}` | {m['family']} | {m['params']} | "
|
|
233
|
-
f"{
|
|
234
|
+
f"{precision.get(model_key) or m.get('native_dtype', '?')} | {load.get('n_layers', '?')} | "
|
|
235
|
+
f"{load.get('d_model', '?')} |"
|
|
234
236
|
)
|
|
235
237
|
return lines
|
|
236
238
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Generated by `python -m benchmarks.report_bench`. Do not edit by hand -- rerun the sweep.
|
|
4
4
|
|
|
5
5
|
- **Run** 2026-09-04 (cells from `2026-09-04T07:18:04+00:00` to `2026-09-04T08:24:40+00:00` UTC)
|
|
6
|
-
- **Report written** 2026-09-
|
|
6
|
+
- **Report written** 2026-09-24T04:33:12+00:00
|
|
7
7
|
- **Cells** 20
|
|
8
8
|
|
|
9
9
|
## What this measures
|
|
@@ -61,13 +61,13 @@ Taken from the deployed set rather than invented, and spanning the range a singl
|
|
|
61
61
|
hold: from a 2.6B dense trunk to a 291B sparse one. A model whose weights do not fit is
|
|
62
62
|
dropped from a plain sweep and named, rather than failing it (`ModelSpec.min_gpu_gib`).
|
|
63
63
|
|
|
64
|
-
| model | HuggingFace id | family | params |
|
|
64
|
+
| model | HuggingFace id | family | params | weights | layers | d_model |
|
|
65
65
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
66
|
-
| `gemma-2-2b` | `google/gemma-2-2b` | Gemma2 | 2.6B |
|
|
67
|
-
| `qwen3-4b` | `Qwen/Qwen3-4B` | Qwen3 | 4.0B |
|
|
68
|
-
| `llama-3.1-8b` | `meta-llama/Llama-3.1-8B` | Llama3 | 8.0B |
|
|
69
|
-
| `qwen3.8-27b` | `Qwen/Qwen3.8-27B` | Qwen3_5 | 27B |
|
|
70
|
-
| `deepseek-v4-flash-0731` | `deepseek-ai/DeepSeek-V4-Flash-0731` | DeepseekV4 | 291B (14B active) |
|
|
66
|
+
| `gemma-2-2b` | `google/gemma-2-2b` | Gemma2 | 2.6B | bf16 | 26 | 2304 |
|
|
67
|
+
| `qwen3-4b` | `Qwen/Qwen3-4B` | Qwen3 | 4.0B | bf16 | 36 | 2560 |
|
|
68
|
+
| `llama-3.1-8b` | `meta-llama/Llama-3.1-8B` | Llama3 | 8.0B | bf16 | 32 | 4096 |
|
|
69
|
+
| `qwen3.8-27b` | `Qwen/Qwen3.8-27B` | Qwen3_5 | 27B | bf16 | 64 | 5120 |
|
|
70
|
+
| `deepseek-v4-flash-0731` | `deepseek-ai/DeepSeek-V4-Flash-0731` | DeepseekV4 | 291B (14B active) | fp8 | 43 | 4096 |
|
|
71
71
|
|
|
72
72
|
### Where a row differs
|
|
73
73
|
|
|
@@ -7,6 +7,7 @@ If you are a human reading this: [USAGE.md](USAGE.md) is the friendlier introduc
|
|
|
7
7
|
[PORTING.md](PORTING.md) covers hook-name translation in more depth.
|
|
8
8
|
|
|
9
9
|
- [Version stamp](#version-stamp)
|
|
10
|
+
- [`capture` and `run_with_cache`](#capture-and-run_with_cache)
|
|
10
11
|
- [What changed in 1.3](#what-changed-in-13)
|
|
11
12
|
- [What changed in 1.1](#what-changed-in-11)
|
|
12
13
|
- [Pick a tier first](#pick-a-tier-first)
|
|
@@ -36,6 +37,18 @@ a minor version when keeping it would mean one of the two backends could not be
|
|
|
36
37
|
[What changed in 1.1](#what-changed-in-11) — and pinning `interp-engine~=1.3` is the way to not be
|
|
37
38
|
surprised by the next one.
|
|
38
39
|
|
|
40
|
+
## `capture` and `run_with_cache`
|
|
41
|
+
|
|
42
|
+
The sync single-forward capture is `capture(model, tokens, points)`. `run_with_cache` is an alias
|
|
43
|
+
with the same signature and the same `Cache`, kept for code and habits that came from
|
|
44
|
+
TransformerLens; nothing is deprecated and nothing raises. New code should say `capture`.
|
|
45
|
+
|
|
46
|
+
Two other things share the word. `await model.capture(...)` is the async *method* on the protocol:
|
|
47
|
+
one prompt, a plain dict keyed by `Address`, no batch axis. `interp_engine.capture` is the
|
|
48
|
+
*submodule* that holds `Cache` and the capture helpers; `from interp_engine.capture import Cache`
|
|
49
|
+
works as before, but after `import interp_engine` the attribute `interp_engine.capture` is the
|
|
50
|
+
function.
|
|
51
|
+
|
|
39
52
|
## What changed in 1.3
|
|
40
53
|
|
|
41
54
|
**"Freeze" is now "static", and the mode is a `backend=` value rather than a kwarg that turned it
|
|
@@ -86,7 +99,7 @@ edits and gains one capability.
|
|
|
86
99
|
| `OrthogonalProjector(...).get_P()` / `.get_orthogonal_complement()` | `.delta(acts)` / `.project(acts)` | The projection matrix was `d_model × d_model` materialized per call; the same arithmetic is one dot product. |
|
|
87
100
|
| `run_with_cache(model, ..., **forward_kwargs)` | *(gone)* | Passthrough kwargs were unreachable on vLLM, so a call using them silently meant something different per backend. |
|
|
88
101
|
|
|
89
|
-
What is new, rather than moved: `run_with_cache
|
|
102
|
+
What is new, rather than moved: `capture` (then still spelled `run_with_cache`), `capture_generation`, `capture_attention`,
|
|
90
103
|
`generate_stream`, `capture_residuals`, `layer_logits` and `steer` all take either backend now, and
|
|
91
104
|
`sync_model(model)` gives you the whole protocol without `await`. `ProjectionCapSpec` works on eager
|
|
92
105
|
(it used to raise `NotImplementedError`), so all three steering specs run on both backends.
|
|
@@ -145,8 +158,8 @@ Keyed on the pattern you find in the source you are porting.
|
|
|
145
158
|
| `LLM(model=id)` + you own that engine | `worker_extension_cls=WORKER_EXTENSION_CLS` | Tier 1. Merge `capture_engine_kwargs()`. |
|
|
146
159
|
| `LLM(model=id)` + you do not care how | `load_model(id)` | Tier 2. |
|
|
147
160
|
| `HookedTransformer.from_pretrained(id)` | `EagerModel.from_pretrained(id)` | Then translate hook names — next row. |
|
|
148
|
-
| `model.run_with_cache(...)["blocks.7.attn.hook_z"]` | `
|
|
149
|
-
| nnsight `with model.trace(...)` / `mlps_output[7]` | `nnsight_accessor_to_point("mlps_output[7]")` | Then `
|
|
161
|
+
| `model.run_with_cache(...)["blocks.7.attn.hook_z"]` | `capture(model, tokens, [tlens_hook_to_point("blocks.7.attn.hook_z", model)])` | **Pass the model.** See the warning below. |
|
|
162
|
+
| nnsight `with model.trace(...)` / `mlps_output[7]` | `nnsight_accessor_to_point("mlps_output[7]")` | Then `capture(model, ...)` or `await model.capture(...)`. |
|
|
150
163
|
| `model.unembed(model.ln_final(x))` | `await model.decode_residuals(x)` | The method normalizes softcapping across backends; the sync free function does not. |
|
|
151
164
|
| a hand-rolled `for _ in range(n): model(...)` decode loop | `generate_stream(model, tokens)` | Yields a `GenStep` per token with logprobs, and logits on eager (rule 12). |
|
|
152
165
|
| `asyncio.run(model.capture(...))` in a script, once per call | `sync = sync_model(model)`, then `sync.capture(...)` | One background loop for the model's lifetime, instead of a new one per call — which on vLLM would abandon the engine's tasks. |
|
|
@@ -205,7 +218,7 @@ an obvious one.
|
|
|
205
218
|
|
|
206
219
|
1. **Every model *method* is `async`,** including on eager where the work underneath is synchronous.
|
|
207
220
|
`model.capture(...)` without `await` gives you a coroutine, not activations. The sync free
|
|
208
|
-
functions (`
|
|
221
|
+
functions (`capture`, `capture_generation`, `capture_attention`, `generate_stream`,
|
|
209
222
|
`steer`, `layer_logits`) take either backend, and `sync_model(model)` mirrors the whole protocol
|
|
210
223
|
for the times you want a method. Neither can be called from inside a running event loop — they
|
|
211
224
|
refuse, naming the `await` to use instead, rather than deadlocking. The mirror image also
|
|
@@ -239,9 +252,9 @@ an obvious one.
|
|
|
239
252
|
7. **`await model.shutdown()` before loading a second model in the same process on vLLM.** Its KV
|
|
240
253
|
cache lives in a child process that dropping a Python reference does not reap. Idempotent, so
|
|
241
254
|
there is no cost to calling it always.
|
|
242
|
-
8. **`capture()` returns a plain dict keyed by `Address`, not by string.** You may ask with a string
|
|
255
|
+
8. **`await model.capture()` returns a plain dict keyed by `Address`, not by string.** You may ask with a string
|
|
243
256
|
or a `(name, layer)` tuple, but on the way back out use `to_address("resid_post.10")` as the key.
|
|
244
|
-
(`
|
|
257
|
+
(The free function `capture(model, ...)` returns a `Cache`, which accepts either.)
|
|
245
258
|
9. **Do not apply `final_logit_softcapping` after `await model.decode_residuals(...)`.** The method
|
|
246
259
|
applies the model's configured value so the two backends are comparable. The sync free function
|
|
247
260
|
`interp_engine.decode_residuals` is the raw one and takes `softcap` explicitly.
|
|
@@ -289,9 +302,9 @@ mistake you made.
|
|
|
289
302
|
|
|
290
303
|
## When you are not sure, ask the model
|
|
291
304
|
|
|
292
|
-
Capabilities are queryable
|
|
293
|
-
|
|
294
|
-
what keeps one code path working across both backends:
|
|
305
|
+
Capabilities are queryable and cheap: they read configuration rather than running a forward, and the
|
|
306
|
+
model-wide ones below are answerable **before** `warmup()`. Gate on these rather than on backend name
|
|
307
|
+
or model id, because that is what keeps one code path working across both backends:
|
|
295
308
|
|
|
296
309
|
```python
|
|
297
310
|
from interp_engine import load_model
|
|
@@ -304,6 +317,28 @@ model.residual_basis.lens_valid # is a logit-lens read-out meaningful here?
|
|
|
304
317
|
model.n_layers, model.d_model # remember z is n_heads * head_dim, not d_model
|
|
305
318
|
```
|
|
306
319
|
|
|
320
|
+
For one point rather than the whole model, ask `refuses()`. It returns the reason as a string, or
|
|
321
|
+
`None` when the point is servable, so a caller filtering a list gets to say which gap it hit rather
|
|
322
|
+
than logging "unavailable":
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
for point in ("resid_post", "router_logits", "q_norm_out"):
|
|
326
|
+
why = model.refuses(point, 12)
|
|
327
|
+
print(point, "ok" if why is None else f"skipped: {why}")
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Do not rebuild this from the point tables in these docs.** `SUPPORTED_POINTS.md` and
|
|
331
|
+
`ENGINE_HOOK_MAPPINGS.md` say what a *point* is, per backend; `refuses()` also knows what this
|
|
332
|
+
*checkpoint* carries and how this instance was built, which no table can. A serving pod that derived
|
|
333
|
+
its advertised set from a table instead shipped a 400 for attention-output SAEs that blamed the
|
|
334
|
+
paged-attention kernel, and a second one that narrowed by tensor-parallel shard width refused points
|
|
335
|
+
the worker gathers. Both read as maintained lists right up until they were wrong.
|
|
336
|
+
|
|
337
|
+
The one caveat is the order on vLLM: the architecture half of the answer lives in the worker
|
|
338
|
+
process, so it arrives at `warmup()`. Before that, vLLM's `refuses()` answers from the point table
|
|
339
|
+
and how the engine was built, which can say yes to a point the checkpoint has no module for. Warm up
|
|
340
|
+
before advertising. Eager holds its own module tree and needs no warmup.
|
|
341
|
+
|
|
307
342
|
For a coarse "which surface do I have", `isinstance(model, InterpModel)` works —
|
|
308
343
|
but note it checks method *presence* only, not signatures:
|
|
309
344
|
|
|
@@ -311,7 +311,7 @@ must split", on a family whose `fused_qkv` is false — so `value` no longer ask
|
|
|
311
311
|
**Reading a norm costs a rank, and the point does not pay it.** A norm over `head_dim` can only be
|
|
312
312
|
given the per-head view, so both engines hand `v_norm` a `(…, n_kv_heads, head_dim)` tensor where a
|
|
313
313
|
`v_proj` would have produced the flat one — and `value` would then be 4-D on this family and 3-D on the
|
|
314
|
-
next, against a `Width.HEADS` declaration and `
|
|
314
|
+
next, against a `Width.HEADS` declaration and `capture`'s `[batch, seq, width]`. Flattened back
|
|
315
315
|
on both sides (`hooks.flat_per_head`, `vllm_capture._hooks.flat_value`), including on the way *into* a
|
|
316
316
|
steer, so a vector measured on a capture of `value` is the shape the write expects; the module gets its
|
|
317
317
|
own rank back before the attention reshapes it. Eagerly the head count is checked rather than assumed,
|
|
@@ -501,7 +501,7 @@ router_logits)`. `facts.ROUTER_OUTPUTS` holds the exceptions; `facts.assert_rout
|
|
|
501
501
|
- **A fused kernel can leave the router module unused.** transformers' MXFP4 path for gpt-oss replaces
|
|
502
502
|
the _block's_ forward and routes inline (`F.linear` on `self.router.weight`, then a Triton top-k), so
|
|
503
503
|
the router is in the tree, correct, and never called. `arch.moe_router` detects the replaced forward
|
|
504
|
-
and refuses with the remedy (`Mxfp4Config(dequantize=True)`); more generally, `
|
|
504
|
+
and refuses with the remedy (`Mxfp4Config(dequantize=True)`); more generally, `capture` now
|
|
505
505
|
raises when a resolved module did not run, rather than returning a short cache. `router_logits` is the
|
|
506
506
|
exception, and it does not need the remedy: `mlp_forward` returns `(routed_out, router_logits)`, so the
|
|
507
507
|
point resolves to the _block's_ `output:1` instead (`arch.inline_routing_logits`) and is bit-identical
|
|
@@ -510,7 +510,7 @@ router_logits)`. `facts.ROUTER_OUTPUTS` holds the exceptions; `facts.assert_rout
|
|
|
510
510
|
index — a four-wide top-k against the logits' 32 — and gpt-oss carries a second, unverified kernel
|
|
511
511
|
swap (`@use_kernel_forward_from_hub("MegaBlocksMoeMLP")`) whose layout nothing here has confirmed.
|
|
512
512
|
`expert_weights` and `expert_indices` have no boundary on that path even so, and are _rebuilt_ from
|
|
513
|
-
those logits by `
|
|
513
|
+
those logits by `capture` (`interp_engine.moe_routing`) — the one place a routing convention is
|
|
514
514
|
reimplemented, allowed because the derivation is a top-k on an already-captured tensor and because it
|
|
515
515
|
is verified against `GptOssTopKRouter` on the real checkpoint rather than read off the source. They
|
|
516
516
|
still refuse from `resolve_point`, which answers with addresses and has none to give.
|
|
@@ -811,8 +811,9 @@ Two things a randomly-initialised model cannot show you, both worth knowing when
|
|
|
811
811
|
- **`head_dim ≠ d_model/n_heads`** on Gemma (GQA + explicit `head_dim`): the attention-output /
|
|
812
812
|
`z` width is `n_heads*head_dim`, not `d_model`. Attention-SAE code must use `head_dim`.
|
|
813
813
|
- **Embedding scaling** (Gemma multiplies embeddings by `sqrt(d_model)`): free when you hook real
|
|
814
|
-
modules
|
|
815
|
-
|
|
814
|
+
modules. When you feed embeddings _in_ (`generate_steps_from_embeds`, which NLA's concept
|
|
815
|
+
injection rides), the rows must carry the scale already — they are the `embeddings` point, and
|
|
816
|
+
the NLA path's `resolve_embed_scale` applies it.
|
|
816
817
|
- **Multimodal `*ForConditionalGeneration`**: dims live under `config.text_config` (top-level are
|
|
817
818
|
`None`); the text trunk nests under `model.language_model`. `resolve_arch` handles both; just
|
|
818
819
|
don't assume top-level `num_hidden_layers`. The vLLM worker resolves its _own_ module handles
|
|
@@ -126,11 +126,11 @@ The norm's _boundaries_ are `resid_mid` (input) and `mlp_in` (output), and the e
|
|
|
126
126
|
for you rather than making you reproduce TransformerLens' `RMSNorm.forward` from memory:
|
|
127
127
|
|
|
128
128
|
```python
|
|
129
|
-
from interp_engine import pre_gain_normalized, rms_norm_eps_for_model,
|
|
129
|
+
from interp_engine import capture, pre_gain_normalized, rms_norm_eps_for_model, tlens_normalized_hook
|
|
130
130
|
|
|
131
131
|
address = tlens_normalized_hook("blocks.19.ln2.hook_normalized") # -> Address("resid_mid", 19)
|
|
132
132
|
eps = rms_norm_eps_for_model(model) # None on a LayerNorm family, where this equation does not apply
|
|
133
|
-
cache =
|
|
133
|
+
cache = capture(model, tokens, [address])
|
|
134
134
|
normalized = pre_gain_normalized(cache[address], eps)
|
|
135
135
|
```
|
|
136
136
|
|
|
@@ -246,7 +246,7 @@ module is present and never called. `router_logits` survives that anyway — `ml
|
|
|
246
246
|
`return routed_out, router_logits`, so the logits the kernel routed on leave the block, and the point
|
|
247
247
|
resolves to the block's own `output:1`, bit-identical to `F.linear(hidden, router.weight, router.bias)`.
|
|
248
248
|
`expert_weights` and `expert_indices` have no boundary there at all — the selection happens inside the
|
|
249
|
-
kernel — so on that path they are **rebuilt** from those logits by `
|
|
249
|
+
kernel — so on that path they are **rebuilt** from those logits by `capture`
|
|
250
250
|
(`interp_engine.moe_routing`), and `resolve_point` still refuses them because there is no address to
|
|
251
251
|
return. The index the logits come off is allowlisted per replacement (`facts.INLINE_ROUTING_FORWARDS`)
|
|
252
252
|
rather than assumed, because `output:1` on the _un-replaced_ gpt-oss block is `router_scores` — the
|
|
@@ -422,12 +422,13 @@ Both mechanisms carry the optional `stream` coordinate, which on a write means o
|
|
|
422
422
|
in one row of a stack the worker already holds — a weaker claim than the one a read makes, which is why
|
|
423
423
|
vLLM serves it here while still refusing to *read* a single stream through the resid points.
|
|
424
424
|
|
|
425
|
-
A
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
on this family at all — `resid_post` there names the whole stack,
|
|
429
|
-
a swap wants is a collapse. The global `set_lens_intervention`
|
|
430
|
-
and refuses a spec that names a point, rather than ignoring
|
|
425
|
+
A lens steer/ablate/swap is a `steer()` block of `NormScaledAddSpec` / `AblateSpec` / `SwapSpec` ops
|
|
426
|
+
and goes through the same gate and the same sites: the spec's `point` (`resid_post` by default, which
|
|
427
|
+
is what it has always meant on a conventional trunk) is where the per-request path keys the write.
|
|
428
|
+
That is what makes the lens usable on this family at all — `resid_post` there names the whole stack,
|
|
429
|
+
which no sublayer reads, so the tensor a swap wants is a collapse. The global `set_lens_intervention`
|
|
430
|
+
stays pinned to the decoder layer's output and refuses a spec that names a point, rather than ignoring
|
|
431
|
+
it.
|
|
431
432
|
|
|
432
433
|
### On the AMD and XPU trees they are ordinary modules
|
|
433
434
|
|
|
@@ -26,7 +26,7 @@ gradient *of the model* is eager-only.
|
|
|
26
26
|
|
|
27
27
|
```python
|
|
28
28
|
model = EagerModel("openai-community/gpt2", requires_grad=True) # default is False
|
|
29
|
-
cache =
|
|
29
|
+
cache = capture(model, input_ids, [("resid_post", 5)], detach=False)
|
|
30
30
|
cache.get("resid_post", 5).sum().backward()
|
|
31
31
|
model.arch.embed.weight.grad # populated: the graph reached the embedding
|
|
32
32
|
```
|
|
@@ -25,7 +25,8 @@ files and why they are where they are.
|
|
|
25
25
|
`sync.py`; `tests/test_sync_parity.py` fails on a missing twin.
|
|
26
26
|
- `sync.py` / `_loop.py` — `sync_model(model)`: the protocol without an event loop, one explicit
|
|
27
27
|
wrapper per method over a background loop thread that is created lazily, reused per model, and
|
|
28
|
-
refuses rather than deadlocks when called from inside a running loop.
|
|
28
|
+
refuses rather than deadlocks when called from inside a running loop. Each call runs in a copy of
|
|
29
|
+
the caller's context, so an open `steer()` block is what the method reads on the loop thread.
|
|
29
30
|
- `dispatch.py` — the shared plumbing every free function's two arms sit on: token coercion
|
|
30
31
|
(`TokensLike`, batch refusals) and `CAPABILITIES`, the table each `CapabilityUnsupported` message
|
|
31
32
|
is built from.
|
|
@@ -56,7 +57,11 @@ files and why they are where they are.
|
|
|
56
57
|
`tests/test_steer_math_parity.py` runs against it on CPU; `steer()` is the context both backends
|
|
57
58
|
take, registering per-request on vLLM rather than installing a global hook. The methods are named
|
|
58
59
|
once, in `steer_specs.SteerMethod`: the eager `SteerSpec.method` and the worker dict's `op` both
|
|
59
|
-
read from it, so a method has one spelling on either backend.
|
|
60
|
+
read from it, so a method has one spelling on either backend. A block's
|
|
61
|
+
`position_mask` and `generated=False` are placed by `forward_from` on eager, which every engine
|
|
62
|
+
loop declares, and travel with the request on vLLM (`worker_register_steering`'s
|
|
63
|
+
`skip_positions` / `steer_generated`); `tests/test_steer_scope.py` checks the rows on eager, in
|
|
64
|
+
closed form, and `tests/test_vllm_capture_gpu.py` holds vLLM to them.
|
|
60
65
|
- `mappers.py` — translation between canonical points and other frameworks' names:
|
|
61
66
|
TransformerLens hook strings and nnsight/nnterp accessors, both directions. See [Porting from
|
|
62
67
|
TransformerLens, nnsight or nnterp](PORTING.md#porting-from-transformerlens-nnsight-or-nnterp).
|
|
@@ -24,6 +24,8 @@ the cache normally. Worth ~1.75x on time-to-first-token for a repeated long pref
|
|
|
24
24
|
pins which requests opt out; `tests/test_vllm_capture_gpu.py` pins the effect on a real engine,
|
|
25
25
|
including a control that reproduces the short capture when the salt is removed. Pass
|
|
26
26
|
`extra_vllm_kwargs={"enable_prefix_caching": False}` to get the old engine-wide behaviour back.
|
|
27
|
+
An engine with `enable_prompt_embeds=True` (LoRA reads, NLA) keeps it on too: vLLM hashes each
|
|
28
|
+
block's embeds rows into its key, and embeds requests take the same global-intervention salt.
|
|
27
29
|
|
|
28
30
|
One further flag is set for you, and only on the architectures that cannot boot without it.
|
|
29
31
|
`kv_cache_dtype` is left to vLLM everywhere else, but a few families serve attention through a KV
|
|
@@ -110,9 +112,9 @@ What is genuinely **free** — do not attribute slowness to these:
|
|
|
110
112
|
`enforce_eager` is required *while capturing*, not permanently, so a generation-only instance
|
|
111
113
|
should turn it off — preferably as `backend="vllm-generate"`, which is the same engine configuration
|
|
112
114
|
reached through a name that says what it gives up. There is already a precedent in-tree:
|
|
113
|
-
`apps/nla/
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
Neuronpedia's `apps/nla/verbalizer.py` loads `backend="vllm-generate"` (CUDA graphs + inductor on)
|
|
116
|
+
precisely because the verbalizer installs no hooks, with `NLA_VERBALIZER_ENFORCE_EAGER=1` as a
|
|
117
|
+
debug escape hatch back to the hooked engine. It also passes
|
|
116
118
|
`compilation_config.cudagraph_capture_sizes` to match graph capture to its expected fan-out.
|
|
117
119
|
|
|
118
120
|
What it costs, measured (`benchmarks/results-latest.md`, and gpt2 separately for the graph modes).
|
|
@@ -164,9 +166,9 @@ them:
|
|
|
164
166
|
defaults to `"auto"`: `resid_post` at every layer, to read *and* to write, via static `copy_`
|
|
165
167
|
taps. Turns Dynamo off (`VLLM_USE_BREAKABLE_CUDAGRAPH=1`), so the win is replay without compile.
|
|
166
168
|
Because breakable `add_eager` keeps the wrap as ordinary PyTorch on the live tensor, the write path
|
|
167
|
-
is not limited to additive — `orthogonal`, `projection_cap` and the
|
|
168
|
-
ops all ride the same wrap, per-request and with `position_mask`
|
|
169
|
-
(`register_static_write`). An op outside that set is refused rather than silently skipped. Auto
|
|
169
|
+
is not limited to additive — `orthogonal`, `projection_cap` and the lens's
|
|
170
|
+
`norm_scaled_add`/`ablate`/`swap` ops all ride the same wrap, per-request and with `position_mask`
|
|
171
|
+
and `generated=False` honoured (`register_static_write`). An op outside that set is refused rather than silently skipped. Auto
|
|
170
172
|
covers the write because the two halves are one decision: a read tap alone serves the lens
|
|
171
173
|
read-out and refuses every steer, ablation and swap derived from it, at an address already tapped.
|
|
172
174
|
Pass `static_writes=[]` for the reads without the write sites, and an explicit `static_points`
|