bergson 2.2.2__tar.gz → 2.2.4__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 (180) hide show
  1. {bergson-2.2.2 → bergson-2.2.4}/PKG-INFO +5 -5
  2. {bergson-2.2.2 → bergson-2.2.4}/README.md +4 -4
  3. {bergson-2.2.2 → bergson-2.2.4}/bergson/__init__.py +1 -1
  4. {bergson-2.2.2 → bergson-2.2.4}/bergson/build.py +10 -2
  5. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/shampoo.py +8 -7
  6. {bergson-2.2.2 → bergson-2.2.4}/bergson/process_autocorrelation.py +41 -30
  7. bergson-2.2.4/bergson/utils/batch_size.py +260 -0
  8. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/worker_utils.py +4 -6
  9. {bergson-2.2.2 → bergson-2.2.4}/bergson.egg-info/PKG-INFO +5 -5
  10. {bergson-2.2.2 → bergson-2.2.4}/bergson.egg-info/SOURCES.txt +2 -0
  11. {bergson-2.2.2 → bergson-2.2.4}/pyproject.toml +4 -1
  12. bergson-2.2.4/tests/test_auto_batch_size.py +90 -0
  13. bergson-2.2.4/tests/test_process_autocorrelation.py +62 -0
  14. bergson-2.2.2/bergson/utils/batch_size.py +0 -309
  15. {bergson-2.2.2 → bergson-2.2.4}/LICENSE +0 -0
  16. {bergson-2.2.2 → bergson-2.2.4}/bergson/__main__.py +0 -0
  17. {bergson-2.2.2 → bergson-2.2.4}/bergson/approx_unrolling/adam_preconditioner.py +0 -0
  18. {bergson-2.2.2 → bergson-2.2.4}/bergson/approx_unrolling/approx_unrolling_math.py +0 -0
  19. {bergson-2.2.2 → bergson-2.2.4}/bergson/approx_unrolling/pipeline.py +0 -0
  20. {bergson-2.2.2 → bergson-2.2.4}/bergson/approx_unrolling/precompute_checkpoints.py +0 -0
  21. {bergson-2.2.2 → bergson-2.2.4}/bergson/approx_unrolling/segment_aggregation.py +0 -0
  22. {bergson-2.2.2 → bergson-2.2.4}/bergson/approx_unrolling/train_cfg_io.py +0 -0
  23. {bergson-2.2.2 → bergson-2.2.4}/bergson/builder.py +0 -0
  24. {bergson-2.2.2 → bergson-2.2.4}/bergson/cli/__init__.py +0 -0
  25. {bergson-2.2.2 → bergson-2.2.4}/bergson/cli/commands.py +0 -0
  26. {bergson-2.2.2 → bergson-2.2.4}/bergson/cli/trackstar.py +0 -0
  27. {bergson-2.2.2 → bergson-2.2.4}/bergson/cli/trak.py +0 -0
  28. {bergson-2.2.2 → bergson-2.2.4}/bergson/collection.py +0 -0
  29. {bergson-2.2.2 → bergson-2.2.4}/bergson/collector/__init__.py +0 -0
  30. {bergson-2.2.2 → bergson-2.2.4}/bergson/collector/collector.py +0 -0
  31. {bergson-2.2.2 → bergson-2.2.4}/bergson/collector/dist_autocorrelation_gradient_collector.py +0 -0
  32. {bergson-2.2.2 → bergson-2.2.4}/bergson/collector/gradient_collectors.py +0 -0
  33. {bergson-2.2.2 → bergson-2.2.4}/bergson/collector/in_memory_collector.py +0 -0
  34. {bergson-2.2.2 → bergson-2.2.4}/bergson/collector/projection_matrix.py +0 -0
  35. {bergson-2.2.2 → bergson-2.2.4}/bergson/config/__init__.py +0 -0
  36. {bergson-2.2.2 → bergson-2.2.4}/bergson/config/config.py +0 -0
  37. {bergson-2.2.2 → bergson-2.2.4}/bergson/config/config_io.py +0 -0
  38. {bergson-2.2.2 → bergson-2.2.4}/bergson/config/validation.py +0 -0
  39. {bergson-2.2.2 → bergson-2.2.4}/bergson/data.py +0 -0
  40. {bergson-2.2.2 → bergson-2.2.4}/bergson/diagnose.py +0 -0
  41. {bergson-2.2.2 → bergson-2.2.4}/bergson/distributed.py +0 -0
  42. {bergson-2.2.2 → bergson-2.2.4}/bergson/format.py +0 -0
  43. {bergson-2.2.2 → bergson-2.2.4}/bergson/gradients.py +0 -0
  44. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/apply_hessian.py +0 -0
  45. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/astra.py +0 -0
  46. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/autocorrelation.py +0 -0
  47. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/eigenvectors.py +0 -0
  48. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/hessian_approximations.py +0 -0
  49. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/inversion.py +0 -0
  50. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/kfac.py +0 -0
  51. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/pipeline.py +0 -0
  52. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/preconditioner.py +0 -0
  53. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/sharded_computation.py +0 -0
  54. {bergson-2.2.2 → bergson-2.2.4}/bergson/hessians/tkfac.py +0 -0
  55. {bergson-2.2.2 → bergson-2.2.4}/bergson/huggingface.py +0 -0
  56. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/__init__.py +0 -0
  57. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/cli.py +0 -0
  58. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/config.py +0 -0
  59. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/data_stream.py +0 -0
  60. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/dtensor_patch.py +0 -0
  61. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/fsdp.py +0 -0
  62. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/grad_accum.py +0 -0
  63. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/metasmoothness.py +0 -0
  64. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/optim.py +0 -0
  65. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/rtl_tqdm.py +0 -0
  66. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/score_plot.py +0 -0
  67. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/shard_load.py +0 -0
  68. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/swap.py +0 -0
  69. {bergson-2.2.2 → bergson-2.2.4}/bergson/magic/trainer.py +0 -0
  70. {bergson-2.2.2 → bergson-2.2.4}/bergson/moe.py +0 -0
  71. {bergson-2.2.2 → bergson-2.2.4}/bergson/process_grads.py +0 -0
  72. {bergson-2.2.2 → bergson-2.2.4}/bergson/query/__init__.py +0 -0
  73. {bergson-2.2.2 → bergson-2.2.4}/bergson/query/attributor.py +0 -0
  74. {bergson-2.2.2 → bergson-2.2.4}/bergson/query/faiss_index.py +0 -0
  75. {bergson-2.2.2 → bergson-2.2.4}/bergson/query/query_index.py +0 -0
  76. {bergson-2.2.2 → bergson-2.2.4}/bergson/recall/__init__.py +0 -0
  77. {bergson-2.2.2 → bergson-2.2.4}/bergson/recall/facts.py +0 -0
  78. {bergson-2.2.2 → bergson-2.2.4}/bergson/recall/generate.py +0 -0
  79. {bergson-2.2.2 → bergson-2.2.4}/bergson/recall/recall.py +0 -0
  80. {bergson-2.2.2 → bergson-2.2.4}/bergson/score/__init__.py +0 -0
  81. {bergson-2.2.2 → bergson-2.2.4}/bergson/score/candidates.py +0 -0
  82. {bergson-2.2.2 → bergson-2.2.4}/bergson/score/output_influence.py +0 -0
  83. {bergson-2.2.2 → bergson-2.2.4}/bergson/score/score.py +0 -0
  84. {bergson-2.2.2 → bergson-2.2.4}/bergson/score/score_writer.py +0 -0
  85. {bergson-2.2.2 → bergson-2.2.4}/bergson/score/scorer.py +0 -0
  86. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/__init__.py +0 -0
  87. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/csv_writer.py +0 -0
  88. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/gradcheck.py +0 -0
  89. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/load_from_optimizer.py +0 -0
  90. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/logger.py +0 -0
  91. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/logging.py +0 -0
  92. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/math.py +0 -0
  93. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/peft.py +0 -0
  94. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/step_state.py +0 -0
  95. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/trainer_export.py +0 -0
  96. {bergson-2.2.2 → bergson-2.2.4}/bergson/utils/utils.py +0 -0
  97. {bergson-2.2.2 → bergson-2.2.4}/bergson/validate.py +0 -0
  98. {bergson-2.2.2 → bergson-2.2.4}/bergson.egg-info/dependency_links.txt +0 -0
  99. {bergson-2.2.2 → bergson-2.2.4}/bergson.egg-info/entry_points.txt +0 -0
  100. {bergson-2.2.2 → bergson-2.2.4}/bergson.egg-info/requires.txt +0 -0
  101. {bergson-2.2.2 → bergson-2.2.4}/bergson.egg-info/top_level.txt +0 -0
  102. {bergson-2.2.2 → bergson-2.2.4}/setup.cfg +0 -0
  103. {bergson-2.2.2 → bergson-2.2.4}/tests/test_adam_state_loading.py +0 -0
  104. {bergson-2.2.2 → bergson-2.2.4}/tests/test_advantages.py +0 -0
  105. {bergson-2.2.2 → bergson-2.2.4}/tests/test_approx_unrolling_checkpoint_parse.py +0 -0
  106. {bergson-2.2.2 → bergson-2.2.4}/tests/test_attention_head_normalizers.py +0 -0
  107. {bergson-2.2.2 → bergson-2.2.4}/tests/test_attribute_tokens.py +0 -0
  108. {bergson-2.2.2 → bergson-2.2.4}/tests/test_attributor.py +0 -0
  109. {bergson-2.2.2 → bergson-2.2.4}/tests/test_bank_loss_cache.py +0 -0
  110. {bergson-2.2.2 → bergson-2.2.4}/tests/test_banked_model_loading.py +0 -0
  111. {bergson-2.2.2 → bergson-2.2.4}/tests/test_batch_size_invariance.py +0 -0
  112. {bergson-2.2.2 → bergson-2.2.4}/tests/test_build.py +0 -0
  113. {bergson-2.2.2 → bergson-2.2.4}/tests/test_builder.py +0 -0
  114. {bergson-2.2.2 → bergson-2.2.4}/tests/test_candidates.py +0 -0
  115. {bergson-2.2.2 → bergson-2.2.4}/tests/test_ckpt_avg_query_grads.py +0 -0
  116. {bergson-2.2.2 → bergson-2.2.4}/tests/test_cli.py +0 -0
  117. {bergson-2.2.2 → bergson-2.2.4}/tests/test_compute_lambda.py +0 -0
  118. {bergson-2.2.2 → bergson-2.2.4}/tests/test_config_runner.py +0 -0
  119. {bergson-2.2.2 → bergson-2.2.4}/tests/test_contrast.py +0 -0
  120. {bergson-2.2.2 → bergson-2.2.4}/tests/test_data.py +0 -0
  121. {bergson-2.2.2 → bergson-2.2.4}/tests/test_ddp.py +0 -0
  122. {bergson-2.2.2 → bergson-2.2.4}/tests/test_diagnose.py +0 -0
  123. {bergson-2.2.2 → bergson-2.2.4}/tests/test_distributed_batch_budget.py +0 -0
  124. {bergson-2.2.2 → bergson-2.2.4}/tests/test_distributed_cap.py +0 -0
  125. {bergson-2.2.2 → bergson-2.2.4}/tests/test_distributed_failure.py +0 -0
  126. {bergson-2.2.2 → bergson-2.2.4}/tests/test_distributed_magic.py +0 -0
  127. {bergson-2.2.2 → bergson-2.2.4}/tests/test_epoch_shuffle.py +0 -0
  128. {bergson-2.2.2 → bergson-2.2.4}/tests/test_faiss_ann_cpu_load.py +0 -0
  129. {bergson-2.2.2 → bergson-2.2.4}/tests/test_force_math_sdp.py +0 -0
  130. {bergson-2.2.2 → bergson-2.2.4}/tests/test_format.py +0 -0
  131. {bergson-2.2.2 → bergson-2.2.4}/tests/test_global_kfac.py +0 -0
  132. {bergson-2.2.2 → bergson-2.2.4}/tests/test_global_projection.py +0 -0
  133. {bergson-2.2.2 → bergson-2.2.4}/tests/test_grad_clipping.py +0 -0
  134. {bergson-2.2.2 → bergson-2.2.4}/tests/test_gradcheck.py +0 -0
  135. {bergson-2.2.2 → bergson-2.2.4}/tests/test_gradients.py +0 -0
  136. {bergson-2.2.2 → bergson-2.2.4}/tests/test_hessian.py +0 -0
  137. {bergson-2.2.2 → bergson-2.2.4}/tests/test_launch_devices.py +0 -0
  138. {bergson-2.2.2 → bergson-2.2.4}/tests/test_log_fn.py +0 -0
  139. {bergson-2.2.2 → bergson-2.2.4}/tests/test_logit_scale.py +0 -0
  140. {bergson-2.2.2 → bergson-2.2.4}/tests/test_magic.py +0 -0
  141. {bergson-2.2.2 → bergson-2.2.4}/tests/test_metasmoothness.py +0 -0
  142. {bergson-2.2.2 → bergson-2.2.4}/tests/test_moe.py +0 -0
  143. {bergson-2.2.2 → bergson-2.2.4}/tests/test_multi_query_validate.py +0 -0
  144. {bergson-2.2.2 → bergson-2.2.4}/tests/test_multinode.py +0 -0
  145. {bergson-2.2.2 → bergson-2.2.4}/tests/test_muon.py +0 -0
  146. {bergson-2.2.2 → bergson-2.2.4}/tests/test_normalizer.py +0 -0
  147. {bergson-2.2.2 → bergson-2.2.4}/tests/test_outer_product_gradients.py +0 -0
  148. {bergson-2.2.2 → bergson-2.2.4}/tests/test_output_influence.py +0 -0
  149. {bergson-2.2.2 → bergson-2.2.4}/tests/test_padding.py +0 -0
  150. {bergson-2.2.2 → bergson-2.2.4}/tests/test_per_query_magic.py +0 -0
  151. {bergson-2.2.2 → bergson-2.2.4}/tests/test_per_token_lds.py +0 -0
  152. {bergson-2.2.2 → bergson-2.2.4}/tests/test_pretokenized.py +0 -0
  153. {bergson-2.2.2 → bergson-2.2.4}/tests/test_projection_inner_products.py +0 -0
  154. {bergson-2.2.2 → bergson-2.2.4}/tests/test_projection_matrix.py +0 -0
  155. {bergson-2.2.2 → bergson-2.2.4}/tests/test_projection_settings.py +0 -0
  156. {bergson-2.2.2 → bergson-2.2.4}/tests/test_query_batching_invariance.py +0 -0
  157. {bergson-2.2.2 → bergson-2.2.4}/tests/test_query_loss_padding.py +0 -0
  158. {bergson-2.2.2 → bergson-2.2.4}/tests/test_query_set_config.py +0 -0
  159. {bergson-2.2.2 → bergson-2.2.4}/tests/test_recall.py +0 -0
  160. {bergson-2.2.2 → bergson-2.2.4}/tests/test_reduce.py +0 -0
  161. {bergson-2.2.2 → bergson-2.2.4}/tests/test_save_mode_final.py +0 -0
  162. {bergson-2.2.2 → bergson-2.2.4}/tests/test_score.py +0 -0
  163. {bergson-2.2.2 → bergson-2.2.4}/tests/test_score_plot.py +0 -0
  164. {bergson-2.2.2 → bergson-2.2.4}/tests/test_score_writer_dist.py +0 -0
  165. {bergson-2.2.2 → bergson-2.2.4}/tests/test_shard_load.py +0 -0
  166. {bergson-2.2.2 → bergson-2.2.4}/tests/test_source_fisher_normalization.py +0 -0
  167. {bergson-2.2.2 → bergson-2.2.4}/tests/test_source_optimizer_variants.py +0 -0
  168. {bergson-2.2.2 → bergson-2.2.4}/tests/test_source_resume_polarity.py +0 -0
  169. {bergson-2.2.2 → bergson-2.2.4}/tests/test_source_trainer_integration.py +0 -0
  170. {bergson-2.2.2 → bergson-2.2.4}/tests/test_step_matrix.py +0 -0
  171. {bergson-2.2.2 → bergson-2.2.4}/tests/test_step_state.py +0 -0
  172. {bergson-2.2.2 → bergson-2.2.4}/tests/test_tokenize.py +0 -0
  173. {bergson-2.2.2 → bergson-2.2.4}/tests/test_trainer_callback.py +0 -0
  174. {bergson-2.2.2 → bergson-2.2.4}/tests/test_trak.py +0 -0
  175. {bergson-2.2.2 → bergson-2.2.4}/tests/test_truncation.py +0 -0
  176. {bergson-2.2.2 → bergson-2.2.4}/tests/test_validate_filter.py +0 -0
  177. {bergson-2.2.2 → bergson-2.2.4}/tests/test_validate_routing.py +0 -0
  178. {bergson-2.2.2 → bergson-2.2.4}/tests/test_validation_config.py +0 -0
  179. {bergson-2.2.2 → bergson-2.2.4}/tests/test_wandb_logging.py +0 -0
  180. {bergson-2.2.2 → bergson-2.2.4}/tests/test_weighted_ce.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bergson
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: Tracing the memory of neural nets with data attribution
5
5
  License: MIT License
6
6
  Keywords: interpretability,explainable-ai
@@ -91,16 +91,16 @@ Indicative performance of data attribution methods in the finetuning regime - se
91
91
  | Method | QLD | LDS | Per-token QLD |
92
92
  |:---|:---:|:---:|:---:|
93
93
  | MAGIC | 0.100 [0.090, 0.112] | 0.931 [0.925, 0.936] | 1.375 [1.348, 1.403] |
94
- | EK-FAC + ASTRA | 0.074 [0.063, 0.087] | 0.643 [0.624, 0.660] | 1.082 [1.047, 1.116] |
94
+ | EKFAC + ASTRA | 0.074 [0.063, 0.087] | 0.643 [0.624, 0.660] | 1.082 [1.047, 1.116] |
95
95
  | Eigenvalue-corrected Shampoo | 0.071 [0.060, 0.082] | 0.517 [0.491, 0.539] | 0.922 [0.887, 0.957] |
96
96
  | SOURCE (Adam, EK-FAC) | 0.071 [0.060, 0.084] | 0.473 [0.446, 0.498] | 0.908 [0.877, 0.942] |
97
- | EK-FAC | 0.070 [0.058, 0.082] | 0.454 [0.426, 0.479] | 0.863 [0.833, 0.894] |
97
+ | EKFAC | 0.070 [0.058, 0.082] | 0.454 [0.426, 0.479] | 0.863 [0.833, 0.894] |
98
98
  | BM25 | 0.062 [0.048, 0.076] | 0.220 [0.185, 0.252] | 0.677 [0.650, 0.704] |
99
+ | Activation similarity (activation covariance preconditioner) | 0.055 [0.043, 0.069] | 0.157 [0.120, 0.192] | 0.073 [0.056, 0.092] |
99
100
  | Semantic search ([Qwen3 8B](https://huggingface.co/Qwen/Qwen3-Embedding-8B)) | 0.049 [0.038, 0.061] | 0.132 [0.093, 0.169] | 0.008 [0.006, 0.010] |
100
- | TrackStar (no optimizer correction, projection 64) | 0.045 [0.036, 0.055] | 0.270 [0.240, 0.295] | 0.489 [0.465, 0.515] |
101
+ | TrackStar (no optimizer) | 0.045 [0.036, 0.055] | 0.276 [0.252, 0.299] | 0.492 [0.467, 0.517] |
101
102
  | TRAK (8-model ensemble) | 0.032 [0.024, 0.040] | 0.138 [0.111, 0.165] | 0.119 [0.112, 0.125] |
102
103
  | Gradient cosine similarity | 0.021 [0.016, 0.027] | 0.156 [0.131, 0.181] | 0.217 [0.206, 0.229] |
103
- | Activation similarity | 0.000 [-0.000, 0.001] | 0.110 [0.070, 0.149] | 0.028 [0.018, 0.039] |
104
104
 
105
105
  Results with 95% confidence intervals for GPT-2 finetuned on 4 epochs of the WikiText corpus. Held-out loss dropped from 3.545 to 3.111 over training.
106
106
 
@@ -30,16 +30,16 @@ Indicative performance of data attribution methods in the finetuning regime - se
30
30
  | Method | QLD | LDS | Per-token QLD |
31
31
  |:---|:---:|:---:|:---:|
32
32
  | MAGIC | 0.100 [0.090, 0.112] | 0.931 [0.925, 0.936] | 1.375 [1.348, 1.403] |
33
- | EK-FAC + ASTRA | 0.074 [0.063, 0.087] | 0.643 [0.624, 0.660] | 1.082 [1.047, 1.116] |
33
+ | EKFAC + ASTRA | 0.074 [0.063, 0.087] | 0.643 [0.624, 0.660] | 1.082 [1.047, 1.116] |
34
34
  | Eigenvalue-corrected Shampoo | 0.071 [0.060, 0.082] | 0.517 [0.491, 0.539] | 0.922 [0.887, 0.957] |
35
35
  | SOURCE (Adam, EK-FAC) | 0.071 [0.060, 0.084] | 0.473 [0.446, 0.498] | 0.908 [0.877, 0.942] |
36
- | EK-FAC | 0.070 [0.058, 0.082] | 0.454 [0.426, 0.479] | 0.863 [0.833, 0.894] |
36
+ | EKFAC | 0.070 [0.058, 0.082] | 0.454 [0.426, 0.479] | 0.863 [0.833, 0.894] |
37
37
  | BM25 | 0.062 [0.048, 0.076] | 0.220 [0.185, 0.252] | 0.677 [0.650, 0.704] |
38
+ | Activation similarity (activation covariance preconditioner) | 0.055 [0.043, 0.069] | 0.157 [0.120, 0.192] | 0.073 [0.056, 0.092] |
38
39
  | Semantic search ([Qwen3 8B](https://huggingface.co/Qwen/Qwen3-Embedding-8B)) | 0.049 [0.038, 0.061] | 0.132 [0.093, 0.169] | 0.008 [0.006, 0.010] |
39
- | TrackStar (no optimizer correction, projection 64) | 0.045 [0.036, 0.055] | 0.270 [0.240, 0.295] | 0.489 [0.465, 0.515] |
40
+ | TrackStar (no optimizer) | 0.045 [0.036, 0.055] | 0.276 [0.252, 0.299] | 0.492 [0.467, 0.517] |
40
41
  | TRAK (8-model ensemble) | 0.032 [0.024, 0.040] | 0.138 [0.111, 0.165] | 0.119 [0.112, 0.125] |
41
42
  | Gradient cosine similarity | 0.021 [0.016, 0.027] | 0.156 [0.131, 0.181] | 0.217 [0.206, 0.229] |
42
- | Activation similarity | 0.000 [-0.000, 0.001] | 0.110 [0.070, 0.149] | 0.028 [0.018, 0.039] |
43
43
 
44
44
  Results with 95% confidence intervals for GPT-2 finetuned on 4 epochs of the WikiText corpus. Held-out loss dropped from 3.545 to 3.111 over training.
45
45
 
@@ -1,4 +1,4 @@
1
- __version__ = "2.2.2"
1
+ __version__ = "2.2.4"
2
2
 
3
3
  import logging
4
4
 
@@ -84,12 +84,20 @@ def build_worker(
84
84
  model, target_modules = setup_model_and_peft(index_cfg)
85
85
  processor = create_processor(model, index_cfg, target_modules)
86
86
 
87
- maybe_auto_batch_size(index_cfg, model, ds, processor, target_modules, rank)
88
-
89
87
  attention_cfgs = {
90
88
  module: index_cfg.attention for module in index_cfg.split_attention_modules
91
89
  }
92
90
 
91
+ maybe_auto_batch_size(
92
+ index_cfg,
93
+ model,
94
+ ds,
95
+ processor,
96
+ target_modules,
97
+ attention_cfgs,
98
+ preprocess_cfg,
99
+ )
100
+
93
101
  kwargs = {
94
102
  "model": model,
95
103
  "data": ds,
@@ -15,14 +15,15 @@ from bergson.utils.utils import assert_type
15
15
  @dataclass(kw_only=True)
16
16
  class ShampooCollector(HookCollectorBase):
17
17
  """
18
- Collects activation and gradient covariances for TKFAC.
18
+ Collects activation and gradient covariances for Shampoo.
19
19
 
20
20
  Computes:
21
- A_shampoo = sum over batches of Grad @ Grad.T for activations
22
- S_shampoo = sum over batches of Grad.T @ Grad for gradients * trace(A_cov)
21
+ A_shampoo = sum over batches of (Grad.T @ Grad) / trace for activations
22
+ S_shampoo = sum over batches of (Grad @ Grad.T) for gradients
23
23
 
24
-
25
- where X is input activations [N*S, I] and G is output gradients [N*S, O].
24
+ where Grad = G.T @ X is the batch gradient [O, I], X is input activations
25
+ [N*S, I], G is output gradients [N*S, O], and trace is the trace of the
26
+ summed activation factor.
26
27
  """
27
28
 
28
29
  dtype: torch.dtype
@@ -42,7 +43,7 @@ class ShampooCollector(HookCollectorBase):
42
43
  )
43
44
 
44
45
  def forward_hook(self, module: nn.Module, a: Tensor) -> None:
45
- """Compute activation covariance: A^T @ A."""
46
+ """Store the input activations for the backward hook."""
46
47
 
47
48
  mask = self.collection_mask(module)
48
49
  assert mask is not None, "Collection mask not set for forward hook."
@@ -60,7 +61,7 @@ class ShampooCollector(HookCollectorBase):
60
61
  module._inputs = a_bi
61
62
 
62
63
  def backward_hook(self, module: nn.Module, g: Tensor) -> None:
63
- """Compute gradient covariance: G^T @ G."""
64
+ """Accumulate gradient and activation covariances, A.T @ A and G.T @ G."""
64
65
  name = assert_type(str, module._name)
65
66
  S_shampoo_po = self.S_shampoo_dict[name]
66
67
  A_shampoo_ki = self.A_shampoo_dict[name]
@@ -4,6 +4,14 @@ import torch.distributed as dist
4
4
  from bergson.gradients import GradientProcessor
5
5
 
6
6
 
7
+ def _eigh(prec: torch.Tensor, device, dtype) -> tuple[torch.Tensor, torch.Tensor]:
8
+ eigvals, eigvecs = torch.linalg.eigh(prec.to(dtype=torch.float64, device=device))
9
+ return (
10
+ eigvals.to(dtype=dtype).contiguous().cpu(),
11
+ eigvecs.to(dtype=dtype).contiguous().cpu(),
12
+ )
13
+
14
+
7
15
  def process_autocorrelation_matrices(
8
16
  processor: GradientProcessor,
9
17
  hessians: dict[str, torch.Tensor],
@@ -20,9 +28,11 @@ def process_autocorrelation_matrices(
20
28
  count when ``attribute_tokens`` produced per-token rows — so the
21
29
  normalized matrix is the second moment over whichever unit the rows
22
30
  represent.
23
- """
24
- hessians_eigen = {}
25
31
 
32
+ A rank may hold a partial Gram over its own rows for every module, or the
33
+ full Gram for the modules it owns. Each module's Gram is summed onto one
34
+ of the ranks that hold it, which eigendecomposes the total.
35
+ """
26
36
  device = next(iter(hessians.values())).device
27
37
  dtype = next(iter(hessians.values())).dtype
28
38
 
@@ -32,57 +42,58 @@ def process_autocorrelation_matrices(
32
42
  for name, prec in hessians.items():
33
43
  hessians[name] = (prec / num_rows).cpu()
34
44
 
35
- if rank == 0:
36
- print("Computing hessian eigen decompositions...")
37
-
38
- for name in hessians.keys():
39
- prec = hessians[name].to(dtype=torch.float64, device=device)
40
- eigvals, eigvecs = torch.linalg.eigh(prec)
41
- hessians_eigen[name] = (
42
- eigvals.to(dtype=dtype).contiguous().cpu(),
43
- eigvecs.to(dtype=dtype).contiguous().cpu(),
44
- )
45
-
46
45
  if not dist.is_initialized():
46
+ if rank == 0:
47
+ print("Computing hessian eigen decompositions...")
47
48
  processor.hessians = hessians
48
- processor.hessians_eigen = hessians_eigen
49
+ processor.hessians_eigen = {
50
+ name: _eigh(prec, device, dtype) for name, prec in hessians.items()
51
+ }
49
52
  return
50
53
 
51
54
  if rank == 0:
52
- print("Gathering hessians...")
55
+ print("Reducing hessians and computing eigen decompositions...")
53
56
 
54
57
  cpu_group: dist.ProcessGroup = dist.new_group(backend="gloo") # type: ignore[assignment]
55
-
56
- for name, grad_size in grad_sizes.items():
58
+ world_size = dist.get_world_size()
59
+ held: list[list[str]] = [[] for _ in range(world_size)]
60
+ dist.all_gather_object(held, sorted(hessians), group=cpu_group)
61
+
62
+ totals, hessians_eigen = {}, {}
63
+ for i, (name, grad_size) in enumerate(grad_sizes.items()):
64
+ holders = [r for r in range(world_size) if name in held[r]] or [0]
65
+ owner = holders[i % len(holders)]
57
66
  if name in hessians:
58
- local_prec = hessians[name]
59
- del hessians[name]
67
+ local_prec = hessians.pop(name)
60
68
  else:
61
69
  local_prec = torch.zeros([grad_size, grad_size], dtype=dtype, device="cpu")
62
70
 
63
- dist.reduce(local_prec, dst=0, op=dist.ReduceOp.SUM, group=cpu_group)
71
+ dist.reduce(local_prec, dst=owner, op=dist.ReduceOp.SUM, group=cpu_group)
64
72
 
65
- if rank == 0:
66
- hessians[name] = local_prec
73
+ if rank == owner:
74
+ totals[name] = local_prec
75
+ hessians_eigen[name] = _eigh(local_prec, device, dtype)
67
76
 
68
77
  if rank == 0:
69
- processor.hessians = hessians
70
-
71
- print("Gathering eigen decompositions...")
78
+ print("Gathering hessians and eigen decompositions...")
72
79
 
73
80
  for name, grad_size in grad_sizes.items():
74
81
  prec_size = torch.Size([grad_size, grad_size])
75
- if name not in hessians_eigen:
82
+ prec = totals.pop(name, None)
83
+ if name in hessians_eigen:
84
+ eigval, eigvec = hessians_eigen[name]
85
+ else:
86
+ prec = torch.zeros(prec_size, dtype=dtype)
76
87
  eigval = torch.zeros(prec_size[0], dtype=dtype)
77
88
  eigvec = torch.zeros(prec_size, dtype=dtype)
78
- else:
79
- eigval, eigvec = hessians_eigen[name]
80
89
 
81
- dist.reduce(eigval, dst=0, op=dist.ReduceOp.SUM, group=cpu_group)
82
- dist.reduce(eigvec, dst=0, op=dist.ReduceOp.SUM, group=cpu_group)
90
+ for t in (prec, eigval, eigvec):
91
+ dist.reduce(t, dst=0, op=dist.ReduceOp.SUM, group=cpu_group)
83
92
 
84
93
  if rank == 0:
94
+ hessians[name] = prec
85
95
  hessians_eigen[name] = (eigval, eigvec)
86
96
 
87
97
  if rank == 0:
98
+ processor.hessians = hessians
88
99
  processor.hessians_eigen = hessians_eigen
@@ -0,0 +1,260 @@
1
+ import dataclasses
2
+ import gc
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+ import torch
10
+ import torch.distributed as dist
11
+ from datasets import Dataset
12
+ from peft import PeftModel
13
+ from transformers import AutoTokenizer, PreTrainedModel
14
+
15
+ from bergson.collector.collector import fwd_bwd_factory
16
+ from bergson.collector.gradient_collectors import GradientCollector
17
+ from bergson.config.config import AttentionConfig, IndexConfig, PreprocessConfig
18
+ from bergson.data import _allocate_batches_world, pad_and_tensor
19
+ from bergson.gradients import GradientProcessor
20
+ from bergson.utils.worker_utils import max_tokens_for_model
21
+
22
+ CACHE_PATH = Path(".cache") / "batch_size_cache.jsonl"
23
+
24
+ PROBE_MEMORY_FRACTION = 0.9
25
+ """Share of device memory the probe batches may use, leaving the rest for the
26
+ fragmentation a full run builds up."""
27
+
28
+ UNKEYED_FIELDS = {
29
+ "run_path",
30
+ "overwrite",
31
+ "token_batch_size",
32
+ "auto_batch_size",
33
+ "distributed",
34
+ "debug",
35
+ "profile",
36
+ }
37
+ """IndexConfig fields that leave a run's memory unchanged."""
38
+
39
+
40
+ def maybe_auto_batch_size(
41
+ cfg: IndexConfig,
42
+ model: PreTrainedModel | PeftModel,
43
+ ds: Dataset,
44
+ processor: GradientProcessor,
45
+ target_modules: set[str] | None,
46
+ attention_cfgs: dict[str, AttentionConfig],
47
+ preprocess_cfg: PreprocessConfig,
48
+ ) -> None:
49
+ """Set ``cfg.token_batch_size`` to the largest budget whose batches fit, if
50
+ ``cfg.auto_batch_size``.
51
+
52
+ Every rank probes the same batches and the ranks agree on each result, so
53
+ sharded models and multi-node runs search together.
54
+ """
55
+ if not cfg.auto_batch_size:
56
+ return
57
+
58
+ lengths = [int(n) for n in ds["length"][:]]
59
+ key = _cache_key(cfg, preprocess_cfg, lengths)
60
+ rank = dist.get_rank() if dist.is_initialized() else 0
61
+
62
+ found = [_check_cache(CACHE_PATH, key) if rank == 0 else None]
63
+ if dist.is_initialized():
64
+ dist.broadcast_object_list(found, src=0)
65
+ if found[0] is not None:
66
+ cfg.token_batch_size = found[0]
67
+ print(f"Loaded token_batch_size from cache: {found[0]}")
68
+ return
69
+
70
+ tokenizer = AutoTokenizer.from_pretrained(cfg.tokenizer or cfg.model)
71
+ max_len = max_tokens_for_model(tokenizer, cfg.model, cfg.revision)
72
+ collector = GradientCollector(
73
+ model=model.base_model, # type: ignore
74
+ cfg=cfg,
75
+ processor=processor,
76
+ target_modules=target_modules,
77
+ data=ds,
78
+ preprocess_cfg=preprocess_cfg,
79
+ attention_cfgs=attention_cfgs,
80
+ filter_modules=cfg.filter_modules,
81
+ )
82
+ try:
83
+ cfg.token_batch_size = determine_batch_size(
84
+ cfg, model, ds, collector, lengths, max_len
85
+ )
86
+ finally:
87
+ del collector
88
+ _clear_cache()
89
+
90
+ if rank == 0:
91
+ _append_to_cache(CACHE_PATH, key, cfg.token_batch_size)
92
+
93
+
94
+ def determine_batch_size(
95
+ cfg: IndexConfig,
96
+ model: PreTrainedModel | PeftModel,
97
+ ds: Dataset,
98
+ collector: GradientCollector,
99
+ lengths: list[int],
100
+ max_len: int,
101
+ ) -> int:
102
+ """Find the largest token budget whose batches fit, doubling from the
103
+ longest document and then bisecting."""
104
+ # Documents longer than the budget can't be batched, and a budget past
105
+ # the context length is rejected when the data is tokenized
106
+ lo = max(lengths)
107
+ limit = max(lo, min(max_len, sum(lengths)))
108
+ print(f"Determining token_batch_size in [{lo}, {limit}]...")
109
+ if not _fits(cfg, model, ds, collector, lengths, lo):
110
+ raise RuntimeError(
111
+ f"token_batch_size={lo}, the longest document, does not fit in memory."
112
+ )
113
+
114
+ hi = None
115
+ while lo < limit:
116
+ candidate = min(2 * lo, limit)
117
+ if not _fits(cfg, model, ds, collector, lengths, candidate):
118
+ hi = candidate
119
+ break
120
+ lo = candidate
121
+
122
+ # lo fits and hi, when set, does not
123
+ while hi is not None and hi - lo > max(256, lo // 16):
124
+ mid = (lo + hi) // 2
125
+ if _fits(cfg, model, ds, collector, lengths, mid):
126
+ lo = mid
127
+ else:
128
+ hi = mid
129
+
130
+ print(f"Optimal token_batch_size: {lo}")
131
+ return lo
132
+
133
+
134
+ def probe_batches(
135
+ lengths: list[int], N: int, world_size: int, max_batch_size: int | None
136
+ ) -> list[list[int]]:
137
+ """The batches of budget ``N`` with the most padded tokens, the most
138
+ documents and the most attention entries, over every rank's batches."""
139
+ batches = [
140
+ b
141
+ for rank in _allocate_batches_world(
142
+ lengths, N, world_size, max_batch_size=max_batch_size
143
+ )
144
+ for b in rank
145
+ ]
146
+
147
+ def shape(b):
148
+ return len(b), max(lengths[i] for i in b)
149
+
150
+ keys = (
151
+ lambda b: shape(b)[0] * shape(b)[1],
152
+ lambda b: shape(b)[0],
153
+ lambda b: shape(b)[0] * shape(b)[1] ** 2,
154
+ )
155
+ return [max(batches, key=key) for key in keys]
156
+
157
+
158
+ def _fits(
159
+ cfg: IndexConfig,
160
+ model: PreTrainedModel | PeftModel,
161
+ ds: Dataset,
162
+ collector: GradientCollector,
163
+ lengths: list[int],
164
+ N: int,
165
+ ) -> bool:
166
+ """Whether every rank runs the probe batches of budget ``N`` in memory."""
167
+ world_size = dist.get_world_size() if dist.is_initialized() else 1
168
+ device = torch.device(model.device) # type: ignore[attr-defined]
169
+ fwd_bwd = fwd_bwd_factory(cfg)
170
+
171
+ print(f" Testing {N}...", end=" ", flush=True)
172
+ fits = True
173
+ _clear_cache()
174
+ if device.type == "cuda":
175
+ torch.cuda.set_per_process_memory_fraction(PROBE_MEMORY_FRACTION, device)
176
+ try:
177
+ for indices in probe_batches(lengths, N, world_size, cfg.max_batch_size):
178
+ batch = ds[indices]
179
+ x, y, _, collection_mask = pad_and_tensor(
180
+ batch["input_ids"],
181
+ labels=batch.get("labels"),
182
+ device=device,
183
+ sync_max_len=False,
184
+ )
185
+ with collector.with_batch(collection_mask):
186
+ losses = fwd_bwd(model, x, y, batch)
187
+ collector.process_batch(indices, losses=losses)
188
+ except torch.cuda.OutOfMemoryError:
189
+ fits = False
190
+ finally:
191
+ collector.mod_grads.clear()
192
+ model.zero_grad(set_to_none=True)
193
+ if device.type == "cuda":
194
+ torch.cuda.set_per_process_memory_fraction(1.0, device)
195
+ _clear_cache()
196
+
197
+ if dist.is_initialized():
198
+ flag = torch.tensor(int(fits), device=device)
199
+ dist.all_reduce(flag, op=dist.ReduceOp.MIN)
200
+ fits = bool(flag.item())
201
+ print("fits" if fits else "OOM")
202
+ return fits
203
+
204
+
205
+ def _clear_cache() -> None:
206
+ """Aggressively clear memory."""
207
+ gc.collect()
208
+ if torch.cuda.is_available():
209
+ torch.cuda.empty_cache()
210
+
211
+
212
+ def _cache_key(
213
+ cfg: IndexConfig, preprocess_cfg: PreprocessConfig, lengths: list[int]
214
+ ) -> dict[str, Any]:
215
+ """Everything a run's memory depends on: its configs, the document lengths,
216
+ the device and the number of ranks."""
217
+ index = {
218
+ k: v for k, v in dataclasses.asdict(cfg).items() if k not in UNKEYED_FIELDS
219
+ }
220
+ gpu_name, gpu_mem = "cpu", 0
221
+ if torch.cuda.is_available():
222
+ gpu_name = torch.cuda.get_device_name(0)
223
+ gpu_mem = torch.cuda.get_device_properties(0).total_memory
224
+ configs = json.dumps(
225
+ [index, dataclasses.asdict(preprocess_cfg)], sort_keys=True, default=str
226
+ )
227
+ return {
228
+ "configs": hashlib.sha256(configs.encode()).hexdigest(),
229
+ "lengths": hashlib.sha256(np.asarray(lengths, np.int64).tobytes()).hexdigest(),
230
+ "world_size": dist.get_world_size() if dist.is_initialized() else 1,
231
+ "gpu_name": gpu_name,
232
+ "gpu_memory": gpu_mem,
233
+ "torch": torch.__version__,
234
+ }
235
+
236
+
237
+ def _check_cache(cache_file: Path, key: dict[str, Any]) -> int | None:
238
+ """The batch size cached under ``key``, the latest if there are several."""
239
+ if not cache_file.exists():
240
+ return None
241
+
242
+ found = None
243
+ with open(cache_file) as f:
244
+ for line in f:
245
+ try:
246
+ row = json.loads(line)
247
+ except json.JSONDecodeError:
248
+ continue
249
+ if row.get("key") == key:
250
+ found = row.get("token_batch_size")
251
+ return found
252
+
253
+
254
+ def _append_to_cache(cache_file: Path, key: dict[str, Any], batch_size: int) -> None:
255
+ """Append a new row to the JSONL cache file."""
256
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
257
+ with open(cache_file, "a") as f:
258
+ f.write(json.dumps({"key": key, "token_batch_size": batch_size}) + "\n")
259
+
260
+ print(f"Cached batch size {batch_size} to {cache_file}")
@@ -217,13 +217,11 @@ def setup_model_and_peft(
217
217
  model_kwargs.update(simple_parse_kwargs_string(cfg.model_kwargs))
218
218
 
219
219
  if meta_init:
220
- config = AutoConfig.from_pretrained(base_model_path, revision=cfg.revision)
220
+ config = AutoConfig.from_pretrained(
221
+ base_model_path, revision=cfg.revision, **model_kwargs
222
+ )
221
223
  with torch.device("meta"):
222
- model = AutoModelForCausalLM.from_config(
223
- config,
224
- dtype=dtype,
225
- **model_kwargs,
226
- )
224
+ model = AutoModelForCausalLM.from_config(config, dtype=dtype)
227
225
  else:
228
226
  model = AutoModelForCausalLM.from_pretrained(
229
227
  base_model_path,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bergson
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: Tracing the memory of neural nets with data attribution
5
5
  License: MIT License
6
6
  Keywords: interpretability,explainable-ai
@@ -91,16 +91,16 @@ Indicative performance of data attribution methods in the finetuning regime - se
91
91
  | Method | QLD | LDS | Per-token QLD |
92
92
  |:---|:---:|:---:|:---:|
93
93
  | MAGIC | 0.100 [0.090, 0.112] | 0.931 [0.925, 0.936] | 1.375 [1.348, 1.403] |
94
- | EK-FAC + ASTRA | 0.074 [0.063, 0.087] | 0.643 [0.624, 0.660] | 1.082 [1.047, 1.116] |
94
+ | EKFAC + ASTRA | 0.074 [0.063, 0.087] | 0.643 [0.624, 0.660] | 1.082 [1.047, 1.116] |
95
95
  | Eigenvalue-corrected Shampoo | 0.071 [0.060, 0.082] | 0.517 [0.491, 0.539] | 0.922 [0.887, 0.957] |
96
96
  | SOURCE (Adam, EK-FAC) | 0.071 [0.060, 0.084] | 0.473 [0.446, 0.498] | 0.908 [0.877, 0.942] |
97
- | EK-FAC | 0.070 [0.058, 0.082] | 0.454 [0.426, 0.479] | 0.863 [0.833, 0.894] |
97
+ | EKFAC | 0.070 [0.058, 0.082] | 0.454 [0.426, 0.479] | 0.863 [0.833, 0.894] |
98
98
  | BM25 | 0.062 [0.048, 0.076] | 0.220 [0.185, 0.252] | 0.677 [0.650, 0.704] |
99
+ | Activation similarity (activation covariance preconditioner) | 0.055 [0.043, 0.069] | 0.157 [0.120, 0.192] | 0.073 [0.056, 0.092] |
99
100
  | Semantic search ([Qwen3 8B](https://huggingface.co/Qwen/Qwen3-Embedding-8B)) | 0.049 [0.038, 0.061] | 0.132 [0.093, 0.169] | 0.008 [0.006, 0.010] |
100
- | TrackStar (no optimizer correction, projection 64) | 0.045 [0.036, 0.055] | 0.270 [0.240, 0.295] | 0.489 [0.465, 0.515] |
101
+ | TrackStar (no optimizer) | 0.045 [0.036, 0.055] | 0.276 [0.252, 0.299] | 0.492 [0.467, 0.517] |
101
102
  | TRAK (8-model ensemble) | 0.032 [0.024, 0.040] | 0.138 [0.111, 0.165] | 0.119 [0.112, 0.125] |
102
103
  | Gradient cosine similarity | 0.021 [0.016, 0.027] | 0.156 [0.131, 0.181] | 0.217 [0.206, 0.229] |
103
- | Activation similarity | 0.000 [-0.000, 0.001] | 0.110 [0.070, 0.149] | 0.028 [0.018, 0.039] |
104
104
 
105
105
  Results with 95% confidence intervals for GPT-2 finetuned on 4 epochs of the WikiText corpus. Held-out loss dropped from 3.545 to 3.111 over training.
106
106
 
@@ -101,6 +101,7 @@ tests/test_approx_unrolling_checkpoint_parse.py
101
101
  tests/test_attention_head_normalizers.py
102
102
  tests/test_attribute_tokens.py
103
103
  tests/test_attributor.py
104
+ tests/test_auto_batch_size.py
104
105
  tests/test_bank_loss_cache.py
105
106
  tests/test_banked_model_loading.py
106
107
  tests/test_batch_size_invariance.py
@@ -145,6 +146,7 @@ tests/test_padding.py
145
146
  tests/test_per_query_magic.py
146
147
  tests/test_per_token_lds.py
147
148
  tests/test_pretokenized.py
149
+ tests/test_process_autocorrelation.py
148
150
  tests/test_projection_inner_products.py
149
151
  tests/test_projection_matrix.py
150
152
  tests/test_projection_settings.py
@@ -37,7 +37,7 @@ dependencies = [
37
37
  "bitsandbytes",
38
38
  "huggingface-hub>=1.13.0",
39
39
  ]
40
- version = "2.2.2"
40
+ version = "2.2.4"
41
41
  [project.optional-dependencies]
42
42
  dev = [
43
43
  "pre-commit",
@@ -93,6 +93,9 @@ reportPrivateImportUsage = false
93
93
  [tool.setuptools.packages.find]
94
94
  include = ["bergson*"]
95
95
 
96
+ [tool.uv.pip]
97
+ torch-backend = "auto"
98
+
96
99
  [tool.black]
97
100
  target-version = ["py310", "py311", "py312"]
98
101
 
@@ -0,0 +1,90 @@
1
+ import subprocess
2
+ from dataclasses import replace
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+ import torch
7
+
8
+ from bergson.config import IndexConfig, PreprocessConfig
9
+ from bergson.data import _allocate_batches_world
10
+ from bergson.utils.batch_size import _cache_key, probe_batches
11
+
12
+ from .cli_command import bergson_cmd, bergson_env
13
+
14
+
15
+ def test_probe_batches_cover_every_rank():
16
+ lengths = [512] * 4 + [8] * 300 + [100] * 40
17
+ world_size = 4
18
+ probes = probe_batches(lengths, 1024, world_size, None)
19
+ batches = [
20
+ b for rank in _allocate_batches_world(lengths, 1024, world_size) for b in rank
21
+ ]
22
+
23
+ def docs(b):
24
+ return len(b)
25
+
26
+ def padded(b):
27
+ return len(b) * max(lengths[i] for i in b)
28
+
29
+ assert padded(probes[0]) == max(padded(b) for b in batches)
30
+ assert docs(probes[1]) == max(docs(b) for b in batches)
31
+ # The many-short-documents batch, which a probe of full-length rows misses
32
+ assert docs(probes[1]) == 1024 // 8
33
+
34
+
35
+ def test_cache_key_tracks_memory():
36
+ cfg = IndexConfig(run_path="a", model="EleutherAI/pythia-14m")
37
+ pre = PreprocessConfig()
38
+ lengths = [10, 20, 30]
39
+ key = _cache_key(cfg, pre, lengths)
40
+
41
+ assert _cache_key(replace(cfg, run_path="b", overwrite=True), pre, lengths) == key
42
+ assert _cache_key(replace(cfg, token_batch_size=4096), pre, lengths) == key
43
+ for changed in (
44
+ replace(cfg, attribute_tokens=True),
45
+ replace(cfg, include_bias=True),
46
+ replace(cfg, filter_modules="*.mlp.*"),
47
+ replace(cfg, loss_fn="kl"),
48
+ replace(cfg, max_batch_size=4),
49
+ ):
50
+ assert _cache_key(changed, pre, lengths) != key
51
+ assert _cache_key(cfg, replace(pre, aggregation="mean"), lengths) != key
52
+ assert _cache_key(cfg, pre, [10, 20, 31]) != key
53
+
54
+
55
+ @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
56
+ @pytest.mark.parametrize("extra", [[], ["--attribute_tokens"]])
57
+ def test_auto_batch_size_build(tmp_path: Path, extra: list[str]):
58
+ def build(run: str):
59
+ return subprocess.run(
60
+ bergson_cmd(
61
+ "build",
62
+ run,
63
+ "--model",
64
+ "EleutherAI/pythia-14m",
65
+ "--dataset",
66
+ "NeelNanda/pile-10k",
67
+ "--split",
68
+ "train[:100]",
69
+ "--truncation",
70
+ "--projection_dim",
71
+ "16",
72
+ "--token_batch_size",
73
+ "512",
74
+ "--auto_batch_size",
75
+ *extra,
76
+ ),
77
+ cwd=tmp_path,
78
+ env=bergson_env(),
79
+ capture_output=True,
80
+ text=True,
81
+ )
82
+
83
+ first = build("first")
84
+ assert first.returncode == 0, first.stderr
85
+ assert "Optimal token_batch_size" in first.stdout
86
+ assert (tmp_path / "first" / "gradients.bin").exists()
87
+
88
+ second = build("second")
89
+ assert second.returncode == 0, second.stderr
90
+ assert "Loaded token_batch_size from cache" in second.stdout