interp-engine 1.3.5__tar.gz → 1.3.6__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 (176) hide show
  1. {interp_engine-1.3.5 → interp_engine-1.3.6}/PKG-INFO +1 -1
  2. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/facts.py +29 -0
  3. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_backend.py +1 -0
  4. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/static.py +9 -2
  5. {interp_engine-1.3.5 → interp_engine-1.3.6}/pyproject.toml +1 -1
  6. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_static_set.py +27 -0
  7. {interp_engine-1.3.5 → interp_engine-1.3.6}/.gitignore +0 -0
  8. {interp_engine-1.3.5 → interp_engine-1.3.6}/LICENSE +0 -0
  9. {interp_engine-1.3.5 → interp_engine-1.3.6}/README.md +0 -0
  10. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/README.md +0 -0
  11. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/__init__.py +0 -0
  12. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/bench_spec.py +0 -0
  13. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/cells.py +0 -0
  14. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/probe.py +0 -0
  15. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/probe_lens_stream.py +0 -0
  16. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/publish.py +0 -0
  17. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/report_bench.py +0 -0
  18. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/deepseek-v4-flash-0731__eager.json +0 -0
  19. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/deepseek-v4-flash-0731__vllm-cudagraph.json +0 -0
  20. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/deepseek-v4-flash-0731__vllm-dspark-cudagraph.json +0 -0
  21. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/deepseek-v4-flash-0731__vllm-dspark.json +0 -0
  22. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/deepseek-v4-flash-0731__vllm-static.json +0 -0
  23. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/deepseek-v4-flash-0731__vllm.json +0 -0
  24. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/gemma-2-2b__eager.json +0 -0
  25. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/gemma-2-2b__vllm-cudagraph.json +0 -0
  26. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/gemma-2-2b__vllm-static.json +0 -0
  27. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/gemma-2-2b__vllm.json +0 -0
  28. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/llama-3.1-8b__eager.json +0 -0
  29. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/llama-3.1-8b__vllm-cudagraph.json +0 -0
  30. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/llama-3.1-8b__vllm-static.json +0 -0
  31. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/llama-3.1-8b__vllm.json +0 -0
  32. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3-4b__eager.json +0 -0
  33. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3-4b__vllm-cudagraph.json +0 -0
  34. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3-4b__vllm-static.json +0 -0
  35. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3-4b__vllm.json +0 -0
  36. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3.8-27b__eager.json +0 -0
  37. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3.8-27b__vllm-cudagraph.json +0 -0
  38. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3.8-27b__vllm-static.json +0 -0
  39. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results/qwen3.8-27b__vllm.json +0 -0
  40. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/results-latest.md +0 -0
  41. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/run_all.sh +0 -0
  42. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/run_bench.py +0 -0
  43. {interp_engine-1.3.5 → interp_engine-1.3.6}/benchmarks/workloads.py +0 -0
  44. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/AGENT_INTEGRATION.md +0 -0
  45. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/ARCHITECTURE_QUIRKS.md +0 -0
  46. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/COMPATIBILITY.md +0 -0
  47. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/ENGINE_HOOK_MAPPINGS.md +0 -0
  48. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/GRADIENTS.md +0 -0
  49. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/INTERNALS.md +0 -0
  50. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/PERFORMANCE.md +0 -0
  51. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/PORTING.md +0 -0
  52. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/README.md +0 -0
  53. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/SUPPORTED_POINTS.md +0 -0
  54. {interp_engine-1.3.5 → interp_engine-1.3.6}/docs/USAGE.md +0 -0
  55. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/__init__.py +0 -0
  56. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/_loop.py +0 -0
  57. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/address.py +0 -0
  58. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/arch.py +0 -0
  59. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/attn_config.py +0 -0
  60. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/attn_scores.py +0 -0
  61. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/autograd_support.py +0 -0
  62. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/capture.py +0 -0
  63. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/chat_compose.py +0 -0
  64. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/chat_conventions.py +0 -0
  65. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/chat_formatters.py +0 -0
  66. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/cuda_preflight.py +0 -0
  67. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/dispatch.py +0 -0
  68. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/hooks.py +0 -0
  69. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/lens.py +0 -0
  70. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/load.py +0 -0
  71. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/mappers.py +0 -0
  72. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/model.py +0 -0
  73. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/moe_routing.py +0 -0
  74. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/notebook_stdout.py +0 -0
  75. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/points.py +0 -0
  76. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/protocol.py +0 -0
  77. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/residual_basis.py +0 -0
  78. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/select.py +0 -0
  79. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/steer.py +0 -0
  80. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/steer_specs.py +0 -0
  81. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/sync.py +0 -0
  82. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/tokenize.py +0 -0
  83. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/__init__.py +0 -0
  84. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/_demux.py +0 -0
  85. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/_hooks.py +0 -0
  86. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/_payload.py +0 -0
  87. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/_tree.py +0 -0
  88. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/attn.py +0 -0
  89. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/capture.py +0 -0
  90. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/graphs.py +0 -0
  91. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/lens/__init__.py +0 -0
  92. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/lens/intervene.py +0 -0
  93. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/lens/readout.py +0 -0
  94. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/lens/unembed.py +0 -0
  95. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/mhc.py +0 -0
  96. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/native.py +0 -0
  97. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/requests.py +0 -0
  98. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_capture/steering.py +0 -0
  99. {interp_engine-1.3.5 → interp_engine-1.3.6}/interp_engine/vllm_plugin.py +0 -0
  100. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/conftest.py +0 -0
  101. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/harness.py +0 -0
  102. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/model_expectations.yaml +0 -0
  103. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/synthetic_families.py +0 -0
  104. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_address.py +0 -0
  105. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_attn_config_tripwire.py +0 -0
  106. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_attn_probs_indexing.py +0 -0
  107. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_attn_scores.py +0 -0
  108. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_attn_z_gqa.py +0 -0
  109. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_autograd_support.py +0 -0
  110. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_bench_workloads.py +0 -0
  111. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_capability_refusals.py +0 -0
  112. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_capture_addressing.py +0 -0
  113. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_chat_compose.py +0 -0
  114. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_chat_formatters.py +0 -0
  115. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_chat_templates.py +0 -0
  116. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_core.py +0 -0
  117. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_cuda_preflight.py +0 -0
  118. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_doc_code_fences.py +0 -0
  119. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_eager_autograd.py +0 -0
  120. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_facts.py +0 -0
  121. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_family_points.py +0 -0
  122. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_gated_attn_out.py +0 -0
  123. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_head_contributions.py +0 -0
  124. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_hook_call_conventions.py +0 -0
  125. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_layer_kinds.py +0 -0
  126. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_load.py +0 -0
  127. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_logit_transform.py +0 -0
  128. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_mappers.py +0 -0
  129. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_mlp_internals.py +0 -0
  130. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_model_expectations.py +0 -0
  131. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_moe.py +0 -0
  132. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_multimodal_arch.py +0 -0
  133. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_new_models_gpu.py +0 -0
  134. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_no_chat_template.py +0 -0
  135. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_normalized_hook.py +0 -0
  136. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_notebook_stdout.py +0 -0
  137. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_packaging.py +0 -0
  138. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_parity_gpt2.py +0 -0
  139. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_per_layer_attn_dims.py +0 -0
  140. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_points_registry.py +0 -0
  141. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_protocol.py +0 -0
  142. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_published_benchmarks.py +0 -0
  143. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_qk_norm.py +0 -0
  144. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_qkv_layout.py +0 -0
  145. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_reasoning_spans.py +0 -0
  146. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_release.py +0 -0
  147. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_resid_mid.py +0 -0
  148. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_residual_basis.py +0 -0
  149. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_sandwich_norms.py +0 -0
  150. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_select.py +0 -0
  151. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_sliding_window_attn.py +0 -0
  152. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_small_models_gpu.py +0 -0
  153. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_static_dsv4_gpu.py +0 -0
  154. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_static_parity_gpu.py +0 -0
  155. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_static_warmup.py +0 -0
  156. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_steer_context.py +0 -0
  157. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_steer_math_parity.py +0 -0
  158. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_sync_loop.py +0 -0
  159. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_sync_parity.py +0 -0
  160. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_unified_free_functions.py +0 -0
  161. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_unresolved_families.py +0 -0
  162. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_capture_gpu.py +0 -0
  163. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_capture_scales.py +0 -0
  164. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_engine_loop.py +0 -0
  165. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_graph_path.py +0 -0
  166. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_graphs_on_gpu.py +0 -0
  167. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_hook_availability.py +0 -0
  168. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_hyper_connections.py +0 -0
  169. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_kv_isolation.py +0 -0
  170. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_new_points.py +0 -0
  171. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_only_families.py +0 -0
  172. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_plugin.py +0 -0
  173. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vllm_wire_grammar.py +0 -0
  174. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_vocabulary_boundary.py +0 -0
  175. {interp_engine-1.3.5 → interp_engine-1.3.6}/tests/test_worker_lens_capture_readout.py +0 -0
  176. {interp_engine-1.3.5 → interp_engine-1.3.6}/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.5
3
+ Version: 1.3.6
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
@@ -777,6 +777,35 @@ def mandatory_kv_cache_dtype(architectures: Sequence[str] | None) -> str | None:
777
777
  return None
778
778
 
779
779
 
780
+ # A multimodal model whose image tokens attend bidirectionally (Gemma 3, Gemma 4) has chunked
781
+ # multimodal input forced OFF by vLLM, and one whole item must then fit in a single batch or the
782
+ # engine refuses to start: "Chunked MM input disabled but max_tokens_per_mm_item (2496) is larger
783
+ # than max_num_batched_tokens (512)". vLLM raises its own floor for this, but only while defaulting
784
+ # `max_num_batched_tokens`, and clamps the result against `max_model_len` immediately after -- so a
785
+ # caller who sizes the window to its prompt, which capture does, defeats the fix and gets the raise.
786
+ #
787
+ # A floor rather than the number, because the number is not in the HF config: the per-item token
788
+ # count comes from vLLM's processing info for that family (2496 on Gemma 4, 256 on Gemma 3), and
789
+ # deriving it here would mean reimplementing each family's image-token math against a private API.
790
+ # Generous enough for every multimodal family served so far; if one ever needs more, vLLM's refusal
791
+ # names the value it wanted, which is the only honest way to revise this.
792
+ MM_MIN_BATCHED_TOKENS = 8192
793
+
794
+
795
+ def min_batched_tokens(config: Any) -> int | None:
796
+ """The ``max_num_batched_tokens`` this checkpoint cannot boot below, or ``None`` if unconstrained.
797
+
798
+ Companion to :func:`mandatory_kv_cache_dtype`, and here for the same reason: a boot requirement
799
+ every harness has to know is one the library should answer, not one each caller rediscovers from
800
+ a stack trace.
801
+
802
+ Keyed on the config being a multimodal wrapper rather than on the family, since the constraint
803
+ follows from having a non-text modality at all. Text-only checkpoints get ``None`` and keep
804
+ vLLM's own defaults, which is what makes this safe to ask about unconditionally.
805
+ """
806
+ return MM_MIN_BATCHED_TOKENS if config is not None and text_config(config) is not config else None
807
+
808
+
780
809
  # --- quantization and the backward pass ---------------------------------------
781
810
  #
782
811
  # Quantization is invisible to *capture*: hooks read activations, which transformers dequantizes to a
@@ -1134,6 +1134,7 @@ class VLLMModel:
1134
1134
  ),
1135
1135
  n_layers=self.num_hidden_layers,
1136
1136
  tensor_parallel_size=self.tensor_parallel_size,
1137
+ min_n=facts.min_batched_tokens(cfg) or 0,
1137
1138
  )
1138
1139
  if fitted != max_n:
1139
1140
  logger.warning("lowering max_num_batched_tokens %s -> %s so static buffers fit", max_n, fitted)
@@ -544,6 +544,7 @@ def fit_max_num_batched_tokens(
544
544
  kv_width: int,
545
545
  n_layers: int,
546
546
  tensor_parallel_size: int = 1,
547
+ min_n: int = 0,
547
548
  ) -> int:
548
549
  """Largest capture size at or below ``max_n`` whose static buffers fit, or raise.
549
550
 
@@ -561,14 +562,20 @@ def fit_max_num_batched_tokens(
561
562
  The 1024 floor is for ``"auto"`` / default ``max_n``: vLLM will not usefully serve with a
562
563
  tiny batch. A caller who already passed a smaller ``max_n`` (chunked-prefill tests) keeps it
563
564
  when it fits.
565
+
566
+ ``min_n`` raises that floor to a size the *engine* will not start below, which is a different
567
+ kind of limit and has to outrank the fit: shrinking under it trades an out-of-memory error for a
568
+ refusal to boot, and the refusal comes from vLLM's scheduler talking about multimodal items,
569
+ which reads as anything but a static-buffer problem. Above the floor it changes nothing --
570
+ ``fitted == asked`` stays the common case. See :func:`interp_engine.facts.min_batched_tokens`.
564
571
  """
565
572
  graph_fudge = 3 * 1024**3
566
573
  tp = max(int(tensor_parallel_size), 1)
567
574
  min_kv = max(int(n_layers), 1) * max(int(max_model_len), 1) * max(int(kv_width), 1) * 2
568
575
  budget = int(gpu_memory_utilization * device_memory) - int(weight_bytes) // tp - graph_fudge - min_kv
569
576
  asked = int(max_n)
570
- floor = min(1024, asked)
571
- candidates = [asked] + [s for s in _CAPTURE_SIZES if s < asked]
577
+ floor = max(min(1024, asked), int(min_n))
578
+ candidates = [max(asked, floor)] + [s for s in _CAPTURE_SIZES if s < asked]
572
579
  for n in candidates:
573
580
  if n < floor:
574
581
  continue
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "interp-engine"
3
- version = "1.3.5"
3
+ version = "1.3.6"
4
4
  description = "A fast, standardized, and easy to use interpretability engine."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -221,6 +221,33 @@ def test_vram_check_lowers_max_n_instead_of_ooming():
221
221
  assert fitted >= 1024
222
222
 
223
223
 
224
+ def test_vram_check_will_not_shrink_below_an_engine_boot_floor():
225
+ """A multimodal prefix-LM will not start below one whole image, so shrinking under it is no fit.
226
+
227
+ Better to refuse here, naming the buffers, than to hand back a size vLLM rejects later with a
228
+ scheduler error about multimodal items that reads as anything but a static-buffer problem.
229
+ """
230
+
231
+ def fit(device_gib: int, **extra: int) -> int:
232
+ return fit_max_num_batched_tokens(
233
+ n_sites=80,
234
+ width=5120,
235
+ max_n=16384,
236
+ device_memory=device_gib * 1024**3,
237
+ gpu_memory_utilization=0.95,
238
+ weight_bytes=10 * 1024**3,
239
+ max_model_len=4096,
240
+ kv_width=kv_cache_width(d_model=5120),
241
+ n_layers=32,
242
+ **extra,
243
+ )
244
+
245
+ assert fit(20) == 4096 # left alone, this card shrinks under the floor
246
+ with pytest.raises(ValueError, match="do not fit even at max_num_batched_tokens=8192"):
247
+ fit(20, min_n=8192)
248
+ assert fit(24, min_n=8192) == 8192 # a floor it can meet changes nothing
249
+
250
+
224
251
  def test_vram_check_keeps_a_caller_max_n_below_the_1024_floor():
225
252
  """Chunked-prefill tests pass max_num_batched_tokens=32; skipping that candidate used to raise."""
226
253
  fitted = fit_max_num_batched_tokens(
File without changes
File without changes
File without changes