interp-engine 1.7.2__tar.gz → 1.7.3__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.7.2 → interp_engine-1.7.3}/PKG-INFO +1 -1
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/USAGE.md +3 -1
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/tokenize.py +78 -2
- {interp_engine-1.7.2 → interp_engine-1.7.3}/pyproject.toml +1 -1
- interp_engine-1.7.3/tests/test_injected_system_spans.py +115 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/.gitignore +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/LICENSE +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/README.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/README.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/__init__.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/bench_spec.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/cells.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/probe.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/probe_lens_stream.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/publish.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/report_bench.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/deepseek-v4-flash-0731__eager.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/deepseek-v4-flash-0731__vllm-cudagraph.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/deepseek-v4-flash-0731__vllm-static.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/deepseek-v4-flash-0731__vllm.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/gemma-2-2b__eager.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/gemma-2-2b__vllm-cudagraph.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/gemma-2-2b__vllm-static.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/gemma-2-2b__vllm.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/llama-3.1-8b__eager.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/llama-3.1-8b__vllm-cudagraph.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/llama-3.1-8b__vllm-static.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/llama-3.1-8b__vllm.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3-4b__eager.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3-4b__vllm-cudagraph.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3-4b__vllm-static.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3-4b__vllm.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3.8-27b__eager.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3.8-27b__vllm-cudagraph.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3.8-27b__vllm-static.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3.8-27b__vllm.json +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results-latest.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/run_all.sh +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/run_bench.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/workloads.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/AGENT_INTEGRATION.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/ARCHITECTURE_QUIRKS.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/COMPATIBILITY.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/ENGINE_HOOK_MAPPINGS.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/GRADIENTS.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/INTERNALS.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/PERFORMANCE.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/PORTING.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/README.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/docs/SUPPORTED_POINTS.md +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/__init__.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/_loop.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/address.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/arch.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/attn_config.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/attn_scores.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/autograd_support.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/capture.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/chat_compose.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/chat_conventions.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/chat_formatters.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/cuda_preflight.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/dispatch.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/facts.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/hooks.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/lens.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/load.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/mappers.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/memory.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/model.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/moe_routing.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/notebook_stdout.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/points.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/protocol.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/residual_basis.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/select.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/steer.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/steer_specs.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/sync.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_backend.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/__init__.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/_demux.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/_hooks.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/_payload.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/_tree.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/attn.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/capture.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/graphs.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/lens/__init__.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/lens/intervene.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/lens/readout.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/lens/unembed.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/mhc.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/native.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/requests.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/static.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_capture/steering.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/interp_engine/vllm_plugin.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/conftest.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/harness.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/model_expectations.yaml +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/synthetic_families.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_address.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_attn_config_tripwire.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_attn_probs_indexing.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_attn_scores.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_attn_z_gqa.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_autograd_support.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_bench_workloads.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_capability_refusals.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_capture_addressing.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_chat_compose.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_chat_formatters.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_chat_templates.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_core.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_cuda_preflight.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_doc_code_fences.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_eager_autograd.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_facts.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_family_points.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_gated_attn_out.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_gpu_sizer.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_head_contributions.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_hook_call_conventions.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_layer_kinds.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_load.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_logit_transform.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_mappers.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_memory.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_mlp_internals.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_model_expectations.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_moe.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_multimodal_arch.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_new_models_gpu.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_no_chat_template.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_normalized_hook.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_notebook_stdout.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_packaging.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_parity_gpt2.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_per_layer_attn_dims.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_points_registry.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_protocol.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_published_benchmarks.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_qk_norm.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_qkv_layout.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_reasoning_spans.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_release.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_resid_mid.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_residual_basis.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_sandwich_norms.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_select.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_sliding_window_attn.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_small_models_gpu.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_static_dsv4_gpu.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_static_parity_gpu.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_static_set.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_static_warmup.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_steer_context.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_steer_math_parity.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_sync_loop.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_sync_parity.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_unified_free_functions.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_unresolved_families.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_capture_gpu.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_capture_scales.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_engine_loop.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_graph_path.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_graphs_on_gpu.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_hook_availability.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_hyper_connections.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_kv_isolation.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_new_points.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_only_families.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_plugin.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vllm_wire_grammar.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_vocabulary_boundary.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/tests/test_worker_lens_capture_readout.py +0 -0
- {interp_engine-1.7.2 → interp_engine-1.7.3}/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.7.
|
|
3
|
+
Version: 1.7.3
|
|
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
|
|
@@ -215,7 +215,9 @@ one entry in `chat_formatters.CODE_CHAT_FORMATS`.
|
|
|
215
215
|
|
|
216
216
|
To attribute *tokens* to messages there are two methods, and the difference matters. `message_spans`
|
|
217
217
|
gives per-token role, channel and section (`header` / `content` / `footer`), leaving the trailing
|
|
218
|
-
generation scaffold owned by no message — use it to read or display structure.
|
|
218
|
+
generation scaffold owned by no message — use it to read or display structure. A system turn the
|
|
219
|
+
template injects on its own (Llama's knowledge-cutoff preamble, Qwen2.5's default persona) is
|
|
220
|
+
tagged `role="system"` with `message_index=None`, so it can be shown as its own turn. `message_partition`
|
|
219
221
|
gives one contiguous `[start, end)` span per message that together cover every token, which is what
|
|
220
222
|
mean-pooling activations per turn needs:
|
|
221
223
|
|
|
@@ -371,6 +371,10 @@ class Tokenize:
|
|
|
371
371
|
- Within a message block, ``header`` / ``content`` / ``footer`` are separated by rendering
|
|
372
372
|
the same message with EMPTY content and diffing (common prefix = header wrapper, common
|
|
373
373
|
suffix = footer wrapper); the middle is the real content.
|
|
374
|
+
- A system turn the template injects on its own (Llama's knowledge-cutoff preamble, Qwen2.5's
|
|
375
|
+
default persona) when the caller sent no system message is tagged ``role="system"`` with
|
|
376
|
+
``message_index=None``, so the frontend can show it as its own turn instead of folding it
|
|
377
|
+
into the first message's header. See :meth:`_injected_system_split`.
|
|
374
378
|
- The trailing ``add_generation_prompt`` scaffold is attributed to the (pending) assistant
|
|
375
379
|
turn as an ``assistant`` ``header`` so the assistant bubble opens before any content.
|
|
376
380
|
|
|
@@ -409,8 +413,8 @@ class Tokenize:
|
|
|
409
413
|
prefix_end: list[int] = []
|
|
410
414
|
for j in range(0, n + 1):
|
|
411
415
|
if j == 0:
|
|
412
|
-
# Empty message list:
|
|
413
|
-
#
|
|
416
|
+
# Empty message list: the leading scaffold (BOS / injected system turn) is split
|
|
417
|
+
# off message 0's block below. Rendering [] would also make some templates raise.
|
|
414
418
|
prefix_end.append(0)
|
|
415
419
|
continue
|
|
416
420
|
if rendered is not None:
|
|
@@ -438,6 +442,18 @@ class Tokenize:
|
|
|
438
442
|
msg_idx: list[int | None] = [None] * total
|
|
439
443
|
sections: list[str] = ["scaffold"] * total
|
|
440
444
|
|
|
445
|
+
# A system turn the template injected itself sits at the head of message 0's block.
|
|
446
|
+
# Tag it as its own turn (no message index) and start message 0 after it.
|
|
447
|
+
injected = None
|
|
448
|
+
if rendered is None and n > 0:
|
|
449
|
+
injected = self._injected_system_split(messages, full_ids, prefix_end[1], **template_kwargs)
|
|
450
|
+
if injected is not None:
|
|
451
|
+
hdr, cnt, ftr = injected
|
|
452
|
+
for pos in range(hdr + cnt + ftr):
|
|
453
|
+
roles[pos] = "system"
|
|
454
|
+
sections[pos] = "header" if pos < hdr else ("content" if pos < hdr + cnt else "footer")
|
|
455
|
+
prefix_end[0] = hdr + cnt + ftr
|
|
456
|
+
|
|
441
457
|
for k in range(n):
|
|
442
458
|
start, end = prefix_end[k], prefix_end[k + 1]
|
|
443
459
|
if end <= start:
|
|
@@ -484,6 +500,66 @@ class Tokenize:
|
|
|
484
500
|
for pos, tid in enumerate(full_ids)
|
|
485
501
|
]
|
|
486
502
|
|
|
503
|
+
def _injected_system_split(
|
|
504
|
+
self,
|
|
505
|
+
messages: list[dict[str, str]],
|
|
506
|
+
full_ids: list[int],
|
|
507
|
+
block_end: int,
|
|
508
|
+
**template_kwargs: Any,
|
|
509
|
+
) -> tuple[int, int, int] | None:
|
|
510
|
+
"""Return ``(header_len, content_len, footer_len)`` of a system turn the template injected
|
|
511
|
+
at the head of ``full_ids``, or ``None`` when there is none.
|
|
512
|
+
|
|
513
|
+
Only applies when the caller sent no system message. The template is asked to render the
|
|
514
|
+
same first message behind an explicit system message holding a probe string, and that
|
|
515
|
+
render is aligned against message 0's real block ``full_ids[:block_end]``:
|
|
516
|
+
|
|
517
|
+
- The common prefix is the system header (Llama's knowledge-cutoff preamble lands here,
|
|
518
|
+
since the template writes it before any system content).
|
|
519
|
+
- The common suffix is the system footer plus the whole first-message block, which is
|
|
520
|
+
identical in both renders. The block's own length comes from rendering the system
|
|
521
|
+
message alone, so the footer is what remains.
|
|
522
|
+
- Whatever sits between in the real block is the injected content (Qwen2.5's default
|
|
523
|
+
persona); in the probe render it must be the probe itself, which is what proves the
|
|
524
|
+
alignment found the system slot and not some other shared token.
|
|
525
|
+
|
|
526
|
+
Templates that do not inject a system turn (Qwen3), fold the system message into the
|
|
527
|
+
first user turn (Gemma-3, Mistral) or reject the system role (Gemma-2) fail one of these
|
|
528
|
+
checks and get ``None``, leaving message 0's block as it was.
|
|
529
|
+
"""
|
|
530
|
+
if messages[0].get("role") == "system":
|
|
531
|
+
return None
|
|
532
|
+
probe = "system-content-probe"
|
|
533
|
+
system = {"role": "system", "content": probe}
|
|
534
|
+
try:
|
|
535
|
+
sys_only = self._render_ids(
|
|
536
|
+
[system], add_generation_prompt=False, continue_final_message=False, **template_kwargs
|
|
537
|
+
)
|
|
538
|
+
probed = self._render_ids(
|
|
539
|
+
[system, messages[0]], add_generation_prompt=False, continue_final_message=False, **template_kwargs
|
|
540
|
+
)
|
|
541
|
+
except Exception: # noqa: BLE001 - some templates reject the system role
|
|
542
|
+
return None
|
|
543
|
+
if not sys_only or len(sys_only) >= len(probed) or probed[: len(sys_only)] != sys_only:
|
|
544
|
+
return None
|
|
545
|
+
block = full_ids[:block_end]
|
|
546
|
+
first_len = len(probed) - len(sys_only)
|
|
547
|
+
sys_end = len(block) - first_len
|
|
548
|
+
if sys_end <= 0:
|
|
549
|
+
return None
|
|
550
|
+
suffix = _common_suffix_len(probed, block)
|
|
551
|
+
if suffix < first_len:
|
|
552
|
+
return None
|
|
553
|
+
footer_len = suffix - first_len
|
|
554
|
+
header_len = min(_common_prefix_len(probed, block), sys_end - footer_len)
|
|
555
|
+
content_len = sys_end - footer_len - header_len
|
|
556
|
+
if content_len < 0:
|
|
557
|
+
return None
|
|
558
|
+
slot = probed[header_len : len(probed) - suffix]
|
|
559
|
+
if probe not in self.tokenizer.decode(slot, clean_up_tokenization_spaces=False):
|
|
560
|
+
return None
|
|
561
|
+
return header_len, content_len, footer_len
|
|
562
|
+
|
|
487
563
|
def _header_footer_split(
|
|
488
564
|
self,
|
|
489
565
|
messages: list[dict[str, str]],
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""A system turn the chat template injects on its own gets its own spans.
|
|
2
|
+
|
|
3
|
+
Llama 3.x writes a knowledge-cutoff preamble and Qwen2.5 a default persona into a system turn
|
|
4
|
+
whenever the caller sends no system message. ``Tokenize.message_spans`` tags that turn
|
|
5
|
+
``role="system"`` with no message index, so a frontend can show it as its own bubble instead of
|
|
6
|
+
folding it into the first user message's header. Templates that inject nothing (Qwen3) or fold
|
|
7
|
+
the system role into the first user turn (Gemma-3) must be left exactly as they were.
|
|
8
|
+
|
|
9
|
+
Tokenizer-only: nothing here runs a forward, so the (ungated) tokenizer repos are enough.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from functools import cache
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
from transformers import AutoTokenizer
|
|
18
|
+
|
|
19
|
+
from interp_engine.tokenize import Tokenize, TokenSpan
|
|
20
|
+
|
|
21
|
+
LLAMA = "unsloth/Llama-3.2-1B-Instruct"
|
|
22
|
+
QWEN25 = "Qwen/Qwen2.5-0.5B-Instruct"
|
|
23
|
+
QWEN3 = "Qwen/Qwen3-0.6B"
|
|
24
|
+
GEMMA3 = "unsloth/gemma-3-1b-it"
|
|
25
|
+
|
|
26
|
+
USER = [{"role": "user", "content": "What is 2+2?"}]
|
|
27
|
+
SYSTEM_THEN_USER = [{"role": "system", "content": "Be terse."}, *USER]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@cache
|
|
31
|
+
def _tok(repo: str) -> Tokenize:
|
|
32
|
+
try:
|
|
33
|
+
return Tokenize(AutoTokenizer.from_pretrained(repo))
|
|
34
|
+
except Exception as exc: # noqa: BLE001 - offline / uncached
|
|
35
|
+
pytest.skip(f"could not load tokenizer {repo}: {exc}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _injected(spans: list[TokenSpan]) -> list[TokenSpan]:
|
|
39
|
+
return [s for s in spans if s.role == "system" and s.message_index is None]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _text(spans: list[TokenSpan]) -> str:
|
|
43
|
+
return "".join(s.token_str for s in spans)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize("repo", [LLAMA, QWEN25])
|
|
47
|
+
def test_injected_system_turn_is_its_own_span_run(repo: str):
|
|
48
|
+
tok = _tok(repo)
|
|
49
|
+
spans = tok.message_spans(USER, add_generation_prompt=True)
|
|
50
|
+
injected = _injected(spans)
|
|
51
|
+
assert injected, "the template injects a system turn, so one must be tagged"
|
|
52
|
+
# It is one contiguous run at the very start, closed by a footer.
|
|
53
|
+
assert [s.position for s in injected] == list(range(len(injected)))
|
|
54
|
+
assert injected[0].section == "header"
|
|
55
|
+
assert injected[-1].section == "footer"
|
|
56
|
+
# Message 0 is the user turn, and it starts right after the injected turn.
|
|
57
|
+
user = [s for s in spans if s.message_index == 0]
|
|
58
|
+
assert user and all(s.role == "user" for s in user)
|
|
59
|
+
assert user[0].position == len(injected)
|
|
60
|
+
assert "2+2" in _text([s for s in user if s.section == "content"]).replace(" ", "")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_llama_preamble_is_the_system_header():
|
|
64
|
+
"""Llama writes the knowledge-cutoff lines before any system content, so they are header."""
|
|
65
|
+
spans = _tok(LLAMA).message_spans(USER, add_generation_prompt=True)
|
|
66
|
+
injected = _injected(spans)
|
|
67
|
+
header = _text([s for s in injected if s.section == "header"])
|
|
68
|
+
assert "Cutting Knowledge Date" in header
|
|
69
|
+
assert not any(s.section == "content" for s in injected)
|
|
70
|
+
assert _text([s for s in injected if s.section == "footer"]) == "<|eot_id|>"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_qwen25_default_persona_is_the_system_content():
|
|
74
|
+
spans = _tok(QWEN25).message_spans(USER, add_generation_prompt=True)
|
|
75
|
+
injected = _injected(spans)
|
|
76
|
+
assert _text([s for s in injected if s.section == "header"]) == "<|im_start|>system\n"
|
|
77
|
+
assert _text([s for s in injected if s.section == "content"]).startswith("You are Qwen")
|
|
78
|
+
assert _text([s for s in injected if s.section == "footer"]) == "<|im_end|>\n"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("repo", [LLAMA, QWEN25, QWEN3, GEMMA3])
|
|
82
|
+
def test_explicit_system_message_is_message_zero(repo: str):
|
|
83
|
+
"""A caller-supplied system message keeps its index; nothing is tagged as injected."""
|
|
84
|
+
spans = _tok(repo).message_spans(SYSTEM_THEN_USER, add_generation_prompt=True)
|
|
85
|
+
assert not _injected(spans)
|
|
86
|
+
assert any(s.message_index == 0 for s in spans)
|
|
87
|
+
assert any(s.message_index == 1 and s.role == "user" for s in spans)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.parametrize("repo", [QWEN3, GEMMA3])
|
|
91
|
+
def test_templates_without_an_injected_turn_are_unchanged(repo: str):
|
|
92
|
+
spans = _tok(repo).message_spans(USER, add_generation_prompt=True)
|
|
93
|
+
assert not _injected(spans)
|
|
94
|
+
assert spans[0].message_index == 0
|
|
95
|
+
assert spans[0].role == "user"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.parametrize("repo", [LLAMA, QWEN25, QWEN3, GEMMA3])
|
|
99
|
+
def test_spans_still_align_with_the_tokenized_prompt(repo: str):
|
|
100
|
+
tok = _tok(repo)
|
|
101
|
+
for messages in (USER, SYSTEM_THEN_USER):
|
|
102
|
+
spans = tok.message_spans(messages, add_generation_prompt=True)
|
|
103
|
+
ids = tok.apply_chat_template(messages, add_generation_prompt=True, tokenize=True)
|
|
104
|
+
assert [s.token_id for s in spans] == list(ids)
|
|
105
|
+
assert [s.position for s in spans] == list(range(len(spans)))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_injected_turn_survives_a_prefill():
|
|
109
|
+
"""The split only touches the head of the sequence; an open final turn stays open."""
|
|
110
|
+
tok = _tok(LLAMA)
|
|
111
|
+
messages = [*USER, {"role": "assistant", "content": "It is"}]
|
|
112
|
+
spans = tok.message_spans(messages, add_generation_prompt=False, continue_final_message=True)
|
|
113
|
+
assert _injected(spans)
|
|
114
|
+
prefill = [s for s in spans if s.message_index == 1]
|
|
115
|
+
assert prefill and prefill[-1].section == "content"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/deepseek-v4-flash-0731__eager.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/deepseek-v4-flash-0731__vllm.json
RENAMED
|
File without changes
|
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/gemma-2-2b__vllm-cudagraph.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/llama-3.1-8b__vllm-cudagraph.json
RENAMED
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/llama-3.1-8b__vllm-static.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3-4b__vllm-cudagraph.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3.8-27b__vllm-cudagraph.json
RENAMED
|
File without changes
|
{interp_engine-1.7.2 → interp_engine-1.7.3}/benchmarks/results/qwen3.8-27b__vllm-static.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|