backcountry 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. backcountry-0.1.0/.gitignore +12 -0
  2. backcountry-0.1.0/LICENSE +21 -0
  3. backcountry-0.1.0/PKG-INFO +302 -0
  4. backcountry-0.1.0/README.md +275 -0
  5. backcountry-0.1.0/RUNNING.md +64 -0
  6. backcountry-0.1.0/docs/architecture.md +135 -0
  7. backcountry-0.1.0/docs/artifacts.md +276 -0
  8. backcountry-0.1.0/docs/judge-protocol.md +242 -0
  9. backcountry-0.1.0/docs/limitations.md +80 -0
  10. backcountry-0.1.0/docs/manifest.md +552 -0
  11. backcountry-0.1.0/docs/measurement.md +430 -0
  12. backcountry-0.1.0/docs/operating.md +229 -0
  13. backcountry-0.1.0/docs/usage.md +267 -0
  14. backcountry-0.1.0/pyproject.toml +59 -0
  15. backcountry-0.1.0/src/backcountry/__init__.py +54 -0
  16. backcountry-0.1.0/src/backcountry/__main__.py +5 -0
  17. backcountry-0.1.0/src/backcountry/harness/__init__.py +0 -0
  18. backcountry-0.1.0/src/backcountry/harness/artifact/__init__.py +0 -0
  19. backcountry-0.1.0/src/backcountry/harness/artifact/benchmark.py +155 -0
  20. backcountry-0.1.0/src/backcountry/harness/artifact/bundle.py +790 -0
  21. backcountry-0.1.0/src/backcountry/harness/artifact/emit.py +897 -0
  22. backcountry-0.1.0/src/backcountry/harness/artifact/loader.py +173 -0
  23. backcountry-0.1.0/src/backcountry/harness/artifact/validate.py +418 -0
  24. backcountry-0.1.0/src/backcountry/harness/bind/__init__.py +0 -0
  25. backcountry-0.1.0/src/backcountry/harness/bind/certify.py +278 -0
  26. backcountry-0.1.0/src/backcountry/harness/bind/emit.py +786 -0
  27. backcountry-0.1.0/src/backcountry/harness/bind/fp32_region.py +158 -0
  28. backcountry-0.1.0/src/backcountry/harness/bind/graph.py +214 -0
  29. backcountry-0.1.0/src/backcountry/harness/bind/sized.py +225 -0
  30. backcountry-0.1.0/src/backcountry/harness/bind/swap.py +6 -0
  31. backcountry-0.1.0/src/backcountry/harness/bind/verify.py +122 -0
  32. backcountry-0.1.0/src/backcountry/harness/cli.py +563 -0
  33. backcountry-0.1.0/src/backcountry/harness/control.py +224 -0
  34. backcountry-0.1.0/src/backcountry/harness/e2e.py +225 -0
  35. backcountry-0.1.0/src/backcountry/harness/front_door.py +272 -0
  36. backcountry-0.1.0/src/backcountry/harness/guide.py +62 -0
  37. backcountry-0.1.0/src/backcountry/harness/inputs/SOURCES.md +38 -0
  38. backcountry-0.1.0/src/backcountry/harness/inputs/text.jsonl.gz +0 -0
  39. backcountry-0.1.0/src/backcountry/harness/intent.py +686 -0
  40. backcountry-0.1.0/src/backcountry/harness/judge/__init__.py +34 -0
  41. backcountry-0.1.0/src/backcountry/harness/judge/agent.py +360 -0
  42. backcountry-0.1.0/src/backcountry/harness/judge/briefing.py +74 -0
  43. backcountry-0.1.0/src/backcountry/harness/judge/client.py +344 -0
  44. backcountry-0.1.0/src/backcountry/harness/judge/directions.py +54 -0
  45. backcountry-0.1.0/src/backcountry/harness/judge/examples.py +222 -0
  46. backcountry-0.1.0/src/backcountry/harness/judge/experience.py +120 -0
  47. backcountry-0.1.0/src/backcountry/harness/judge/method_level.py +107 -0
  48. backcountry-0.1.0/src/backcountry/harness/judge/model_level.py +289 -0
  49. backcountry-0.1.0/src/backcountry/harness/judge/principles.py +485 -0
  50. backcountry-0.1.0/src/backcountry/harness/judge/prompts.py +314 -0
  51. backcountry-0.1.0/src/backcountry/harness/judge/queue.py +182 -0
  52. backcountry-0.1.0/src/backcountry/harness/judge/schema.py +374 -0
  53. backcountry-0.1.0/src/backcountry/harness/judge/scripted.py +65 -0
  54. backcountry-0.1.0/src/backcountry/harness/judge/source_context.py +153 -0
  55. backcountry-0.1.0/src/backcountry/harness/knowledge.py +116 -0
  56. backcountry-0.1.0/src/backcountry/harness/ladder/__init__.py +0 -0
  57. backcountry-0.1.0/src/backcountry/harness/ladder/child.py +657 -0
  58. backcountry-0.1.0/src/backcountry/harness/ladder/gates.py +212 -0
  59. backcountry-0.1.0/src/backcountry/harness/ladder/golden.py +266 -0
  60. backcountry-0.1.0/src/backcountry/harness/ladder/numeric.py +78 -0
  61. backcountry-0.1.0/src/backcountry/harness/ladder/sizes.py +287 -0
  62. backcountry-0.1.0/src/backcountry/harness/ladder/static_checks.py +298 -0
  63. backcountry-0.1.0/src/backcountry/harness/log.py +54 -0
  64. backcountry-0.1.0/src/backcountry/harness/loop.py +5765 -0
  65. backcountry-0.1.0/src/backcountry/harness/manifest.py +575 -0
  66. backcountry-0.1.0/src/backcountry/harness/measure/__init__.py +0 -0
  67. backcountry-0.1.0/src/backcountry/harness/measure/clocks.py +461 -0
  68. backcountry-0.1.0/src/backcountry/harness/measure/controls.py +71 -0
  69. backcountry-0.1.0/src/backcountry/harness/measure/heat.py +236 -0
  70. backcountry-0.1.0/src/backcountry/harness/measure/peaks.py +205 -0
  71. backcountry-0.1.0/src/backcountry/harness/measure/probe.py +128 -0
  72. backcountry-0.1.0/src/backcountry/harness/measure/sequences.py +4 -0
  73. backcountry-0.1.0/src/backcountry/harness/measure/session.py +449 -0
  74. backcountry-0.1.0/src/backcountry/harness/model_search.py +1375 -0
  75. backcountry-0.1.0/src/backcountry/harness/regions/__init__.py +0 -0
  76. backcountry-0.1.0/src/backcountry/harness/regions/build.py +196 -0
  77. backcountry-0.1.0/src/backcountry/harness/regions/fingerprint.py +131 -0
  78. backcountry-0.1.0/src/backcountry/harness/regions/price.py +407 -0
  79. backcountry-0.1.0/src/backcountry/harness/regions/rank.py +256 -0
  80. backcountry-0.1.0/src/backcountry/harness/regions/roofline.py +222 -0
  81. backcountry-0.1.0/src/backcountry/harness/regions/store.py +155 -0
  82. backcountry-0.1.0/src/backcountry/harness/regions/sweep.py +46 -0
  83. backcountry-0.1.0/src/backcountry/harness/regions/types.py +109 -0
  84. backcountry-0.1.0/src/backcountry/harness/report.py +174 -0
  85. backcountry-0.1.0/src/backcountry/harness/resume.py +194 -0
  86. backcountry-0.1.0/src/backcountry/harness/sandbox/__init__.py +0 -0
  87. backcountry-0.1.0/src/backcountry/harness/sandbox/poison.py +32 -0
  88. backcountry-0.1.0/src/backcountry/harness/sandbox/protocol.py +379 -0
  89. backcountry-0.1.0/src/backcountry/harness/sandbox/recovery.py +74 -0
  90. backcountry-0.1.0/src/backcountry/harness/sandbox/watchdog.py +31 -0
  91. backcountry-0.1.0/src/backcountry/harness/sandbox/worker.py +35 -0
  92. backcountry-0.1.0/src/backcountry/harness/scaffold/__init__.py +65 -0
  93. backcountry-0.1.0/src/backcountry/harness/scaffold/lower.py +1552 -0
  94. backcountry-0.1.0/src/backcountry/harness/scaffold/native.py +118 -0
  95. backcountry-0.1.0/src/backcountry/harness/scaffold/stitch.py +458 -0
  96. backcountry-0.1.0/src/backcountry/harness/scaffold/symshape.py +173 -0
  97. backcountry-0.1.0/src/backcountry/harness/serve.py +426 -0
  98. backcountry-0.1.0/src/backcountry/harness/size_questions.py +216 -0
  99. backcountry-0.1.0/src/backcountry/harness/trace/__init__.py +59 -0
  100. backcountry-0.1.0/src/backcountry/harness/trace/freeze.py +110 -0
  101. backcountry-0.1.0/src/backcountry/harness/trace/optable.py +128 -0
  102. backcountry-0.1.0/src/backcountry/harness/trace/patches.py +565 -0
  103. backcountry-0.1.0/src/backcountry/harness/trace/recorder.py +565 -0
  104. backcountry-0.1.0/src/backcountry/harness/trace/replay.py +186 -0
  105. backcountry-0.1.0/src/backcountry/harness/trace/serialize.py +95 -0
  106. backcountry-0.1.0/src/backcountry/harness/trace/types.py +107 -0
  107. backcountry-0.1.0/src/backcountry/harness/trace/walk.py +96 -0
  108. backcountry-0.1.0/src/backcountry/harness/watch.py +374 -0
  109. backcountry-0.1.0/src/backcountry/harness/workload.py +206 -0
  110. backcountry-0.1.0/src/backcountry_runtime/LICENSE-omlx +190 -0
  111. backcountry-0.1.0/src/backcountry_runtime/__init__.py +0 -0
  112. backcountry-0.1.0/src/backcountry_runtime/apply.py +313 -0
  113. backcountry-0.1.0/src/backcountry_runtime/captured_kernels.py +86 -0
  114. backcountry-0.1.0/src/backcountry_runtime/checkpoints.py +53 -0
  115. backcountry-0.1.0/src/backcountry_runtime/compare.py +161 -0
  116. backcountry-0.1.0/src/backcountry_runtime/exact.py +10 -0
  117. backcountry-0.1.0/src/backcountry_runtime/exact_rows.py +228 -0
  118. backcountry-0.1.0/src/backcountry_runtime/functions.py +323 -0
  119. backcountry-0.1.0/src/backcountry_runtime/generation.py +334 -0
  120. backcountry-0.1.0/src/backcountry_runtime/grammar.py +150 -0
  121. backcountry-0.1.0/src/backcountry_runtime/graph.py +512 -0
  122. backcountry-0.1.0/src/backcountry_runtime/graph_native/CMakeLists.txt +12 -0
  123. backcountry-0.1.0/src/backcountry_runtime/graph_native/__init__.py +167 -0
  124. backcountry-0.1.0/src/backcountry_runtime/graph_native/graph_native.cpp +331 -0
  125. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_14_0_arm64/cpython-312/_graph_native.cpython-312-darwin.so +0 -0
  126. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_14_0_arm64/cpython-312/build.json +1 -0
  127. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_14_0_arm64/cpython-313/_graph_native.cpython-313-darwin.so +0 -0
  128. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_14_0_arm64/cpython-313/build.json +1 -0
  129. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_14_0_arm64/cpython-314/_graph_native.cpython-314-darwin.so +0 -0
  130. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_14_0_arm64/cpython-314/build.json +1 -0
  131. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_15_0_arm64/cpython-312/_graph_native.cpython-312-darwin.so +0 -0
  132. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_15_0_arm64/cpython-312/build.json +1 -0
  133. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_15_0_arm64/cpython-313/_graph_native.cpython-313-darwin.so +0 -0
  134. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_15_0_arm64/cpython-313/build.json +1 -0
  135. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_15_0_arm64/cpython-314/_graph_native.cpython-314-darwin.so +0 -0
  136. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_15_0_arm64/cpython-314/build.json +1 -0
  137. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_26_0_arm64/cpython-312/_graph_native.cpython-312-darwin.so +0 -0
  138. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_26_0_arm64/cpython-312/build.json +1 -0
  139. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_26_0_arm64/cpython-313/_graph_native.cpython-313-darwin.so +0 -0
  140. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_26_0_arm64/cpython-313/build.json +1 -0
  141. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_26_0_arm64/cpython-314/_graph_native.cpython-314-darwin.so +0 -0
  142. backcountry-0.1.0/src/backcountry_runtime/graph_native/prebuilt/py3-none-macosx_26_0_arm64/cpython-314/build.json +1 -0
  143. backcountry-0.1.0/src/backcountry_runtime/inference.py +193 -0
  144. backcountry-0.1.0/src/backcountry_runtime/kernels.py +293 -0
  145. backcountry-0.1.0/src/backcountry_runtime/model_run.py +279 -0
  146. backcountry-0.1.0/src/backcountry_runtime/numeric.py +153 -0
  147. backcountry-0.1.0/src/backcountry_runtime/omlx_row_exact.py +595 -0
  148. backcountry-0.1.0/src/backcountry_runtime/original_sequence.py +93 -0
  149. backcountry-0.1.0/src/backcountry_runtime/sequence.py +188 -0
  150. backcountry-0.1.0/src/backcountry_runtime/sizes.py +229 -0
  151. backcountry-0.1.0/src/backcountry_runtime/stages.py +53 -0
  152. backcountry-0.1.0/src/backcountry_runtime/state.py +329 -0
  153. backcountry-0.1.0/src/backcountry_runtime/stats.py +127 -0
  154. backcountry-0.1.0/src/backcountry_runtime/swap.py +229 -0
  155. backcountry-0.1.0/src/backcountry_runtime/tune.py +1004 -0
  156. backcountry-0.1.0/src/backcountry_runtime/watch.py +256 -0
  157. backcountry-0.1.0/tests/__init__.py +0 -0
  158. backcountry-0.1.0/tests/cheats/__init__.py +247 -0
  159. backcountry-0.1.0/tests/cli/__init__.py +0 -0
  160. backcountry-0.1.0/tests/cli/apps.py +324 -0
  161. backcountry-0.1.0/tests/cli/conftest.py +71 -0
  162. backcountry-0.1.0/tests/cli/fake_judge.py +15 -0
  163. backcountry-0.1.0/tests/cli/fake_loop_judge.py +25 -0
  164. backcountry-0.1.0/tests/cli/test_check.py +72 -0
  165. backcountry-0.1.0/tests/cli/test_guide.py +40 -0
  166. backcountry-0.1.0/tests/cli/test_init.py +174 -0
  167. backcountry-0.1.0/tests/cli/test_job.py +71 -0
  168. backcountry-0.1.0/tests/cli/test_loop.py +92 -0
  169. backcountry-0.1.0/tests/conftest.py +138 -0
  170. backcountry-0.1.0/tests/drift.py +92 -0
  171. backcountry-0.1.0/tests/fixtures/__init__.py +0 -0
  172. backcountry-0.1.0/tests/fixtures/app_run_model.py +37 -0
  173. backcountry-0.1.0/tests/fixtures/bf16_state.py +33 -0
  174. backcountry-0.1.0/tests/fixtures/cache_retention.py +25 -0
  175. backcountry-0.1.0/tests/fixtures/compiled_submodule.py +37 -0
  176. backcountry-0.1.0/tests/fixtures/concat_join.py +17 -0
  177. backcountry-0.1.0/tests/fixtures/const_scale.py +32 -0
  178. backcountry-0.1.0/tests/fixtures/context_model.py +71 -0
  179. backcountry-0.1.0/tests/fixtures/data_branch.py +22 -0
  180. backcountry-0.1.0/tests/fixtures/eager_item.py +21 -0
  181. backcountry-0.1.0/tests/fixtures/engine_held_model.py +21 -0
  182. backcountry-0.1.0/tests/fixtures/engine_held_no_release.py +15 -0
  183. backcountry-0.1.0/tests/fixtures/engine_kit_model.py +33 -0
  184. backcountry-0.1.0/tests/fixtures/examples_model.py +27 -0
  185. backcountry-0.1.0/tests/fixtures/flux2_4b.py +272 -0
  186. backcountry-0.1.0/tests/fixtures/hand_norm.py +30 -0
  187. backcountry-0.1.0/tests/fixtures/history_model.py +71 -0
  188. backcountry-0.1.0/tests/fixtures/kernel_closure.py +32 -0
  189. backcountry-0.1.0/tests/fixtures/kernel_submodule.py +49 -0
  190. backcountry-0.1.0/tests/fixtures/kv_step.py +28 -0
  191. backcountry-0.1.0/tests/fixtures/layer_norm_affine.py +20 -0
  192. backcountry-0.1.0/tests/fixtures/llama_cache_model.py +16 -0
  193. backcountry-0.1.0/tests/fixtures/llama_engine.py +23 -0
  194. backcountry-0.1.0/tests/fixtures/llama_ish.py +59 -0
  195. backcountry-0.1.0/tests/fixtures/native_fusion.py +35 -0
  196. backcountry-0.1.0/tests/fixtures/native_recurrence.py +28 -0
  197. backcountry-0.1.0/tests/fixtures/nested_scopes.py +39 -0
  198. backcountry-0.1.0/tests/fixtures/norm_three_proj.py +24 -0
  199. backcountry-0.1.0/tests/fixtures/opaque_submodule.py +23 -0
  200. backcountry-0.1.0/tests/fixtures/operator_soup.py +28 -0
  201. backcountry-0.1.0/tests/fixtures/plain_function.py +15 -0
  202. backcountry-0.1.0/tests/fixtures/planted_win.py +38 -0
  203. backcountry-0.1.0/tests/fixtures/quantized_linear.py +27 -0
  204. backcountry-0.1.0/tests/fixtures/qwen_cache_model.py +18 -0
  205. backcountry-0.1.0/tests/fixtures/repeated_chain.py +27 -0
  206. backcountry-0.1.0/tests/fixtures/repeated_layers.py +32 -0
  207. backcountry-0.1.0/tests/fixtures/rope_context_model.py +74 -0
  208. backcountry-0.1.0/tests/fixtures/sibling_children.py +48 -0
  209. backcountry-0.1.0/tests/fixtures/size_branch.py +42 -0
  210. backcountry-0.1.0/tests/fixtures/sized_reshape.py +25 -0
  211. backcountry-0.1.0/tests/fixtures/slice_read.py +15 -0
  212. backcountry-0.1.0/tests/fixtures/split_heads.py +15 -0
  213. backcountry-0.1.0/tests/fixtures/square_rows.py +23 -0
  214. backcountry-0.1.0/tests/fixtures/state_call.py +50 -0
  215. backcountry-0.1.0/tests/fixtures/step_output_region.py +21 -0
  216. backcountry-0.1.0/tests/fixtures/tiny_engine.py +34 -0
  217. backcountry-0.1.0/tests/fixtures/two_partial_children.py +30 -0
  218. backcountry-0.1.0/tests/fixtures/two_stage_model.py +20 -0
  219. backcountry-0.1.0/tests/fixtures/vendor_parity.py +19 -0
  220. backcountry-0.1.0/tests/fixtures/views_only.py +22 -0
  221. backcountry-0.1.0/tests/fixtures/weight_view_in_module.py +21 -0
  222. backcountry-0.1.0/tests/fixtures/window_context_model.py +74 -0
  223. backcountry-0.1.0/tests/fixtures/workload_win.py +30 -0
  224. backcountry-0.1.0/tests/judges.py +55 -0
  225. backcountry-0.1.0/tests/test_any_function.py +144 -0
  226. backcountry-0.1.0/tests/test_app_run.py +110 -0
  227. backcountry-0.1.0/tests/test_apply_check.py +179 -0
  228. backcountry-0.1.0/tests/test_artifact.py +703 -0
  229. backcountry-0.1.0/tests/test_artifact_benchmark_defaults.py +85 -0
  230. backcountry-0.1.0/tests/test_artifact_generalization.py +170 -0
  231. backcountry-0.1.0/tests/test_bind.py +389 -0
  232. backcountry-0.1.0/tests/test_capture_versions.py +141 -0
  233. backcountry-0.1.0/tests/test_captured_kernels.py +141 -0
  234. backcountry-0.1.0/tests/test_checkpoint_packaging.py +94 -0
  235. backcountry-0.1.0/tests/test_compare.py +48 -0
  236. backcountry-0.1.0/tests/test_compiled_region_replay.py +126 -0
  237. backcountry-0.1.0/tests/test_context_workload.py +603 -0
  238. backcountry-0.1.0/tests/test_control.py +130 -0
  239. backcountry-0.1.0/tests/test_cooling_handoff.py +171 -0
  240. backcountry-0.1.0/tests/test_cross_model_audit.py +144 -0
  241. backcountry-0.1.0/tests/test_dead_work.py +132 -0
  242. backcountry-0.1.0/tests/test_delivery_accounting.py +459 -0
  243. backcountry-0.1.0/tests/test_drift.py +147 -0
  244. backcountry-0.1.0/tests/test_e2e.py +388 -0
  245. backcountry-0.1.0/tests/test_exact_rows.py +139 -0
  246. backcountry-0.1.0/tests/test_finish_control.py +184 -0
  247. backcountry-0.1.0/tests/test_fp32_reference.py +306 -0
  248. backcountry-0.1.0/tests/test_fp32_region_reference.py +87 -0
  249. backcountry-0.1.0/tests/test_freeze.py +65 -0
  250. backcountry-0.1.0/tests/test_front_door.py +175 -0
  251. backcountry-0.1.0/tests/test_generated_workload.py +158 -0
  252. backcountry-0.1.0/tests/test_graph_install.py +676 -0
  253. backcountry-0.1.0/tests/test_graph_model_matrix.py +132 -0
  254. backcountry-0.1.0/tests/test_graph_native.py +327 -0
  255. backcountry-0.1.0/tests/test_heat.py +246 -0
  256. backcountry-0.1.0/tests/test_identical_outputs.py +1382 -0
  257. backcountry-0.1.0/tests/test_inference_flow.py +319 -0
  258. backcountry-0.1.0/tests/test_install.py +518 -0
  259. backcountry-0.1.0/tests/test_intent.py +179 -0
  260. backcountry-0.1.0/tests/test_judge.py +1000 -0
  261. backcountry-0.1.0/tests/test_judge_agent.py +434 -0
  262. backcountry-0.1.0/tests/test_judge_briefing.py +143 -0
  263. backcountry-0.1.0/tests/test_judge_tries.py +51 -0
  264. backcountry-0.1.0/tests/test_knowledge.py +122 -0
  265. backcountry-0.1.0/tests/test_ladder_gates.py +579 -0
  266. backcountry-0.1.0/tests/test_ladder_numeric.py +478 -0
  267. backcountry-0.1.0/tests/test_launch_guard.py +153 -0
  268. backcountry-0.1.0/tests/test_library_inference.py +246 -0
  269. backcountry-0.1.0/tests/test_live_method.py +708 -0
  270. backcountry-0.1.0/tests/test_llama_ish.py +57 -0
  271. backcountry-0.1.0/tests/test_log.py +23 -0
  272. backcountry-0.1.0/tests/test_loop.py +789 -0
  273. backcountry-0.1.0/tests/test_manifest.py +381 -0
  274. backcountry-0.1.0/tests/test_measure.py +454 -0
  275. backcountry-0.1.0/tests/test_mixed_workloads.py +140 -0
  276. backcountry-0.1.0/tests/test_model_loading.py +158 -0
  277. backcountry-0.1.0/tests/test_multi_shape_bind.py +160 -0
  278. backcountry-0.1.0/tests/test_native_fusion.py +190 -0
  279. backcountry-0.1.0/tests/test_native_search.py +289 -0
  280. backcountry-0.1.0/tests/test_nested_bind.py +251 -0
  281. backcountry-0.1.0/tests/test_numeric_contract.py +185 -0
  282. backcountry-0.1.0/tests/test_output_diagnostics.py +65 -0
  283. backcountry-0.1.0/tests/test_output_layout.py +64 -0
  284. backcountry-0.1.0/tests/test_platform_env_and_compile.py +461 -0
  285. backcountry-0.1.0/tests/test_platform_metal_kernel.py +195 -0
  286. backcountry-0.1.0/tests/test_platform_msl_tree.py +112 -0
  287. backcountry-0.1.0/tests/test_platform_patching.py +270 -0
  288. backcountry-0.1.0/tests/test_platform_retention.py +122 -0
  289. backcountry-0.1.0/tests/test_platform_swap.py +214 -0
  290. backcountry-0.1.0/tests/test_prepared_replay.py +50 -0
  291. backcountry-0.1.0/tests/test_recurrent_cache.py +228 -0
  292. backcountry-0.1.0/tests/test_reference_starter.py +239 -0
  293. backcountry-0.1.0/tests/test_region_boundaries.py +235 -0
  294. backcountry-0.1.0/tests/test_region_selection.py +445 -0
  295. backcountry-0.1.0/tests/test_regions.py +699 -0
  296. backcountry-0.1.0/tests/test_regressions.py +530 -0
  297. backcountry-0.1.0/tests/test_report.py +60 -0
  298. backcountry-0.1.0/tests/test_resume.py +37 -0
  299. backcountry-0.1.0/tests/test_rope_offset.py +154 -0
  300. backcountry-0.1.0/tests/test_run_efficiency.py +301 -0
  301. backcountry-0.1.0/tests/test_run_recovery.py +561 -0
  302. backcountry-0.1.0/tests/test_runtime.py +136 -0
  303. backcountry-0.1.0/tests/test_sandbox.py +202 -0
  304. backcountry-0.1.0/tests/test_scaffold.py +936 -0
  305. backcountry-0.1.0/tests/test_scaffold_model_fallback.py +130 -0
  306. backcountry-0.1.0/tests/test_scaffold_reductions.py +136 -0
  307. backcountry-0.1.0/tests/test_search_experience.py +209 -0
  308. backcountry-0.1.0/tests/test_search_feedback.py +149 -0
  309. backcountry-0.1.0/tests/test_selection_flow.py +161 -0
  310. backcountry-0.1.0/tests/test_sequences.py +178 -0
  311. backcountry-0.1.0/tests/test_serve.py +868 -0
  312. backcountry-0.1.0/tests/test_sibling_bind.py +104 -0
  313. backcountry-0.1.0/tests/test_size_stretch.py +116 -0
  314. backcountry-0.1.0/tests/test_sizes.py +163 -0
  315. backcountry-0.1.0/tests/test_source_context.py +156 -0
  316. backcountry-0.1.0/tests/test_stages.py +308 -0
  317. backcountry-0.1.0/tests/test_state_calls.py +334 -0
  318. backcountry-0.1.0/tests/test_state_correctness.py +88 -0
  319. backcountry-0.1.0/tests/test_static_checks.py +74 -0
  320. backcountry-0.1.0/tests/test_stitch.py +551 -0
  321. backcountry-0.1.0/tests/test_through_the_run.py +144 -0
  322. backcountry-0.1.0/tests/test_tracer.py +523 -0
  323. backcountry-0.1.0/tests/test_tune.py +384 -0
  324. backcountry-0.1.0/tests/test_watch.py +197 -0
  325. backcountry-0.1.0/tests/test_widening.py +300 -0
  326. backcountry-0.1.0/tests/test_worker_signature_guard.py +51 -0
  327. backcountry-0.1.0/tests/test_worker_watchdog.py +256 -0
  328. backcountry-0.1.0/tests/test_workload.py +200 -0
  329. backcountry-0.1.0/tests/test_workload_acceptance.py +277 -0
  330. backcountry-0.1.0/tests/test_workload_scoring_gpu.py +115 -0
  331. backcountry-0.1.0/tests/test_workload_targeting.py +211 -0
  332. backcountry-0.1.0/tools/build_graph_native.py +100 -0
  333. backcountry-0.1.0/tools/build_text_pool.py +90 -0
  334. backcountry-0.1.0/uv.lock +1658 -0
@@ -0,0 +1,12 @@
1
+ # run folders and generated results stay out of the tree
2
+ /runs/
3
+ /dist/
4
+ # python and tooling
5
+ __pycache__/
6
+ *.pyc
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .venv/
10
+ # machine-local agent settings
11
+ /.claude/
12
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shivam Garg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.5
2
+ Name: backcountry
3
+ Version: 0.1.0
4
+ Summary: Makes MLX models faster on Apple Silicon without changing their answers: an AI proposes Metal kernels and faster loops, and a harness keeps only verified wins
5
+ Project-URL: Homepage, https://github.com/shivamg05/backcountry
6
+ Project-URL: Repository, https://github.com/shivamg05/backcountry
7
+ Project-URL: Issues, https://github.com/shivamg05/backcountry/issues
8
+ Author: Shivam Garg
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: apple-silicon,inference,kernels,llm,metal,mlx,optimization
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: <3.15,>=3.12
20
+ Requires-Dist: anthropic>=0.40
21
+ Requires-Dist: cmake>=3.27
22
+ Requires-Dist: mlx==0.32.4
23
+ Requires-Dist: nanobind==3.1.0
24
+ Requires-Dist: numpy>=2.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Description-Content-Type: text/markdown
27
+
28
+ # backcountry
29
+
30
+ Make an MLX model faster on Apple Silicon without changing its answers.
31
+
32
+ You mark the function your app calls for one request. An AI proposes faster
33
+ versions: custom Metal GPU code (kernels) for parts of the model and, if you
34
+ allow it, a faster way to use the model (a reply drafted several tokens at a
35
+ time and checked). A separate harness tests every one, and keeps it only if
36
+ the outputs still match the original (bit for bit, or within a tolerance you
37
+ control when the math is reordered) and the **whole request** gets measurably
38
+ faster. Whatever survives is saved beside your code, and your function uses it
39
+ from its next call.
40
+
41
+ Sometimes nothing survives. That's a real answer, not a crash: no candidate
42
+ beat your code by a margin the tool could confirm within its attempt budget.
43
+
44
+ ## Quickstart
45
+
46
+ You need an Apple Silicon Mac and Python 3.12, 3.13 or 3.14.
47
+
48
+ 1. **Install [Claude Code](https://code.claude.com/docs/en/setup)**, run
49
+ `claude` once, sign in, and exit. Claude Code is the AI that writes the
50
+ kernels, so your account needs Claude Code access. A signed-in Codex or
51
+ Gemini CLI works too, or the Anthropic API with `ANTHROPIC_API_KEY`.
52
+ 2. **Install backcountry** in the environment your app runs in:
53
+
54
+ ```sh
55
+ pip install backcountry # or: uv add backcountry
56
+ ```
57
+
58
+ 3. **Mark the function** your app calls for one request, the one that turns
59
+ an input into an output (writes a reply, makes an image, classifies a
60
+ batch), with two lines:
61
+
62
+ ```python
63
+ import backcountry
64
+
65
+ @backcountry.tune
66
+ def generate_text(model, tokenizer, prompt, max_tokens=150): # your function, unchanged
67
+ ...
68
+ ```
69
+
70
+ 4. **Run four commands** from your project's folder:
71
+
72
+ ```sh
73
+ backcountry init app.py # runs app.py once; asks the sizes to optimize
74
+ backcountry check backcountry.yaml # minutes, no attempts: does it run, fit, have room?
75
+ backcountry start backcountry.yaml # the search, in the background
76
+ backcountry status --wait # returns at each milestone
77
+ ```
78
+
79
+ A few things to know while it runs:
80
+
81
+ - **Expect it to take hours.** Each attempt is checked and timed, and the
82
+ budget counts attempts, not minutes. `start` runs in the background, so you
83
+ can close the terminal.
84
+ - **Keep other GPU work quiet.** Background load makes the timings noisy.
85
+ - **Kernel requests use your AI provider's allowance** (subscription or API
86
+ credits).
87
+
88
+ When it finishes, the result is a folder beside the file that defines your
89
+ function, `backcountry-results/<function>/`. Commit it, or ship it with your
90
+ code: the function uses it from its next call, and says so once.
91
+
92
+ ## Run it through an AI agent
93
+
94
+ Don't want to answer questions and watch logs? Install backcountry, then paste
95
+ this into a coding agent (Claude Code, Codex, …) opened on your project:
96
+
97
+ ```text
98
+ Run `backcountry guide` and follow it to make my app faster.
99
+ Ask me what you need, and update me only at milestones.
100
+ ```
101
+
102
+ `backcountry guide` prints the steps for the installed version: where to put
103
+ the mark, which questions to ask you, how to start and watch the run. There
104
+ are two AIs here, and they can come from different providers: the agent you're
105
+ chatting with runs and watches the job; the one named by `--judge` writes the
106
+ kernels.
107
+
108
+ ## What init asks
109
+
110
+ The function can do whatever one request does in your app: tokenize, run
111
+ your own loop with your own cache, sample, stop at an end token, decode,
112
+ post-process, print. `init` runs `app.py` as you would (`python app.py`) and
113
+ records each call of the marked function and the model it uses (passed in,
114
+ held by an object it is given, or a global, even one a library keeps for
115
+ itself). Then it tries each argument of the first call at another size and
116
+ watches what the model does, so it asks only about the ones that change the
117
+ work:
118
+
119
+ ```
120
+ These change how much work the model does:
121
+ 1. width and height (together)
122
+ These run the model more or fewer times (one value, used for every size):
123
+ 2. steps
124
+ These don't change the work:
125
+ prompt, seed
126
+ Sizes for width and height (together): a list like 512x512, 1024x768 [default: 1024x1024]: 512x512, 1024x1024
127
+ steps: the value to use for every size [default: 4]:
128
+ ```
129
+
130
+ The same questions come for any model: a prompt's length in tokens, a batch's
131
+ count, an array's axis, an image's width and height, any number argument.
132
+ Each question's default is the sizes your app called it with (here the
133
+ script made one 1024x1024 image), so pressing Enter optimizes what the app
134
+ actually runs. Answer with a list (`1, 8, 32`), a range (`32-4096`, spread into five sizes)
135
+ or pairs (`512x512, 1024x768`); at most eight sizes in all, since each is
136
+ checked and timed. `init` makes the call at every size once before writing
137
+ anything; agents pass the answers by number (`--sizes "1=512x512, 1024x1024; 2=4"`).
138
+ The search replays your function at each size, from the arguments your app
139
+ passed, repeated or cut to the size (never made-up data); it compares what the
140
+ function returns (arrays, numbers, text by its bytes, images) and times the
141
+ whole call, so the speedup it reports is your request's. Random draws are
142
+ seeded the same for every replay, and what the function prints is muted.
143
+
144
+ `init` also asks whether the search may rewrite the function itself
145
+ (`same-output`, the default). The AI then first proposes new versions of it,
146
+ with the same signature, that use the model differently: a loop that does
147
+ less work, a reply drafted several tokens at a time and checked. One ships
148
+ only if everything it returns matches the original's on fresh inputs and the
149
+ whole call is faster. The kernel search follows. `same-math` keeps your
150
+ function exactly as written and only makes the model's math faster.
151
+
152
+ A serving engine is marked the same way, whatever engine it is: mark the
153
+ function that handles one request (an engine's generate call, a server's route
154
+ handler). The engine may run the model on worker threads of its own, on a
155
+ stream of its own, and keep the model on its own objects (`engine.model`
156
+ beside your global): the search replays the request the same way, puts its
157
+ copies of the model everywhere the app holds it, and may rewrite the handler
158
+ and the code it calls.
159
+
160
+ An app that makes no request itself is asked for one. When the script ends
161
+ without calling the marked function, `init` asks for each of its arguments
162
+ (text, a number, a list, or a file beside the app: an image, a `.json`
163
+ conversation, a `.txt`); an argument named like one of the app's globals (its
164
+ `model`, its `tokenizer`) is taken from there. A server never ends: put its
165
+ serving loop under `if __name__ == "__main__":` and run
166
+ `backcountry init app.py --ask`, which loads the app without that block and
167
+ asks. One typed request is enough: the size questions repeat or cut it to
168
+ each size. An agent reads the questions with `--questions` and answers with
169
+ `--request "question=What is MLX?; history=chat.json"`.
170
+
171
+ What the app must do: run an MLX model (a PyTorch script is told so before it
172
+ runs), make the same calls each time it runs (`init` and the search rerun it),
173
+ and give the same output, doing the same work, for the same input (seeded randomness is fine; a serving engine's prompt cache, which skips reading a request it has just read, is not: `check` says so). The
174
+ app's own arguments go after `--` (`backcountry init app.py -- --image cat.png`).
175
+
176
+ `backcountry.yaml` holds what you chose: the sizes (one line each), a request
177
+ you typed, whether the function may be rewritten, the attempts. Edit it and
178
+ the next `check` or `start` uses it. Its paths are read from its own folder, so
179
+ a project copied or cloned to another Mac runs as it is: commit
180
+ `backcountry.yaml` and `.backcountry/backcountry_model.py` with your code (the
181
+ rest of `.backcountry/`, this machine's recording, is git-ignored). `check`
182
+ says how much headroom the hardware leaves at each size ("up to 14%
183
+ headroom"; under 5%: "may not be worth a run"), what fits in memory, and that
184
+ the AI is signed in.
185
+
186
+ ## While it runs
187
+
188
+ The commands below act on the newest run under `runs/` in your project;
189
+ `--work-dir` names another.
190
+
191
+ ```sh
192
+ backcountry status # one screen; --json for scripts
193
+ backcountry status --wait # returns at the next milestone: a kernel shipped, a region
194
+ # opened or closed, a pause or crash, the final result
195
+ backcountry finish # stop new attempts; still runs the final checks and saves the result
196
+ backcountry skip # leave a part of the model that isn't going anywhere
197
+ backcountry pause # need the Mac back (sleep, restart, other GPU work)?
198
+ backcountry resume # pick it up where it stopped
199
+ ```
200
+
201
+ `--attempts N` (at `init`, or in `backcountry.yaml`) is how many proposals the
202
+ search may try (up to 25 on any one part of the model). There is no time
203
+ limit: an attempt takes minutes on a small model and up to half an hour on a
204
+ large one, and the run ends when its attempts are spent (or at
205
+ `backcountry finish`). After the search, each kernel is checked at every size
206
+ in the range, then timed and saved: that is extra time. The result says how
207
+ many attempts it rests on and how long each part took.
208
+
209
+ Each run's folder holds `report.json` (every measurement, attempt and failure,
210
+ plus the final outcome), `run.jsonl` and `candidates.log` (progress and a
211
+ result for each attempt). The final summary says which of three things
212
+ happened: a verified result, no confirmed improvement, or a failure. A kernel
213
+ that is fast on its own doesn't count; only a confirmed whole-request speedup
214
+ ships.
215
+
216
+ ## Using the result
217
+
218
+ A marked function needs nothing more: its first call installs the kernels
219
+ into the model it uses (and, when the search found a faster version of the
220
+ function, calls that from then on). A faster version is made for the
221
+ function's code as the run saw it: once you edit the function, the app runs
222
+ your edited code (it says so; the kernels still apply) until a new run. To run
223
+ it untouched, set `BACKCOUNTRY_OFF=1`, or keep the mark with
224
+ `@backcountry.tune(apply=False)`; to remove the result, delete its folder.
225
+
226
+ Elsewhere, install a result into a model you hold:
227
+
228
+ ```python
229
+ from backcountry import apply
230
+ model = apply(model) # always use the returned model
231
+ ```
232
+
233
+ Everything after that line (generation, batching, caches) stays as it was.
234
+ A finished run also copies its result to `~/.backcountry/artifacts/` (set
235
+ `BACKCOUNTRY_HOME` to move it), and `apply(model)` uses the newest one there
236
+ whose layers your model has; a model none fits runs as it is, with a warning.
237
+ To use a particular result, name its folder: `apply(model, "path/to/artifact")`.
238
+
239
+ **See it yourself, through your own code.** `compare` calls your function with
240
+ the model as loaded and with the optimization switched in, alternately, on one
241
+ copy of the weights, and tells you whether the outputs are identical and how
242
+ much faster it is:
243
+
244
+ ```python
245
+ from backcountry import compare
246
+ print(compare(lambda m: generate(m, tokenizer, prompt=my_prompt, max_tokens=100), model))
247
+ # 48 layers replaced; outputs identical; 1.124x faster (as loaded 1.503 s, optimized 1.337 s, ...)
248
+ ```
249
+
250
+ Keep in mind:
251
+
252
+ - **Weights aren't included.** You load them yourself as usual. The model's
253
+ architecture and quantization have to match what was optimized, but the
254
+ weight values can differ.
255
+ - **Only tested input sizes get the new kernels.** Anything else quietly runs
256
+ the original code, so it's still correct, just not faster.
257
+ - **Each layer is checked first.** A layer that isn't the one the kernels were
258
+ made for (another model, quantization or MLX version) keeps its own
259
+ code, with a warning.
260
+ - **Speed is measured on the run's chip.** On another Mac the outputs are the
261
+ same, and `apply()` warns that the speed there is unmeasured.
262
+
263
+ More in [the result guide](https://github.com/shivamg05/backcountry/blob/main/docs/artifacts.md).
264
+
265
+ ## Other ways to run it
266
+
267
+ A language model MLX-LM loads can be named directly, with no app:
268
+
269
+ ```sh
270
+ backcountry init mlx-community/Qwen3-8B-4bit
271
+ ```
272
+
273
+ `init` then asks whether prompts, replies or both should get faster, the
274
+ sizes, whether tokens may come from a faster generation loop (`same-output`)
275
+ or only faster kernels (`same-math`), and how many attempts to make. Use the
276
+ result through MLX-LM's own functions, imported from backcountry: they take
277
+ the same arguments and give the same results, and use the loop stored for the
278
+ model you pass (any other model runs MLX-LM's own):
279
+
280
+ ```python
281
+ from backcountry import apply, generate # or stream_generate, generate_step
282
+ model = apply(model)
283
+ text = generate(model, tokenizer, prompt, max_tokens=256)
284
+ ```
285
+
286
+ For full control (several kinds of call, your own limits, a size range), write
287
+ a model file and a manifest yourself: see the [usage guide](https://github.com/shivamg05/backcountry/blob/main/docs/usage.md) and
288
+ [write a manifest](https://github.com/shivamg05/backcountry/blob/main/docs/manifest.md). Any supported AI can write the kernels:
289
+ `--judge codex --model YOUR_MODEL_ID`, for example.
290
+
291
+ ## Further reading
292
+
293
+ - [Limitations](https://github.com/shivamg05/backcountry/blob/main/docs/limitations.md): what a result does and doesn't promise.
294
+ Worth reading before you quote a number.
295
+ - [RUNNING.md](https://github.com/shivamg05/backcountry/blob/main/RUNNING.md): where an agent running a job for you should start.
296
+ - [Architecture](https://github.com/shivamg05/backcountry/blob/main/docs/architecture.md): how the harness works and the rules it never breaks.
297
+ - [Judge protocol](https://github.com/shivamg05/backcountry/blob/main/docs/judge-protocol.md): how to plug in another AI provider,
298
+ or answer kernel requests by hand.
299
+
300
+ ## License
301
+
302
+ MIT.
@@ -0,0 +1,275 @@
1
+ # backcountry
2
+
3
+ Make an MLX model faster on Apple Silicon without changing its answers.
4
+
5
+ You mark the function your app calls for one request. An AI proposes faster
6
+ versions: custom Metal GPU code (kernels) for parts of the model and, if you
7
+ allow it, a faster way to use the model (a reply drafted several tokens at a
8
+ time and checked). A separate harness tests every one, and keeps it only if
9
+ the outputs still match the original (bit for bit, or within a tolerance you
10
+ control when the math is reordered) and the **whole request** gets measurably
11
+ faster. Whatever survives is saved beside your code, and your function uses it
12
+ from its next call.
13
+
14
+ Sometimes nothing survives. That's a real answer, not a crash: no candidate
15
+ beat your code by a margin the tool could confirm within its attempt budget.
16
+
17
+ ## Quickstart
18
+
19
+ You need an Apple Silicon Mac and Python 3.12, 3.13 or 3.14.
20
+
21
+ 1. **Install [Claude Code](https://code.claude.com/docs/en/setup)**, run
22
+ `claude` once, sign in, and exit. Claude Code is the AI that writes the
23
+ kernels, so your account needs Claude Code access. A signed-in Codex or
24
+ Gemini CLI works too, or the Anthropic API with `ANTHROPIC_API_KEY`.
25
+ 2. **Install backcountry** in the environment your app runs in:
26
+
27
+ ```sh
28
+ pip install backcountry # or: uv add backcountry
29
+ ```
30
+
31
+ 3. **Mark the function** your app calls for one request, the one that turns
32
+ an input into an output (writes a reply, makes an image, classifies a
33
+ batch), with two lines:
34
+
35
+ ```python
36
+ import backcountry
37
+
38
+ @backcountry.tune
39
+ def generate_text(model, tokenizer, prompt, max_tokens=150): # your function, unchanged
40
+ ...
41
+ ```
42
+
43
+ 4. **Run four commands** from your project's folder:
44
+
45
+ ```sh
46
+ backcountry init app.py # runs app.py once; asks the sizes to optimize
47
+ backcountry check backcountry.yaml # minutes, no attempts: does it run, fit, have room?
48
+ backcountry start backcountry.yaml # the search, in the background
49
+ backcountry status --wait # returns at each milestone
50
+ ```
51
+
52
+ A few things to know while it runs:
53
+
54
+ - **Expect it to take hours.** Each attempt is checked and timed, and the
55
+ budget counts attempts, not minutes. `start` runs in the background, so you
56
+ can close the terminal.
57
+ - **Keep other GPU work quiet.** Background load makes the timings noisy.
58
+ - **Kernel requests use your AI provider's allowance** (subscription or API
59
+ credits).
60
+
61
+ When it finishes, the result is a folder beside the file that defines your
62
+ function, `backcountry-results/<function>/`. Commit it, or ship it with your
63
+ code: the function uses it from its next call, and says so once.
64
+
65
+ ## Run it through an AI agent
66
+
67
+ Don't want to answer questions and watch logs? Install backcountry, then paste
68
+ this into a coding agent (Claude Code, Codex, …) opened on your project:
69
+
70
+ ```text
71
+ Run `backcountry guide` and follow it to make my app faster.
72
+ Ask me what you need, and update me only at milestones.
73
+ ```
74
+
75
+ `backcountry guide` prints the steps for the installed version: where to put
76
+ the mark, which questions to ask you, how to start and watch the run. There
77
+ are two AIs here, and they can come from different providers: the agent you're
78
+ chatting with runs and watches the job; the one named by `--judge` writes the
79
+ kernels.
80
+
81
+ ## What init asks
82
+
83
+ The function can do whatever one request does in your app: tokenize, run
84
+ your own loop with your own cache, sample, stop at an end token, decode,
85
+ post-process, print. `init` runs `app.py` as you would (`python app.py`) and
86
+ records each call of the marked function and the model it uses (passed in,
87
+ held by an object it is given, or a global, even one a library keeps for
88
+ itself). Then it tries each argument of the first call at another size and
89
+ watches what the model does, so it asks only about the ones that change the
90
+ work:
91
+
92
+ ```
93
+ These change how much work the model does:
94
+ 1. width and height (together)
95
+ These run the model more or fewer times (one value, used for every size):
96
+ 2. steps
97
+ These don't change the work:
98
+ prompt, seed
99
+ Sizes for width and height (together): a list like 512x512, 1024x768 [default: 1024x1024]: 512x512, 1024x1024
100
+ steps: the value to use for every size [default: 4]:
101
+ ```
102
+
103
+ The same questions come for any model: a prompt's length in tokens, a batch's
104
+ count, an array's axis, an image's width and height, any number argument.
105
+ Each question's default is the sizes your app called it with (here the
106
+ script made one 1024x1024 image), so pressing Enter optimizes what the app
107
+ actually runs. Answer with a list (`1, 8, 32`), a range (`32-4096`, spread into five sizes)
108
+ or pairs (`512x512, 1024x768`); at most eight sizes in all, since each is
109
+ checked and timed. `init` makes the call at every size once before writing
110
+ anything; agents pass the answers by number (`--sizes "1=512x512, 1024x1024; 2=4"`).
111
+ The search replays your function at each size, from the arguments your app
112
+ passed, repeated or cut to the size (never made-up data); it compares what the
113
+ function returns (arrays, numbers, text by its bytes, images) and times the
114
+ whole call, so the speedup it reports is your request's. Random draws are
115
+ seeded the same for every replay, and what the function prints is muted.
116
+
117
+ `init` also asks whether the search may rewrite the function itself
118
+ (`same-output`, the default). The AI then first proposes new versions of it,
119
+ with the same signature, that use the model differently: a loop that does
120
+ less work, a reply drafted several tokens at a time and checked. One ships
121
+ only if everything it returns matches the original's on fresh inputs and the
122
+ whole call is faster. The kernel search follows. `same-math` keeps your
123
+ function exactly as written and only makes the model's math faster.
124
+
125
+ A serving engine is marked the same way, whatever engine it is: mark the
126
+ function that handles one request (an engine's generate call, a server's route
127
+ handler). The engine may run the model on worker threads of its own, on a
128
+ stream of its own, and keep the model on its own objects (`engine.model`
129
+ beside your global): the search replays the request the same way, puts its
130
+ copies of the model everywhere the app holds it, and may rewrite the handler
131
+ and the code it calls.
132
+
133
+ An app that makes no request itself is asked for one. When the script ends
134
+ without calling the marked function, `init` asks for each of its arguments
135
+ (text, a number, a list, or a file beside the app: an image, a `.json`
136
+ conversation, a `.txt`); an argument named like one of the app's globals (its
137
+ `model`, its `tokenizer`) is taken from there. A server never ends: put its
138
+ serving loop under `if __name__ == "__main__":` and run
139
+ `backcountry init app.py --ask`, which loads the app without that block and
140
+ asks. One typed request is enough: the size questions repeat or cut it to
141
+ each size. An agent reads the questions with `--questions` and answers with
142
+ `--request "question=What is MLX?; history=chat.json"`.
143
+
144
+ What the app must do: run an MLX model (a PyTorch script is told so before it
145
+ runs), make the same calls each time it runs (`init` and the search rerun it),
146
+ and give the same output, doing the same work, for the same input (seeded randomness is fine; a serving engine's prompt cache, which skips reading a request it has just read, is not: `check` says so). The
147
+ app's own arguments go after `--` (`backcountry init app.py -- --image cat.png`).
148
+
149
+ `backcountry.yaml` holds what you chose: the sizes (one line each), a request
150
+ you typed, whether the function may be rewritten, the attempts. Edit it and
151
+ the next `check` or `start` uses it. Its paths are read from its own folder, so
152
+ a project copied or cloned to another Mac runs as it is: commit
153
+ `backcountry.yaml` and `.backcountry/backcountry_model.py` with your code (the
154
+ rest of `.backcountry/`, this machine's recording, is git-ignored). `check`
155
+ says how much headroom the hardware leaves at each size ("up to 14%
156
+ headroom"; under 5%: "may not be worth a run"), what fits in memory, and that
157
+ the AI is signed in.
158
+
159
+ ## While it runs
160
+
161
+ The commands below act on the newest run under `runs/` in your project;
162
+ `--work-dir` names another.
163
+
164
+ ```sh
165
+ backcountry status # one screen; --json for scripts
166
+ backcountry status --wait # returns at the next milestone: a kernel shipped, a region
167
+ # opened or closed, a pause or crash, the final result
168
+ backcountry finish # stop new attempts; still runs the final checks and saves the result
169
+ backcountry skip # leave a part of the model that isn't going anywhere
170
+ backcountry pause # need the Mac back (sleep, restart, other GPU work)?
171
+ backcountry resume # pick it up where it stopped
172
+ ```
173
+
174
+ `--attempts N` (at `init`, or in `backcountry.yaml`) is how many proposals the
175
+ search may try (up to 25 on any one part of the model). There is no time
176
+ limit: an attempt takes minutes on a small model and up to half an hour on a
177
+ large one, and the run ends when its attempts are spent (or at
178
+ `backcountry finish`). After the search, each kernel is checked at every size
179
+ in the range, then timed and saved: that is extra time. The result says how
180
+ many attempts it rests on and how long each part took.
181
+
182
+ Each run's folder holds `report.json` (every measurement, attempt and failure,
183
+ plus the final outcome), `run.jsonl` and `candidates.log` (progress and a
184
+ result for each attempt). The final summary says which of three things
185
+ happened: a verified result, no confirmed improvement, or a failure. A kernel
186
+ that is fast on its own doesn't count; only a confirmed whole-request speedup
187
+ ships.
188
+
189
+ ## Using the result
190
+
191
+ A marked function needs nothing more: its first call installs the kernels
192
+ into the model it uses (and, when the search found a faster version of the
193
+ function, calls that from then on). A faster version is made for the
194
+ function's code as the run saw it: once you edit the function, the app runs
195
+ your edited code (it says so; the kernels still apply) until a new run. To run
196
+ it untouched, set `BACKCOUNTRY_OFF=1`, or keep the mark with
197
+ `@backcountry.tune(apply=False)`; to remove the result, delete its folder.
198
+
199
+ Elsewhere, install a result into a model you hold:
200
+
201
+ ```python
202
+ from backcountry import apply
203
+ model = apply(model) # always use the returned model
204
+ ```
205
+
206
+ Everything after that line (generation, batching, caches) stays as it was.
207
+ A finished run also copies its result to `~/.backcountry/artifacts/` (set
208
+ `BACKCOUNTRY_HOME` to move it), and `apply(model)` uses the newest one there
209
+ whose layers your model has; a model none fits runs as it is, with a warning.
210
+ To use a particular result, name its folder: `apply(model, "path/to/artifact")`.
211
+
212
+ **See it yourself, through your own code.** `compare` calls your function with
213
+ the model as loaded and with the optimization switched in, alternately, on one
214
+ copy of the weights, and tells you whether the outputs are identical and how
215
+ much faster it is:
216
+
217
+ ```python
218
+ from backcountry import compare
219
+ print(compare(lambda m: generate(m, tokenizer, prompt=my_prompt, max_tokens=100), model))
220
+ # 48 layers replaced; outputs identical; 1.124x faster (as loaded 1.503 s, optimized 1.337 s, ...)
221
+ ```
222
+
223
+ Keep in mind:
224
+
225
+ - **Weights aren't included.** You load them yourself as usual. The model's
226
+ architecture and quantization have to match what was optimized, but the
227
+ weight values can differ.
228
+ - **Only tested input sizes get the new kernels.** Anything else quietly runs
229
+ the original code, so it's still correct, just not faster.
230
+ - **Each layer is checked first.** A layer that isn't the one the kernels were
231
+ made for (another model, quantization or MLX version) keeps its own
232
+ code, with a warning.
233
+ - **Speed is measured on the run's chip.** On another Mac the outputs are the
234
+ same, and `apply()` warns that the speed there is unmeasured.
235
+
236
+ More in [the result guide](https://github.com/shivamg05/backcountry/blob/main/docs/artifacts.md).
237
+
238
+ ## Other ways to run it
239
+
240
+ A language model MLX-LM loads can be named directly, with no app:
241
+
242
+ ```sh
243
+ backcountry init mlx-community/Qwen3-8B-4bit
244
+ ```
245
+
246
+ `init` then asks whether prompts, replies or both should get faster, the
247
+ sizes, whether tokens may come from a faster generation loop (`same-output`)
248
+ or only faster kernels (`same-math`), and how many attempts to make. Use the
249
+ result through MLX-LM's own functions, imported from backcountry: they take
250
+ the same arguments and give the same results, and use the loop stored for the
251
+ model you pass (any other model runs MLX-LM's own):
252
+
253
+ ```python
254
+ from backcountry import apply, generate # or stream_generate, generate_step
255
+ model = apply(model)
256
+ text = generate(model, tokenizer, prompt, max_tokens=256)
257
+ ```
258
+
259
+ For full control (several kinds of call, your own limits, a size range), write
260
+ a model file and a manifest yourself: see the [usage guide](https://github.com/shivamg05/backcountry/blob/main/docs/usage.md) and
261
+ [write a manifest](https://github.com/shivamg05/backcountry/blob/main/docs/manifest.md). Any supported AI can write the kernels:
262
+ `--judge codex --model YOUR_MODEL_ID`, for example.
263
+
264
+ ## Further reading
265
+
266
+ - [Limitations](https://github.com/shivamg05/backcountry/blob/main/docs/limitations.md): what a result does and doesn't promise.
267
+ Worth reading before you quote a number.
268
+ - [RUNNING.md](https://github.com/shivamg05/backcountry/blob/main/RUNNING.md): where an agent running a job for you should start.
269
+ - [Architecture](https://github.com/shivamg05/backcountry/blob/main/docs/architecture.md): how the harness works and the rules it never breaks.
270
+ - [Judge protocol](https://github.com/shivamg05/backcountry/blob/main/docs/judge-protocol.md): how to plug in another AI provider,
271
+ or answer kernel requests by hand.
272
+
273
+ ## License
274
+
275
+ MIT.
@@ -0,0 +1,64 @@
1
+ # Run an optimization
2
+
3
+ Start with [README.md](README.md) for installation and the four commands.
4
+ Detailed model and manifest options are in [docs/usage.md](docs/usage.md).
5
+
6
+ If you are an agent operating a run for someone, read
7
+ [docs/operating.md](docs/operating.md) before starting. It defines preflight,
8
+ event-driven progress updates, failure reporting, and how to finish early
9
+ without losing final validation. Follow the requested manifest and judge;
10
+ do not inspect other runs unless asked.
11
+
12
+ ```sh
13
+ backcountry start path/to/manifest.yaml --judge claude-cli # in the background
14
+ backcountry status --wait # repeat: each call returns at the next milestone; relay it
15
+ ```
16
+
17
+ `start` keeps running if the chat session or terminal closes; `status` (add
18
+ `--json` for fields) works from any later session. `run` does the same in the
19
+ foreground.
20
+
21
+ The default output is a fresh timestamped folder under `runs/`. You can specify
22
+ `--work-dir runs/YOUR_LABEL` or another location. Never reuse a
23
+ nonempty directory or run competing GPU benchmarks alongside an optimization.
24
+
25
+ ## Pause and resume
26
+
27
+ A run can stop and continue later, in a new process, where it left off:
28
+
29
+ ```sh
30
+ backcountry pause # the newest run; --work-dir names another
31
+ backcountry resume # later, from the same folder
32
+ ```
33
+
34
+ - **Pause before the Mac sleeps, restarts or needs its GPU.** The job stops at
35
+ its next safe point, after the attempt it is on, with nothing left running
36
+ on the GPU, and exits with status `paused`. An attempt can take a while (on
37
+ FLUX, half take over 10 minutes and the longest took an hour). If the
38
+ operator can't wait, press Ctrl-C instead and resume later: only the attempt
39
+ in progress is lost, and it is tried again. A sleeping Mac can also drop the
40
+ job's network; don't rely on the job surviving a sleep.
41
+ - **Resume** reuses the manifest and judge settings the job started with
42
+ (`--judge`, `--model`, `--judge-effort`, `--judge-cmd` override them). It
43
+ records the model again (seconds), restores everything setup measured,
44
+ reinstalls the accepted kernels, checks the model's outputs still match the
45
+ original, and continues the same region with the same history and budget. It
46
+ refuses if the manifest changed, and warns if the tool's code changed.
47
+ - **The job pauses itself** when the judge doesn't answer three times in a
48
+ row (no network, an expired login, a reply over 30 minutes). Those failed calls don't use attempts.
49
+ Fix the cause, then resume.
50
+ - **Ctrl-C, a crash or a killed process** can be resumed too, from the last
51
+ safe point: at most the one attempt in progress is repeated. A win accepted
52
+ in that attempt is found again; its checkpoint folder stays, and the repeat
53
+ is saved beside it.
54
+ - **A finished job deletes its `boundaries/`** (the captured region inputs
55
+ only a search reads, often tens of GB). A paused, failed or killed job keeps
56
+ them so it can resume.
57
+ - **An export that fails after a passed final check** (a crash, a bug, out of
58
+ memory) resumes at the export: the search and the final check are not run
59
+ again.
60
+ - Runs started before pause and resume existed have no saved state and can't
61
+ be resumed.
62
+
63
+ After completion, report the confirmed whole-workload result and artifact path,
64
+ or explain why nothing shipped. See [artifact usage](docs/artifacts.md).