soup-cli 0.53.1__tar.gz → 0.53.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. {soup_cli-0.53.1 → soup_cli-0.53.3}/CONTRIBUTING.md +1 -1
  2. {soup_cli-0.53.1 → soup_cli-0.53.3}/PKG-INFO +98 -8
  3. {soup_cli-0.53.1 → soup_cli-0.53.3}/README.md +97 -7
  4. {soup_cli-0.53.1 → soup_cli-0.53.3}/SECURITY.md +7 -1
  5. {soup_cli-0.53.1 → soup_cli-0.53.3}/pyproject.toml +1 -1
  6. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/__init__.py +1 -1
  7. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/train.py +24 -1
  8. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/config/schema.py +30 -0
  9. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/loss_mask.py +68 -0
  10. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/sft_format.py +53 -8
  11. soup_cli-0.53.3/soup_cli/trainer/classifier.py +368 -0
  12. soup_cli-0.53.3/soup_cli/trainer/distill.py +428 -0
  13. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/dpo.py +4 -0
  14. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/grpo.py +27 -1
  15. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/sft.py +5 -0
  16. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/classifier.py +11 -7
  17. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/distill.py +11 -7
  18. soup_cli-0.53.3/soup_cli/utils/ebft_gdpo.py +472 -0
  19. soup_cli-0.53.3/soup_cli/utils/prm.py +175 -0
  20. soup_cli-0.53.3/soup_cli/utils/reasoning_effort.py +97 -0
  21. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/qa/v053_qa.md +79 -0
  22. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0500_part_e.py +7 -3
  23. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0520.py +23 -12
  24. soup_cli-0.53.3/tests/test_v0532.py +1664 -0
  25. soup_cli-0.53.3/tests/test_v0533.py +375 -0
  26. soup_cli-0.53.1/soup_cli/utils/ebft_gdpo.py +0 -191
  27. soup_cli-0.53.1/soup_cli/utils/prm.py +0 -100
  28. soup_cli-0.53.1/soup_cli/utils/reasoning_effort.py +0 -37
  29. {soup_cli-0.53.1 → soup_cli-0.53.3}/.dockerignore +0 -0
  30. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/FUNDING.yml +0 -0
  31. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  32. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  33. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  34. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/pull_request_template.md +0 -0
  35. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/workflows/ci.yml +0 -0
  36. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/workflows/docker.yml +0 -0
  37. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/workflows/publish.yml +0 -0
  38. {soup_cli-0.53.1 → soup_cli-0.53.3}/.github/workflows/recipe-validation.yml +0 -0
  39. {soup_cli-0.53.1 → soup_cli-0.53.3}/.gitignore +0 -0
  40. {soup_cli-0.53.1 → soup_cli-0.53.3}/CODEOWNERS +0 -0
  41. {soup_cli-0.53.1 → soup_cli-0.53.3}/CODE_OF_CONDUCT.md +0 -0
  42. {soup_cli-0.53.1 → soup_cli-0.53.3}/Dockerfile +0 -0
  43. {soup_cli-0.53.1 → soup_cli-0.53.3}/LICENSE +0 -0
  44. {soup_cli-0.53.1 → soup_cli-0.53.3}/NOTICE +0 -0
  45. {soup_cli-0.53.1 → soup_cli-0.53.3}/docker-compose.yml +0 -0
  46. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/README.md +0 -0
  47. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/dpo_chat.yaml +0 -0
  48. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/dpo_example.yaml +0 -0
  49. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/grpo_reasoning.yaml +0 -0
  50. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/rlhf_step1_sft.yaml +0 -0
  51. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/rlhf_step2_reward.yaml +0 -0
  52. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/rlhf_step3_ppo.yaml +0 -0
  53. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/sft_basic.yaml +0 -0
  54. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/configs/vision_llama.yaml +0 -0
  55. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/data/alpaca_tiny.jsonl +0 -0
  56. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/data/chat_preferences.jsonl +0 -0
  57. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/data/dpo_sample.jsonl +0 -0
  58. {soup_cli-0.53.1 → soup_cli-0.53.3}/examples/data/reasoning_math.jsonl +0 -0
  59. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup.png +0 -0
  60. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/__main__.py +0 -0
  61. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/autopilot/__init__.py +0 -0
  62. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/autopilot/analyzer.py +0 -0
  63. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/autopilot/decisions.py +0 -0
  64. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/autopilot/generate_config.py +0 -0
  65. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cans/__init__.py +0 -0
  66. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cans/pack.py +0 -0
  67. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cans/publish.py +0 -0
  68. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cans/run.py +0 -0
  69. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cans/schema.py +0 -0
  70. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cans/unpack.py +0 -0
  71. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cans/verify.py +0 -0
  72. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/cli.py +0 -0
  73. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/__init__.py +0 -0
  74. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/adapters.py +0 -0
  75. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/agent.py +0 -0
  76. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/autopilot.py +0 -0
  77. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/bench.py +0 -0
  78. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/can.py +0 -0
  79. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/chat.py +0 -0
  80. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/cost.py +0 -0
  81. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/data.py +0 -0
  82. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/data_forge.py +0 -0
  83. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/data_mix.py +0 -0
  84. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/data_score.py +0 -0
  85. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/delinearize_llama4.py +0 -0
  86. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/deploy.py +0 -0
  87. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/diff.py +0 -0
  88. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/doctor.py +0 -0
  89. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/eval.py +0 -0
  90. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/export.py +0 -0
  91. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/fetch.py +0 -0
  92. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/generate.py +0 -0
  93. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/history.py +0 -0
  94. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/infer.py +0 -0
  95. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/init.py +0 -0
  96. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/llama.py +0 -0
  97. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/merge.py +0 -0
  98. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/merge_sharded_fsdp_weights.py +0 -0
  99. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/migrate.py +0 -0
  100. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/monitor.py +0 -0
  101. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/plugins.py +0 -0
  102. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/profile.py +0 -0
  103. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/push.py +0 -0
  104. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/quantize.py +0 -0
  105. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/quickstart.py +0 -0
  106. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/recipes.py +0 -0
  107. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/registry.py +0 -0
  108. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/runs.py +0 -0
  109. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/serve.py +0 -0
  110. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/sweep.py +0 -0
  111. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/tui.py +0 -0
  112. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/ui.py +0 -0
  113. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/commands/why.py +0 -0
  114. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/config/__init__.py +0 -0
  115. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/config/loader.py +0 -0
  116. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/__init__.py +0 -0
  117. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/augment.py +0 -0
  118. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/chat_templates.py +0 -0
  119. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/collators.py +0 -0
  120. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/formats.py +0 -0
  121. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/loader.py +0 -0
  122. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/providers/__init__.py +0 -0
  123. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/providers/_utils.py +0 -0
  124. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/providers/anthropic.py +0 -0
  125. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/providers/ollama.py +0 -0
  126. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/providers/vllm.py +0 -0
  127. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/__init__.py +0 -0
  128. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/code.py +0 -0
  129. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/conversation.py +0 -0
  130. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/preference.py +0 -0
  131. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/qa.py +0 -0
  132. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/reasoning.py +0 -0
  133. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/tool_calling.py +0 -0
  134. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/templates/verifiable.py +0 -0
  135. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/traces/__init__.py +0 -0
  136. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/traces/pair_builder.py +0 -0
  137. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/traces/parsers.py +0 -0
  138. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/traces/quality.py +0 -0
  139. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/data/validator.py +0 -0
  140. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/__init__.py +0 -0
  141. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/arena.py +0 -0
  142. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/benchmarks_v0_43.py +0 -0
  143. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/calibrate.py +0 -0
  144. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/checkpoint_intelligence.py +0 -0
  145. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/custom.py +0 -0
  146. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/forgetting.py +0 -0
  147. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/gate.py +0 -0
  148. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/human.py +0 -0
  149. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/judge.py +0 -0
  150. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/leaderboard.py +0 -0
  151. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/eval/quant_check.py +0 -0
  152. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/experiment/__init__.py +0 -0
  153. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/experiment/tracker.py +0 -0
  154. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/migrate/__init__.py +0 -0
  155. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/migrate/axolotl.py +0 -0
  156. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/migrate/common.py +0 -0
  157. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/migrate/llamafactory.py +0 -0
  158. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/migrate/unsloth.py +0 -0
  159. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/monitoring/__init__.py +0 -0
  160. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/monitoring/callback.py +0 -0
  161. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/monitoring/display.py +0 -0
  162. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/monitoring/hf_push.py +0 -0
  163. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/monitoring/trace_logger.py +0 -0
  164. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/plugins/__init__.py +0 -0
  165. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/recipes/__init__.py +0 -0
  166. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/recipes/catalog.py +0 -0
  167. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/registry/__init__.py +0 -0
  168. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/registry/attach.py +0 -0
  169. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/registry/diff.py +0 -0
  170. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/registry/hashing.py +0 -0
  171. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/registry/store.py +0 -0
  172. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/__init__.py +0 -0
  173. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/audio.yaml +0 -0
  174. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/bco.yaml +0 -0
  175. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/chat.yaml +0 -0
  176. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/code.yaml +0 -0
  177. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/embedding.yaml +0 -0
  178. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/fetch_examples/llama-3.1-8b-lora.yaml +0 -0
  179. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/fetch_examples/qwen2.5-7b-dpo.yaml +0 -0
  180. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/fetch_examples/zero3-cpu-offload.json +0 -0
  181. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/ipo.yaml +0 -0
  182. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/kto.yaml +0 -0
  183. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/longcontext.yaml +0 -0
  184. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/manifest.json +0 -0
  185. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/medical.yaml +0 -0
  186. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/moe.yaml +0 -0
  187. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/orpo.yaml +0 -0
  188. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/pretrain.yaml +0 -0
  189. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/reasoning.yaml +0 -0
  190. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/rlhf.yaml +0 -0
  191. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/simpo.yaml +0 -0
  192. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/tool-calling.yaml +0 -0
  193. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/templates/vision.yaml +0 -0
  194. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/__init__.py +0 -0
  195. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/bco.py +0 -0
  196. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/embedding.py +0 -0
  197. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/ipo.py +0 -0
  198. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/kto.py +0 -0
  199. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/mlx_dpo.py +0 -0
  200. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/mlx_grpo.py +0 -0
  201. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/mlx_routing.py +0 -0
  202. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/mlx_sft.py +0 -0
  203. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/orpo.py +0 -0
  204. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/ppo.py +0 -0
  205. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/preference.py +0 -0
  206. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/pretrain.py +0 -0
  207. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/reward_model.py +0 -0
  208. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/rewards.py +0 -0
  209. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/trainer/simpo.py +0 -0
  210. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/tui_app.py +0 -0
  211. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/__init__.py +0 -0
  212. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/app.py +0 -0
  213. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/plugins/__init__.py +0 -0
  214. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/static/app.js +0 -0
  215. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/static/index.html +0 -0
  216. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/static/logo.png +0 -0
  217. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/static/logo.svg +0 -0
  218. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/ui/static/style.css +0 -0
  219. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/__init__.py +0 -0
  220. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/activation_offload.py +0 -0
  221. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/advanced_precision.py +0 -0
  222. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/agent_forge.py +0 -0
  223. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/agent_rollout.py +0 -0
  224. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/anthropic_messages.py +0 -0
  225. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/auto_quant.py +0 -0
  226. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/batch_probe.py +0 -0
  227. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/bitnet.py +0 -0
  228. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/block_expansion.py +0 -0
  229. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/checkpoint_trigger.py +0 -0
  230. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/constants.py +0 -0
  231. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/convergence.py +0 -0
  232. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/crash.py +0 -0
  233. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/cross_doc_attn.py +0 -0
  234. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/curriculum.py +0 -0
  235. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/curriculum_dynamic.py +0 -0
  236. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/cut_ce.py +0 -0
  237. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/data_forge.py +0 -0
  238. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/data_mix.py +0 -0
  239. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/data_pipeline.py +0 -0
  240. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/data_score.py +0 -0
  241. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/deepspeed.py +0 -0
  242. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/delinearize_llama4.py +0 -0
  243. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/demo_bundles.py +0 -0
  244. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/deploy_autopilot.py +0 -0
  245. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/deploy_measure.py +0 -0
  246. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/dpo_variants.py +0 -0
  247. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/encoding.py +0 -0
  248. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/errors.py +0 -0
  249. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/fetch_examples.py +0 -0
  250. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/flash_attn.py +0 -0
  251. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/fp8.py +0 -0
  252. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/freeze.py +0 -0
  253. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/fsdp.py +0 -0
  254. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/fsdp_consolidate.py +0 -0
  255. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/galore.py +0 -0
  256. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/gguf_quant.py +0 -0
  257. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/gpu.py +0 -0
  258. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/gpu_monitor.py +0 -0
  259. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/graceful_save.py +0 -0
  260. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/grad_accum.py +0 -0
  261. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/gradient_ckpt.py +0 -0
  262. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/grpo_long_context.py +0 -0
  263. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/grpo_variants.py +0 -0
  264. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/hf.py +0 -0
  265. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/hf_space.py +0 -0
  266. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/hubs.py +0 -0
  267. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/integrations.py +0 -0
  268. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/jinja_analyzer.py +0 -0
  269. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/kernel_picker.py +0 -0
  270. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/kv_cache.py +0 -0
  271. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/launcher.py +0 -0
  272. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/liger.py +0 -0
  273. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/llama_proxy.py +0 -0
  274. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/llama_server_timings.py +0 -0
  275. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/loftq_init.py +0 -0
  276. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/log_level.py +0 -0
  277. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/long_context.py +0 -0
  278. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/longlora.py +0 -0
  279. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/lr_finder.py +0 -0
  280. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/lr_groups.py +0 -0
  281. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/metrics.py +0 -0
  282. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/mii.py +0 -0
  283. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/mixed_precision.py +0 -0
  284. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/mlx.py +0 -0
  285. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/moe.py +0 -0
  286. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/moe_quant.py +0 -0
  287. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/multipack.py +0 -0
  288. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/multipack_sampler.py +0 -0
  289. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/multipack_trainer.py +0 -0
  290. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/neat_packing.py +0 -0
  291. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/ngram_spec.py +0 -0
  292. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/nlg_metrics.py +0 -0
  293. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/ollama.py +0 -0
  294. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/onboarding.py +0 -0
  295. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/optimizer_zoo.py +0 -0
  296. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/paths.py +0 -0
  297. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/peft_builder.py +0 -0
  298. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/peft_patches.py +0 -0
  299. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/peft_wiring.py +0 -0
  300. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/pipeline.py +0 -0
  301. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/preference_combine.py +0 -0
  302. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/profiler.py +0 -0
  303. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/profiling.py +0 -0
  304. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/profiling_v0_43.py +0 -0
  305. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/qat.py +0 -0
  306. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/qr_url.py +0 -0
  307. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/quality.py +0 -0
  308. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/quant_menu.py +0 -0
  309. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/reasoning_parser.py +0 -0
  310. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/recipe_dag.py +0 -0
  311. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/registry.py +0 -0
  312. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/relora.py +0 -0
  313. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/replay.py +0 -0
  314. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/ring_attention.py +0 -0
  315. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/run_cost.py +0 -0
  316. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/save_formats.py +0 -0
  317. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/server_tools.py +0 -0
  318. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/sglang.py +0 -0
  319. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/shortcuts.py +0 -0
  320. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/spec_pairing.py +0 -0
  321. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/spike_recovery.py +0 -0
  322. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/sse_train_stream.py +0 -0
  323. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/structured_output.py +0 -0
  324. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/sweep_config.py +0 -0
  325. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/tail_latency.py +0 -0
  326. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/tool_outputs.py +0 -0
  327. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/topology.py +0 -0
  328. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/tracing.py +0 -0
  329. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/trackers.py +0 -0
  330. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/trainer_plugins.py +0 -0
  331. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/trust_remote.py +0 -0
  332. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/tts.py +0 -0
  333. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/ui_env.py +0 -0
  334. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/unsloth.py +0 -0
  335. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/v028_features.py +0 -0
  336. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/vllm.py +0 -0
  337. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/vscode_setup.py +0 -0
  338. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/warmup.py +0 -0
  339. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_cli/utils/why.py +0 -0
  340. {soup_cli-0.53.1 → soup_cli-0.53.3}/soup_logo_svg.svg +0 -0
  341. {soup_cli-0.53.1 → soup_cli-0.53.3}/templates/chat.yaml +0 -0
  342. {soup_cli-0.53.1 → soup_cli-0.53.3}/templates/code.yaml +0 -0
  343. {soup_cli-0.53.1 → soup_cli-0.53.3}/templates/medical.yaml +0 -0
  344. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/__init__.py +0 -0
  345. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/conftest.py +0 -0
  346. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_adapters.py +0 -0
  347. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_advanced_peft.py +0 -0
  348. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_assistant_mask.py +0 -0
  349. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_audio.py +0 -0
  350. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_auto_tuning.py +0 -0
  351. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_autopilot.py +0 -0
  352. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_awq_gptq_export.py +0 -0
  353. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_batch_probe.py +0 -0
  354. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_bco.py +0 -0
  355. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_bench.py +0 -0
  356. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_bugfixes.py +0 -0
  357. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_callback.py +0 -0
  358. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_cans.py +0 -0
  359. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_chat.py +0 -0
  360. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_chat_template.py +0 -0
  361. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_cli.py +0 -0
  362. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_cli_subprocess.py +0 -0
  363. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_config.py +0 -0
  364. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_cost.py +0 -0
  365. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_crash_reporter.py +0 -0
  366. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_curriculum.py +0 -0
  367. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_data.py +0 -0
  368. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_data_augment.py +0 -0
  369. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_data_sample.py +0 -0
  370. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_data_split.py +0 -0
  371. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_data_tools.py +0 -0
  372. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_dataset_hub.py +0 -0
  373. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_dataset_registry.py +0 -0
  374. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_deepspeed.py +0 -0
  375. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_deploy_ollama.py +0 -0
  376. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_diff.py +0 -0
  377. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_display.py +0 -0
  378. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_doctor.py +0 -0
  379. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_dpo_example.py +0 -0
  380. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_dpo_variants.py +0 -0
  381. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_embedding.py +0 -0
  382. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_errors.py +0 -0
  383. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_eval.py +0 -0
  384. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_eval_gate.py +0 -0
  385. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_eval_platform.py +0 -0
  386. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_export.py +0 -0
  387. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_formats.py +0 -0
  388. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_fp8_recipe.py +0 -0
  389. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_freeze_training.py +0 -0
  390. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_generate.py +0 -0
  391. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_gpu.py +0 -0
  392. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_grpo.py +0 -0
  393. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_hf_integration.py +0 -0
  394. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_infer.py +0 -0
  395. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_inference_advanced.py +0 -0
  396. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_init.py +0 -0
  397. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_ipo.py +0 -0
  398. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_jinja_analyzer.py +0 -0
  399. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_kto.py +0 -0
  400. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_loader.py +0 -0
  401. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_log_level.py +0 -0
  402. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_loss_watchdog.py +0 -0
  403. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_merge.py +0 -0
  404. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_migrate.py +0 -0
  405. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_mlx_backend.py +0 -0
  406. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_moe.py +0 -0
  407. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_multi_adapter.py +0 -0
  408. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_multi_gpu.py +0 -0
  409. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_multipack_config.py +0 -0
  410. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_multipack_invariants.py +0 -0
  411. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_multipack_sampler.py +0 -0
  412. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_neat_packing.py +0 -0
  413. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_neftune_rslora.py +0 -0
  414. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_onnx_tensorrt_export.py +0 -0
  415. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_orpo.py +0 -0
  416. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_packing.py +0 -0
  417. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_part_a_wave1.py +0 -0
  418. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_part_a_wave2.py +0 -0
  419. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_part_b.py +0 -0
  420. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_part_c.py +0 -0
  421. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_part_d.py +0 -0
  422. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_part_e.py +0 -0
  423. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_part_f_hardening.py +0 -0
  424. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_peft_methods.py +0 -0
  425. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_peft_patches.py +0 -0
  426. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_performance.py +0 -0
  427. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_pissa_init.py +0 -0
  428. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_ppo.py +0 -0
  429. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_preference_dispatcher.py +0 -0
  430. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_preference_multi.py +0 -0
  431. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_preference_multi_runtime.py +0 -0
  432. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_pretrain.py +0 -0
  433. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_profile.py +0 -0
  434. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_profiling.py +0 -0
  435. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_progress.py +0 -0
  436. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_push.py +0 -0
  437. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_qat.py +0 -0
  438. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_quality_filter.py +0 -0
  439. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_quant_check.py +0 -0
  440. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_quant_menu.py +0 -0
  441. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_quickstart.py +0 -0
  442. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_rank_pattern.py +0 -0
  443. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_recipes.py +0 -0
  444. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_recipes_v031.py +0 -0
  445. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_registry.py +0 -0
  446. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_relora.py +0 -0
  447. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_replay.py +0 -0
  448. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_resume.py +0 -0
  449. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_rlvr.py +0 -0
  450. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_run_cost.py +0 -0
  451. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_runs.py +0 -0
  452. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_serve.py +0 -0
  453. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_server_generate.py +0 -0
  454. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_sglang_serve.py +0 -0
  455. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_simpo.py +0 -0
  456. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_smoke_train.py +0 -0
  457. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_speculative_decoding.py +0 -0
  458. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_sweep.py +0 -0
  459. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_synth_data_pro.py +0 -0
  460. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_templates_yaml.py +0 -0
  461. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_tensorboard.py +0 -0
  462. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_tool_calling.py +0 -0
  463. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_trace_to_pref.py +0 -0
  464. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_tracker.py +0 -0
  465. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_trainer_coverage_v035.py +0 -0
  466. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_trainer_init.py +0 -0
  467. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_training_intelligence.py +0 -0
  468. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_training_speed.py +0 -0
  469. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_trust_remote_code.py +0 -0
  470. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_tui.py +0 -0
  471. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_ui.py +0 -0
  472. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_ui_chat.py +0 -0
  473. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_ui_config_builder.py +0 -0
  474. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_ui_live_monitor.py +0 -0
  475. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_ui_metrics.py +0 -0
  476. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_unsloth.py +0 -0
  477. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0401_part_c.py +0 -0
  478. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0401_part_d.py +0 -0
  479. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0401_part_e.py +0 -0
  480. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0402_part_a.py +0 -0
  481. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0402_part_b.py +0 -0
  482. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0403_part_a.py +0 -0
  483. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0403_part_b.py +0 -0
  484. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0403_part_c.py +0 -0
  485. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0404_part_a.py +0 -0
  486. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0404_part_b.py +0 -0
  487. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0405_part_a.py +0 -0
  488. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0406_part_a.py +0 -0
  489. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0410_part_a.py +0 -0
  490. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0410_part_b.py +0 -0
  491. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0410_part_c.py +0 -0
  492. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0420.py +0 -0
  493. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0430_part_a.py +0 -0
  494. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0430_part_b.py +0 -0
  495. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0430_part_c.py +0 -0
  496. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0430_part_d.py +0 -0
  497. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0440_part_a.py +0 -0
  498. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0440_part_b.py +0 -0
  499. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0440_part_c.py +0 -0
  500. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0440_part_d.py +0 -0
  501. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0440_review_followups.py +0 -0
  502. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0450.py +0 -0
  503. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0460_part_a.py +0 -0
  504. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0460_part_b.py +0 -0
  505. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0470_part_a.py +0 -0
  506. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0470_part_b.py +0 -0
  507. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0480_part_a.py +0 -0
  508. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0480_part_b.py +0 -0
  509. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0490.py +0 -0
  510. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0500_part_a.py +0 -0
  511. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0500_part_b.py +0 -0
  512. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0500_part_c.py +0 -0
  513. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0500_part_d.py +0 -0
  514. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0510.py +0 -0
  515. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0530.py +0 -0
  516. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0531_109.py +0 -0
  517. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0531_139.py +0 -0
  518. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0531_142.py +0 -0
  519. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_v0531_82.py +0 -0
  520. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_validator.py +0 -0
  521. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_vision.py +0 -0
  522. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_vllm_serve.py +0 -0
  523. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_why.py +0 -0
  524. {soup_cli-0.53.1 → soup_cli-0.53.3}/tests/test_windows_encoding.py +0 -0
@@ -111,7 +111,7 @@ soup_cli/
111
111
  templates/ - 17 built-in soup.yaml templates (YAML + manifest.json) with load_template loader (v0.39.0, +bco v0.40.0)
112
112
  ui/ - Web UI (FastAPI + HTML/JS SPA)
113
113
 
114
- tests/ - Test suite (184 files, 7722 tests)
114
+ tests/ - Test suite (186 files, 7879 tests)
115
115
  examples/ - Real-world config examples and datasets
116
116
  ```
117
117
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soup-cli
3
- Version: 0.53.1
3
+ Version: 0.53.3
4
4
  Summary: Fine-tune LLMs in one command. No SSH, no config hell.
5
5
  Project-URL: Homepage, https://github.com/MakazhanAlpamys/Soup
6
6
  Project-URL: Repository, https://github.com/MakazhanAlpamys/Soup
@@ -134,14 +134,13 @@ soup train
134
134
 
135
135
  Latest highlights only. Full history: [GitHub Releases](https://github.com/MakazhanAlpamys/Soup/releases).
136
136
 
137
- **v0.53.1Quant Menu II + Export pipeline live**: Six v0.53.0 deferred stubs lifted autopilot pre-quantized detection, single-stage BNB-4bit merge, TorchAO PTQ export, Unsloth Dynamic 2.0 GGUF ladder via llama.cpp `imatrix`, and `soup deploy autopilot --measure` Quant-Lobotomy scorecard.
137
+ **v0.53.3GRPO Plus partial wiring**: Two surgical GRPO Plus fixes from the v0.50.0 deferred-stub family land cleanly. The four larger items (stability callback, variant loss kernels, PRM trainer, multi-objective preference live combine) are scope-deferred to v0.53.4 each warrants a focused release because they each require deep TRL trainer subclassing.
138
138
 
139
- - **Autopilot detects pre-quantized bases.** `TheBloke/Llama-2-7B-Chat-GPTQ` now recommends `gptq` instead of stacking BNB-4bit on top. Name-regex + `config.json` `quantization_config.quant_method` probe with cwd-containment + symlink rejection on the on-disk path. BNB aliases (`bitsandbytes_4bit` / `nf4` / `bnb_8bit`) canonicalise to `4bit` / `8bit`.
140
- - **`soup merge --save-format 4bit | 4bit_forced`.** Single BNB-4bit-quantized merged checkpoint without the dequant→merge→requant cycle. `4bit_forced` quantizes every Linear (including `lm_head`). Output path is cwd-contained + symlink-rejected at CLI dispatch.
141
- - **`soup export --format torchao --quant-config <yaml>`.** Live `torchao.quantize_` + `save_pretrained`. Closed per-scheme kwarg allowlist (Int4WeightOnly accepts `{group_size, inner_k_tiles}`, NVFP4 accepts nothing extra) defeats kwarg-injection through the YAML.
142
- - **`soup export --format gguf-ud --gguf-flavour <UD-Q4_K_XL | IQ2_M | Q4_0_4_4 | …>`.** Three-stage pipeline: HF f16 GGUF optional importance-matrix (UD ladder + low-bit IQ) quantize. All subprocess calls use argv-list form + 30-min timeout. Calibration JSONL is sanitised (null-byte stripped, newlines collapsed, 8 KB per-line + 50 MB total cap). POSIX `O_NOFOLLOW` defeats the TOCTOU race between the dispatch-time symlink check and the actual open.
143
- - **`soup deploy autopilot --measure --tasks <jsonl>`.** Loops every candidate quant through the v0.26.0 `eval/quant_check` scorer, renders OK/MINOR/MAJOR table, picks the best-by-delta candidate. Results cached at `~/.soup/deploy_autopilot_cache.json` (atomic write, 0o600 perms on POSIX, symlink-rejected on both load and save). Cache key is SHA-256 of `(base, profile, eval-tasks)`.
144
- - **+112 net new tests** (7610 → 7722) across `test_v0531_82.py`, `test_v0531_109.py`, `test_v0531_139.py`, `test_v0531_142.py`. Four review agents (python / code / security / tdd) ran; every CRITICAL / HIGH / MEDIUM / LOW finding fixed: per-scheme TorchAO kwarg allowlist (rejects dunders + unknown keys), corrected BNB 4-bit skip-modules kwarg name, shared `enforce_under_cwd_and_no_symlink` in `utils/paths.py` (single source of truth), `pick_best` switches from `max(after)` to `max(delta)` matching the v0.33.0 #54 design intent, `_run_convert_to_f16` verifies the convert script stays inside `llama_cpp_dir` via realpath + commonpath, `_safe_stderr` Rich-escapes subprocess stderr before exception propagation.
139
+ - **`grpo_fp16: true` is now wired** through `GRPOTrainerWrapper._build_precision_kwargs` — non-CUDA devices (CPU / MPS / XPU) skip HF Trainer's CUDA-specific `fp16`/`bf16` kwargs, CUDA + `grpo_fp16=True` forces FP16 for unsloth parity, default CUDA stays on bf16. The schema now rejects the silent-mutex combo `grpo_fp16=True + auto_mixed_precision=true` at config load with an actionable message naming both flags.
140
+ - **Vision GRPO base-model probe.** `KNOWN_VLM_REGEX` matches 10 VLM families (Qwen2-VL / Qwen2.5-VL / QVQ / Pixtral / InternVL / Llama-3.2-Vision / LLaVA / MiniCPM-V / Idefics / ShareGPT4V / Fuyu) with word-boundary anchors that reject substring noise like `"my-pixtralish"`. A YAML pairing `vision_grpo: true` with a non-VLM checkpoint is now rejected at schema load with a friendly message naming the expected families — instead of surfacing as a cryptic `"module has no attribute 'vision_tower'"` at trainer load time. Error message truncates the echoed base to 64 chars per the v0.34.0 redaction policy.
141
+ - **Validator ordering matters.** `_validate_grpo_fp16_amp_exclusive` short-circuits when `task != 'grpo'` so the v0.50.0 stability task-gate diagnosis ("`grpo_fp16` requires `task=grpo`") fires first regardless of validator execution order keeps the most actionable error in front of the user.
142
+ - **+37 net new tests** (7842 7879) across `test_v0533.py`. Four review agents (python / code / security / tdd) ran; every HIGH / MEDIUM / LOW finding fixed task-gate priority short-circuit, MPS device branch explicitly documented, 64-char error-message truncation, `Optional[object]` `object` parameter cleanup, QVQ regex coverage, 512-char exact-boundary regression test, explicit `grpo_fp16: false` produces bf16 path.
143
+ - **Local YAML smoke** confirms both fixes round-trip via `load_config_from_string`: known VLM bases pass, non-VLM bases plus `vision_grpo: true` are rejected, and the `grpo_fp16` + `auto_mixed_precision` combo is rejected with both flag names in the message.
145
144
 
146
145
  ## Why Soup?
147
146
 
@@ -366,6 +365,97 @@ soup init --template pretrain
366
365
  soup train
367
366
  ```
368
367
 
368
+ ## Knowledge Distillation
369
+
370
+ Train a small student model to match a larger teacher's output distribution.
371
+
372
+ ```yaml
373
+ base: HuggingFaceTB/SmolLM2-135M
374
+ task: distill
375
+ modality: text
376
+ backend: transformers
377
+
378
+ data:
379
+ train: ./data/chat.jsonl
380
+ max_length: 2048
381
+ chat_template: chatml
382
+
383
+ training:
384
+ teacher_model: meta-llama/Llama-3.1-8B
385
+ distill_divergence: forward_kl # kl | forward_kl | reverse_kl | js
386
+ distill_temperature: 2.0
387
+ epochs: 3
388
+ lr: 5e-5
389
+ quantization: 4bit # quantizes student only
390
+ ```
391
+
392
+ Loss = student CE + (T**2) × KL(teacher_logits / T || student_logits / T).
393
+ Teacher is loaded once, frozen via `requires_grad_(False)` + `.eval()`, and its
394
+ inputs / logits are auto-bridged across CPU / CUDA devices.
395
+
396
+ ## Sequence Classification
397
+
398
+ Train a classifier head on top of any base model — supports single-label,
399
+ multi-label, and cross-encoder reranking.
400
+
401
+ ```yaml
402
+ base: BAAI/bge-base-en-v1.5
403
+ task: classifier # or `reranker`, `cross_encoder`
404
+ modality: text
405
+ backend: transformers
406
+
407
+ data:
408
+ train: ./data/labelled.jsonl # rows: {"text": "...", "label": "spam"} or {"text": "...", "label": [0, 1, 0]}
409
+ max_length: 256
410
+
411
+ training:
412
+ num_labels: 3
413
+ classifier_kind: single_label # or `multi_label`
414
+ label_names: [ham, spam, promo] # required when labels are strings
415
+ epochs: 5
416
+ lr: 2e-5
417
+ batch_size: 32
418
+ ```
419
+
420
+ Routes `classifier` / `reranker` / `cross_encoder` through
421
+ `AutoModelForSequenceClassification`. Multi-label heads cap at 1024 entries per
422
+ row, dedup via set conversion, and reject null bytes in label strings.
423
+
424
+ ## Reasoning Effort + EOT Control
425
+
426
+ gpt-oss-style reasoning-effort control for instruction tuning.
427
+
428
+ ```yaml
429
+ training:
430
+ reasoning_effort: high # low | medium | high
431
+ train_on_eot: true # do NOT mask the EOT/EOS token in the loss
432
+ ```
433
+
434
+ `reasoning_effort` injects `<|reasoning_effort|>high<|/reasoning_effort|>` into
435
+ the system turn (creating one if absent). `train_on_eot=True` makes the model
436
+ learn when to stop generating by training on the trailing EOS token instead of
437
+ masking it out. Both are gated to the SFT-family of tasks.
438
+
439
+ ## EBFT / GDPO Loss Variants
440
+
441
+ Entropy-regularised SFT (`ebft_variant: structured | strided`) and generalised
442
+ DPO (`gdpo_variant: standard | length_normalized | margin`) — both attach
443
+ idempotently via `compute_loss` wrappers and auto-fire when the corresponding
444
+ variant field is set on `TrainingConfig`.
445
+
446
+ ```yaml
447
+ # SFT with EBFT structured
448
+ training:
449
+ ebft_variant: structured
450
+ ebft_temperature: 1.0
451
+
452
+ # DPO with GDPO length_normalized
453
+ task: dpo
454
+ training:
455
+ gdpo_variant: length_normalized
456
+ dpo_beta: 0.1
457
+ ```
458
+
369
459
  ## MoE Model Support
370
460
 
371
461
  Fine-tune Mixture of Experts models (Mixtral, Qwen3-30B-A3B, DeepSeek V3) with ScatterMoE LoRA — applies LoRA to both attention layers and expert FFN layers:
@@ -43,14 +43,13 @@ soup train
43
43
 
44
44
  Latest highlights only. Full history: [GitHub Releases](https://github.com/MakazhanAlpamys/Soup/releases).
45
45
 
46
- **v0.53.1Quant Menu II + Export pipeline live**: Six v0.53.0 deferred stubs lifted autopilot pre-quantized detection, single-stage BNB-4bit merge, TorchAO PTQ export, Unsloth Dynamic 2.0 GGUF ladder via llama.cpp `imatrix`, and `soup deploy autopilot --measure` Quant-Lobotomy scorecard.
46
+ **v0.53.3GRPO Plus partial wiring**: Two surgical GRPO Plus fixes from the v0.50.0 deferred-stub family land cleanly. The four larger items (stability callback, variant loss kernels, PRM trainer, multi-objective preference live combine) are scope-deferred to v0.53.4 each warrants a focused release because they each require deep TRL trainer subclassing.
47
47
 
48
- - **Autopilot detects pre-quantized bases.** `TheBloke/Llama-2-7B-Chat-GPTQ` now recommends `gptq` instead of stacking BNB-4bit on top. Name-regex + `config.json` `quantization_config.quant_method` probe with cwd-containment + symlink rejection on the on-disk path. BNB aliases (`bitsandbytes_4bit` / `nf4` / `bnb_8bit`) canonicalise to `4bit` / `8bit`.
49
- - **`soup merge --save-format 4bit | 4bit_forced`.** Single BNB-4bit-quantized merged checkpoint without the dequant→merge→requant cycle. `4bit_forced` quantizes every Linear (including `lm_head`). Output path is cwd-contained + symlink-rejected at CLI dispatch.
50
- - **`soup export --format torchao --quant-config <yaml>`.** Live `torchao.quantize_` + `save_pretrained`. Closed per-scheme kwarg allowlist (Int4WeightOnly accepts `{group_size, inner_k_tiles}`, NVFP4 accepts nothing extra) defeats kwarg-injection through the YAML.
51
- - **`soup export --format gguf-ud --gguf-flavour <UD-Q4_K_XL | IQ2_M | Q4_0_4_4 | …>`.** Three-stage pipeline: HF f16 GGUF optional importance-matrix (UD ladder + low-bit IQ) quantize. All subprocess calls use argv-list form + 30-min timeout. Calibration JSONL is sanitised (null-byte stripped, newlines collapsed, 8 KB per-line + 50 MB total cap). POSIX `O_NOFOLLOW` defeats the TOCTOU race between the dispatch-time symlink check and the actual open.
52
- - **`soup deploy autopilot --measure --tasks <jsonl>`.** Loops every candidate quant through the v0.26.0 `eval/quant_check` scorer, renders OK/MINOR/MAJOR table, picks the best-by-delta candidate. Results cached at `~/.soup/deploy_autopilot_cache.json` (atomic write, 0o600 perms on POSIX, symlink-rejected on both load and save). Cache key is SHA-256 of `(base, profile, eval-tasks)`.
53
- - **+112 net new tests** (7610 → 7722) across `test_v0531_82.py`, `test_v0531_109.py`, `test_v0531_139.py`, `test_v0531_142.py`. Four review agents (python / code / security / tdd) ran; every CRITICAL / HIGH / MEDIUM / LOW finding fixed: per-scheme TorchAO kwarg allowlist (rejects dunders + unknown keys), corrected BNB 4-bit skip-modules kwarg name, shared `enforce_under_cwd_and_no_symlink` in `utils/paths.py` (single source of truth), `pick_best` switches from `max(after)` to `max(delta)` matching the v0.33.0 #54 design intent, `_run_convert_to_f16` verifies the convert script stays inside `llama_cpp_dir` via realpath + commonpath, `_safe_stderr` Rich-escapes subprocess stderr before exception propagation.
48
+ - **`grpo_fp16: true` is now wired** through `GRPOTrainerWrapper._build_precision_kwargs` — non-CUDA devices (CPU / MPS / XPU) skip HF Trainer's CUDA-specific `fp16`/`bf16` kwargs, CUDA + `grpo_fp16=True` forces FP16 for unsloth parity, default CUDA stays on bf16. The schema now rejects the silent-mutex combo `grpo_fp16=True + auto_mixed_precision=true` at config load with an actionable message naming both flags.
49
+ - **Vision GRPO base-model probe.** `KNOWN_VLM_REGEX` matches 10 VLM families (Qwen2-VL / Qwen2.5-VL / QVQ / Pixtral / InternVL / Llama-3.2-Vision / LLaVA / MiniCPM-V / Idefics / ShareGPT4V / Fuyu) with word-boundary anchors that reject substring noise like `"my-pixtralish"`. A YAML pairing `vision_grpo: true` with a non-VLM checkpoint is now rejected at schema load with a friendly message naming the expected families — instead of surfacing as a cryptic `"module has no attribute 'vision_tower'"` at trainer load time. Error message truncates the echoed base to 64 chars per the v0.34.0 redaction policy.
50
+ - **Validator ordering matters.** `_validate_grpo_fp16_amp_exclusive` short-circuits when `task != 'grpo'` so the v0.50.0 stability task-gate diagnosis ("`grpo_fp16` requires `task=grpo`") fires first regardless of validator execution order keeps the most actionable error in front of the user.
51
+ - **+37 net new tests** (7842 7879) across `test_v0533.py`. Four review agents (python / code / security / tdd) ran; every HIGH / MEDIUM / LOW finding fixed task-gate priority short-circuit, MPS device branch explicitly documented, 64-char error-message truncation, `Optional[object]` `object` parameter cleanup, QVQ regex coverage, 512-char exact-boundary regression test, explicit `grpo_fp16: false` produces bf16 path.
52
+ - **Local YAML smoke** confirms both fixes round-trip via `load_config_from_string`: known VLM bases pass, non-VLM bases plus `vision_grpo: true` are rejected, and the `grpo_fp16` + `auto_mixed_precision` combo is rejected with both flag names in the message.
54
53
 
55
54
  ## Why Soup?
56
55
 
@@ -275,6 +274,97 @@ soup init --template pretrain
275
274
  soup train
276
275
  ```
277
276
 
277
+ ## Knowledge Distillation
278
+
279
+ Train a small student model to match a larger teacher's output distribution.
280
+
281
+ ```yaml
282
+ base: HuggingFaceTB/SmolLM2-135M
283
+ task: distill
284
+ modality: text
285
+ backend: transformers
286
+
287
+ data:
288
+ train: ./data/chat.jsonl
289
+ max_length: 2048
290
+ chat_template: chatml
291
+
292
+ training:
293
+ teacher_model: meta-llama/Llama-3.1-8B
294
+ distill_divergence: forward_kl # kl | forward_kl | reverse_kl | js
295
+ distill_temperature: 2.0
296
+ epochs: 3
297
+ lr: 5e-5
298
+ quantization: 4bit # quantizes student only
299
+ ```
300
+
301
+ Loss = student CE + (T**2) × KL(teacher_logits / T || student_logits / T).
302
+ Teacher is loaded once, frozen via `requires_grad_(False)` + `.eval()`, and its
303
+ inputs / logits are auto-bridged across CPU / CUDA devices.
304
+
305
+ ## Sequence Classification
306
+
307
+ Train a classifier head on top of any base model — supports single-label,
308
+ multi-label, and cross-encoder reranking.
309
+
310
+ ```yaml
311
+ base: BAAI/bge-base-en-v1.5
312
+ task: classifier # or `reranker`, `cross_encoder`
313
+ modality: text
314
+ backend: transformers
315
+
316
+ data:
317
+ train: ./data/labelled.jsonl # rows: {"text": "...", "label": "spam"} or {"text": "...", "label": [0, 1, 0]}
318
+ max_length: 256
319
+
320
+ training:
321
+ num_labels: 3
322
+ classifier_kind: single_label # or `multi_label`
323
+ label_names: [ham, spam, promo] # required when labels are strings
324
+ epochs: 5
325
+ lr: 2e-5
326
+ batch_size: 32
327
+ ```
328
+
329
+ Routes `classifier` / `reranker` / `cross_encoder` through
330
+ `AutoModelForSequenceClassification`. Multi-label heads cap at 1024 entries per
331
+ row, dedup via set conversion, and reject null bytes in label strings.
332
+
333
+ ## Reasoning Effort + EOT Control
334
+
335
+ gpt-oss-style reasoning-effort control for instruction tuning.
336
+
337
+ ```yaml
338
+ training:
339
+ reasoning_effort: high # low | medium | high
340
+ train_on_eot: true # do NOT mask the EOT/EOS token in the loss
341
+ ```
342
+
343
+ `reasoning_effort` injects `<|reasoning_effort|>high<|/reasoning_effort|>` into
344
+ the system turn (creating one if absent). `train_on_eot=True` makes the model
345
+ learn when to stop generating by training on the trailing EOS token instead of
346
+ masking it out. Both are gated to the SFT-family of tasks.
347
+
348
+ ## EBFT / GDPO Loss Variants
349
+
350
+ Entropy-regularised SFT (`ebft_variant: structured | strided`) and generalised
351
+ DPO (`gdpo_variant: standard | length_normalized | margin`) — both attach
352
+ idempotently via `compute_loss` wrappers and auto-fire when the corresponding
353
+ variant field is set on `TrainingConfig`.
354
+
355
+ ```yaml
356
+ # SFT with EBFT structured
357
+ training:
358
+ ebft_variant: structured
359
+ ebft_temperature: 1.0
360
+
361
+ # DPO with GDPO length_normalized
362
+ task: dpo
363
+ training:
364
+ gdpo_variant: length_normalized
365
+ dpo_beta: 0.1
366
+ ```
367
+
278
368
  ## MoE Model Support
279
369
 
280
370
  Fine-tune Mixture of Experts models (Mixtral, Qwen3-30B-A3B, DeepSeek V3) with ScatterMoE LoRA — applies LoRA to both attention layers and expert FFN layers:
@@ -9,7 +9,9 @@ We provide security updates for the following versions:
9
9
  - **Versions older than 3 minor versions:** No support
10
10
 
11
11
  Example:
12
- - v0.53.1 -- Full support (latest)
12
+ - v0.53.3 -- Full support (latest)
13
+ - v0.53.2 -- Full support
14
+ - v0.53.1 -- Full support
13
15
  - v0.53.0 -- Full support
14
16
  - v0.52.0 -- Full support
15
17
  - v0.51.0 -- Full support
@@ -147,6 +149,10 @@ No known critical vulnerabilities in current releases.
147
149
  - **v0.32.0 — Training Stability & Auto-Tuning**: `--find-lr-output` containment via shared `utils/paths.is_under_cwd` (prevents writes outside cwd); `save_lr_finder_report` rejects NaN / Infinity floats in `lrs` / `losses` and serialises with `allow_nan=False` (keeps the report parser-safe); `compute_lr_schedule` rejects non-positive `start_lr`, inverted ranges, and `num_steps` outside `[2, 10_000]`; `pick_mixed_precision` rejects empty / null-byte / >200-char model names and resolves multi-version quirks (`qwen2.5` vs `qwen2`, `phi-3.5` vs `phi-3`) by longest-substring-first iteration so an added family can never accidentally make a more-specific entry dead code; `compute_warmup_steps` clamps to `[10, 1000]` with a `ratio==0.0` short-circuit matching HF Trainer's "no warmup" convention; `SpikeRecoveryStrategy` is `@dataclass(frozen=True)` (post-construction mutation cannot bypass validation), `max_attempts ∈ [1, 10]`, `lr_decay ∈ (0, 1)`, `min_lr > 0`; cross-validator `_validate_spike_recovery_requires_watchdog` rejects `loss_spike_recovery=true, loss_watchdog=false` at config-load (fails fast instead of never triggering); `convergence_window ∈ [5, 10_000]`, `convergence_rel_tol ∈ (0, 1]`, `recommend_action` reuses `detect_plateau` so plateau heuristic stays single-source-of-truth; `GradAccumMonitor.recommend()` caps doubled `accum` at `MAX_ACCUM=1024` so a runaway advisory loop cannot blow up DataLoader prefetch; `generate_config` validates BOTH the YAML output path AND the embedded `decisions["output"]` field via `is_under_cwd` (closes the gap where a crafted `decisions["output"]="../../etc"` would have silently propagated into the rendered YAML)
148
150
  - **v0.34.0 — Observability & Dev UX**: `.crash` bundle generator (`utils/crash.py`) recursively redacts `hf_*` / `sk-*` / `Bearer …` token-shaped strings in any captured `config` and metric tail before serialisation, so a `.crash` file shared on a public GitHub issue cannot leak credentials; `output_dir` is reduced to `os.path.basename` so `$HOME` doesn't leak; `write_crash_bundle` uses `os.path.realpath + commonpath` for cwd containment (Windows-safe; raises `ValueError` not `PermissionError` so callers cannot silently swallow with `except OSError`); filename appends `secrets.token_hex(4)` so two crashes in the same UTC second don't collide; bundle truncated to `MAX_BUNDLE_BYTES=1_000_000`. `train.py` crash-write surfaces failures to the user (no silent missing-bundle). `profiling.py` `resolve_trace_path` rejects empty / `.` / `..` / `/` / `\\` / null-byte `run_id` (closes the `output_dir/profiles/../trace.json` escape) and uses `os.path.realpath + is_under_cwd`; profiles dir is created only on successful torch import (no stale empty dirs on torch-less CI). `tracker.get_run` LIKE-prefix match escapes `%` / `_` / `\\` and uses `ESCAPE '\\'` so a crafted `run_id` cannot widen the match (mirrors v0.26.0 registry policy). Lazy schema migration (`_ensure_schema`) tolerates the "duplicate column" race when two CLI processes start simultaneously on a fresh DB (fork-based multi-GPU training, TUI auto-refresh). `runs.py show/replay/clean` switched user `run_id` rendering to `markup_escape` and switched `clean` containment from broken `Path.resolve() + relative_to()` to project-standard `os.path.realpath + is_under_cwd`. `tui_app.py` lazy-imports `ExperimentTracker` and `markup_escape`s every DB-sourced string before passing into Textual widgets so a crafted base_model / experiment_name cannot inject `[bold red]…[/]` markup. `run_cost.estimate_run_cost_usd` rejects `bool` in `num_gpus` (bool is a subclass of int — same defence as v0.30.0 `Candidate.__post_init__`); duration clamped to `[0, 1 year]`; unknown GPU returns `None` so callers render `—` instead of fabricating `$0.00`. `log_level.parse_log_level` rejects non-string + null-byte input.
149
151
  - **v0.33.0 — Live Wire**: RLVR `code_exec_reward` adds OS-level isolation (Linux best-effort `os.unshare(CLONE_NEWUSER|CLONE_NEWNET|CLONE_NEWPID)`, macOS `sandbox-exec` with default-deny `MACOS_SANDBOX_PROFILE` narrowed to a 3-name `mach-lookup` allowlist to prevent DNS / NSURLSession bypass of `(deny network*)`); `prune_checkpoints` switches to TOCTOU-safe `os.lstat + S_ISLNK` + `shutil.rmtree(onerror=_abort_on_symlink)` so a symlink encountered mid-walk aborts rather than escapes; `run_gate` wraps each task scorer in a typed `try/except` so backend failures produce `score=None, error=str(exc)` (never silent `score=1.0`); `_parse_judge_url` removes the bare `http://` catch-all (defence-in-depth after the Pydantic GateTask validator); `soup can run` requires `--yes` or explicit consent callback and raises `ValueError` (not `PermissionError`, which is an `OSError` subclass that broad `except` blocks would swallow); GGUF `rglob` result for ollama deploy is `realpath+commonpath` checked against extract_dir (prevents symlink escape from a crafted can); `DeployTarget.path` validator normalises mixed `\\`/`/` separators before splitting (closes a Windows `..` bypass); `CAN_FORMAT_VERSION` 1→2 (additive — v1 still loads); `soup can publish` validates `repo_id` via `utils/hf.validate_repo_id`, resolves token via `resolve_token`, sanitises commit messages (first-line, 200-char cap), uses HTTPS-only HfApi; `_write_spike_recovery_hint` adds `is_under_cwd` containment check on `args.output_dir` from raw HF `TrainingArguments`; `lookup_entry_by_output_dir` emits `ResourceWarning` when 1000-row scan limit is hit (no silent miss); `CrossDocCollator` no longer mutates input feature dicts (HF Dataset rows are cached and reused — mutation broke subsequent batches); `Candidate` rejects `bool` in `score`/`latency_ms` (was sneaking past `int` isinstance check); `evaluate_candidate` latency mean now divides by *completed* prompts (excludes crashed) so a broken candidate isn't artificially fast; `auto_quant.run_auto_quant_picker` soft-falls-back to highest-scored candidate when no candidate clears `min_score` (server still binds); `build_logits_processors` returns `[]` when neither `outlines` nor `lm-format-enforcer` is installed (server degrades to free-form rather than 500); MII server uses loopback-only CORS, max_tokens cap [1, 16384], stream rejection, generic 500 with no stack-trace leak; `os.execvp` auto-reexec uses list args (no shell), all forwarded flags pre-validated; `cleanup_extract_dir` uses `os.path.commonpath` (Windows-safe) instead of `startswith`; `_run_subprocess` catches `TimeoutExpired` and returns rc=124 (coreutils convention) instead of an unhandled traceback; new `eval_results` and `tensorrt` artifact kinds in `RegistryStore._VALID_KINDS`
152
+ - **v0.53.3 — GRPO Plus partial wiring (#128 grpo_fp16, #129 vision-VLM probe)**: lifts two surgical v0.50.0 GRPO Plus deferred stubs while keeping the project's hardening invariants; the four larger items (#127 stability callback, #123 6 GRPO variant loss kernels, #126 PRMTrainerWrapper, #68 multi-objective preference live combine) are scope-deferred to v0.53.4. (#128 grpo_fp16 routing) New `_validate_grpo_fp16_amp_exclusive` SoupConfig cross-validator rejects the silent-mutex combo `grpo_fp16=True + auto_mixed_precision=True` at config load — both flags pick the mixed-precision dtype via different codepaths; combining them is a footgun where downstream behaviour depends on validator execution order. Cross-validator short-circuits when `task != 'grpo'` so the v0.50.0 stability task-gate diagnosis fires first (keeps the most actionable error at the front; code-review HIGH fix). New `GRPOTrainerWrapper._build_precision_kwargs(self) -> dict[str, bool]` returns the `{fp16, bf16}` HF kwargs per `(device, grpo_fp16)` matrix: non-CUDA (CPU / MPS / XPU) → both False (HF Trainer's fp16/bf16 kwargs are CUDA-specific, MPS / XPU use their own mixed-precision paths), CUDA + `grpo_fp16=True` → `fp16=True, bf16=False` (unsloth parity), default CUDA → `fp16=False, bf16=True` (legacy v0.50.0 path). Direct attribute access on `self.config.training.grpo_fp16` (no `getattr` fallback — Pydantic-guaranteed field). (#129 vision-GRPO base probe) New `soup_cli/utils/prm.KNOWN_VLM_REGEX` compiled regex with 10 word-boundary alternatives covering Qwen2-VL / Qwen2.5-VL / QVQ / Pixtral / InternVL / InternVL2_5 / InternVL3 / Llama-3.2-Vision (any size via `[a-z0-9._-]*vision` glob) / LLaVA / MiniCPM-V / Idefics / ShareGPT4V / Fuyu. Word-boundary idiom `(?:^|[^a-z0-9])…(?:[^a-z0-9]|$)` mirrors v0.39.0 `is_gemma4_model` / v0.44.0 `is_llama4_model` / v0.49.0 `is_llama_model` policy — rejects substring noise like `"my-pixtralish"`. New `is_known_vlm_base(name: object) -> bool` is defensive — returns False (never raises) on non-string / bool / empty / null-byte / `>_MAX_BASE_NAME_LEN=512`. Extended `validate_vision_grpo_compat` with optional `base: str | None = None` kwarg — `None` / empty-string skips the probe (back-compat for legacy v0.50.0 Part E callers); non-empty-non-VLM raises `ValueError` with friendly message naming the expected families (Qwen2-VL / Pixtral / InternVL / Llama-3.2-Vision / LLaVA / MiniCPM-V). Error message **truncates the echoed `base` to 64 chars** before serialisation (security-review MEDIUM fix mirroring v0.34.0 `crash.py` `output_dir` basename policy — defends against adversarial / long bases bloating error logs and from leaking unredacted user input into operator-facing tracebacks). `_validate_vision_grpo` in SoupConfig threads `base=self.base` so a YAML pairing `vision_grpo: true` with a non-VLM checkpoint is rejected at schema-load instead of surfacing as a cryptic `"module has no attribute 'vision_tower'"` runtime error. Test surface: 1 new test file (`test_v0533.py`) carrying 37 new tests covering: every `_build_precision_kwargs` matrix cell (CUDA + grpo_fp16 / default CUDA / CPU / MPS), every cross-validator branch (mutex rejection / task-gate priority / both-off pass), every regex alternative (Qwen2-VL / Pixtral / QVQ / Llama-3.2-Vision variants / negative matches), every defensive guard (bool / non-string / null-byte / 512-byte boundary), error-message truncation (security-review M regression), and end-to-end YAML load (happy + reject). Known limitations: (1) Scope-deferred — 4 larger v0.53.3 items moved to v0.53.4 because each requires deep TRL subclassing and warrants its own focused release; the v0.40.x stub-then-live cadence shipped 5 patch releases over 6 weeks, mirroring that here. (2) VLM allowlist is static name-regex only; a legitimate VLM published under an org whose checkpoint name lacks any of those tokens (e.g. a custom internal fork) is rejected at schema-load and operators must omit `vision_grpo: true` until a future release adds a runtime `model.config.vision_config` probe. (3) `_build_precision_kwargs` is GRPO-only — other RL trainers (PPO / RewardModel) follow their existing mixed-precision conventions. (v0.53.3)
153
+
154
+ - **v0.53.2 — Modality II live trainers**: lifts four v0.52.0 deferred stubs (#137, #135, #133, #132) into real trainer wrappers while keeping the project's hardening invariants. (#137 reasoning_effort + train_on_eot) `apply_reasoning_effort_prefix` follows v0.41.0 / v0.51.0 validator policy (bool-first, null-byte / empty / oversize / case-insensitive normalisation); messages list is treated as immutable (returns a new list — matches v0.33.0 #47 `CrossDocCollator` policy). `build_assistant_only_labels(train_on_eot=True)` reuses the existing v0.36.0 mask infrastructure — same null-byte / max_length / bool guards. (#135 EBFT / GDPO) `apply_ebft_loss` and `apply_gdpo_loss` enforce **finite-only inputs** (`torch.isfinite` guard on tensor inputs + `math.isfinite` on scalar params) — NaN / Inf would silently corrupt training otherwise. `dpo_margin` defaults to `None` (not `0.0`) per security-review M3 fix: silent zeroing in the `margin` variant when the operator forgot to set the margin would have looked like training success but produced a meaningless gradient. Both attach hooks (`attach_ebft_compute_loss`, `attach_gdpo_compute_loss`) are **idempotent** via a marker attribute on the wrapped method — re-attach is a no-op and a dedicated test class verifies the invariant (code-review M2 fix). (#133 DistillTrainerWrapper) **Separate trust_remote_code resolution for student and teacher** (security-review L2 fix): `model_requires_trust_remote_code(teacher)` runs independently of the student probe, otherwise a malicious teacher could piggy-back on the student's opt-in. Teacher is loaded with `device_map="cpu" if device == "cpu" else "auto"`, frozen via `requires_grad_(False)` + `.eval()` immediately after load — never participates in gradient computation. `_DistillTrainer.compute_loss` device-bridge: `teacher_device = next(teacher_ref.parameters()).device`, `teacher_inputs.to(teacher_device)` before teacher forward, `teacher_logits.to(student_logits.device)` before KL kernel — defends against HF Trainer's auto-CUDA promotion silently producing cross-device `index_select` crashes. **DataCollator correctness fix** (surfaced during Wave 3 CPU smoke): `DataCollatorForLanguageModeling` does NOT pad pre-tokenised `labels` — switched to `DataCollatorForSeq2Seq(label_pad_token_id=-100, padding=True)` so variable-length loss-masked rows batch correctly without runtime crash. (#132 ClassifierTrainerWrapper) `_normalise_label` caps multi-label entries at **1024 per row** (matches v0.52.0 schema cap; security-review HIGH fix — unbounded would allow OOM via crafted JSONL), dedups via set conversion, validates `label_names` map entries reject null bytes + empty strings. `problem_type` is set explicitly from `tcfg.classifier_kind` (not silently inferred from labels) so a multi-label-shaped row in a single-label config raises rather than mis-trains. Training Setup Panel renders `Head: num_labels=N, kind=...` for classifier-family tasks instead of meaningless LoRA r/alpha lines (code-review L3 cosmetic fix — Panel no longer mis-represents what the wrapper is doing). (Cross-cutting) `commands/train.py` task routing branches added for `distill` and `classifier` / `reranker` / `cross_encoder` — source-grep regression guards in the test suite use the **full instantiation expression** `DistillTrainerWrapper(cfg, **trainer_kwargs)` so comment-only mentions of the class name cannot satisfy the regression check (TDD-review hardening). Both new factories (`build_distill_trainer`, `build_classifier_trainer`) reject unknown kwargs via Python signature contract — dedicated `pytest.raises(TypeError)` tests cover the path (TDD-review L1 fix). Test surface: 1 new test file (`test_v0532.py`) carrying 120 new tests across 14 classes. Known limitations: (1) `#71` TinyLlama-1.1B-LoRA full ONNX export is host-RAM-bound (≥16 GB free RAM needed for the `onnx.load(load_external_data=True)` post-process step); tiny-gpt2 smoke proves pipeline integrity — recorded in `tests/qa/v053_qa.md`. (2) Distillation supports same-tokenizer pairs only — cross-tokenizer (Llama → Qwen) needs a projection or sequence-level loss, out of scope. (3) Classifier wrapper has no LoRA path — full head + base training; LoRA classifier finetuning is a follow-up. (4) EBFT / GDPO auto-attach only fires when the corresponding `*_variant` field is set; manual `attach_*` invocation from custom training loops is supported and idempotent. (5) `reasoning_effort` injection happens at data-prep time inside `build_format_row`; changing the level between runs requires re-rendering the dataset. (v0.53.2)
155
+
150
156
  - **v0.53.1 — Quant Menu II + Export pipeline live**: lifts six v0.53.0 deferred stubs to live wiring while keeping the project's hardening invariants. New shared helper `soup_cli/utils/paths.enforce_under_cwd_and_no_symlink` consolidates the v0.33.0 #22 TOCTOU pattern (cwd containment via `os.path.realpath + os.path.commonpath` + `os.lstat + S_ISLNK` rejection) — used by `commands/merge.py`, `commands/export.py`, `utils/save_formats.py`, and `utils/gguf_quant.py` so the same boundary check fires at every CLI dispatch point. `merge_4bit` and `export_torchao` (`utils/save_formats.py`): cwd containment + symlink rejection on `merged_dir` / `model_dir` / `output_dir`; `load_quant_config` enforces `yaml.safe_load` only + 256 KB cap + extension allowlist (`.yaml`/`.yml`); **per-scheme closed kwarg allowlist** rejects dunder keys + unknown params before the splat into `torchao.<scheme>Config(**kwargs)` (security-review HIGH fix — `Int4WeightOnly` accepts `{group_size, inner_k_tiles}`, `NVFP4` accepts nothing extra). Corrected BNB-4bit skip-modules kwarg name from `llm_int8_skip_modules` to `bnb_4bit_skip_modules`. `export_advanced_gguf` (`utils/gguf_quant.py`): all three subprocess invocations (`convert_hf_to_gguf.py`, `llama-imatrix`, `llama-quantize`) use argv-list form with no shell, 30-min timeout, `sys.executable` for the convert script; `_run_convert_to_f16` realpath-verifies that `convert_hf_to_gguf.py` stays inside the `llama_cpp_dir` after resolution (security-review HIGH M5 fix — defends against a symlinked script escape). `_prepare_calibration_text` strips null bytes, collapses newlines to spaces, caps per-line at 8 KB + total at 50 MB (security-review M1), uses POSIX `O_NOFOLLOW` to refuse symlinks at the kernel level (security-review M3 — closes the TOCTOU window between the dispatch-time check and the actual `open()`); requires ≥ 1 usable row before invoking imatrix. `_safe_stderr` Rich-markup-escapes subprocess stderr before embedding in `RuntimeError` (security-review L4) so a crafted llama.cpp error cannot inject `[red]...[/]` into the operator-facing panel. UD-prefix stripped from flavour arg before passing to llama-quantize (`UD-Q4_K_XL` → `Q4_K_XL`). Calibration data path containment + symlink rejection fires at CLI dispatch in `commands/export.py::_export_gguf_advanced`. `detect_prequantized_format_from_path` (`autopilot/decisions.py`): cwd containment + `os.lstat + S_ISLNK` on `<model_dir>/config.json` (security-review HIGH H2 — out-of-cwd model paths silently return `None` to preserve soft-probe semantics so HF Hub repo IDs aren't rejected); null-byte rejection on `model_dir`. `commands/merge.py`: early `is_under_cwd(output)` check at CLI boundary (security-review M4) — consistent with the v0.20.0 / v0.40.2 containment-at-the-boundary policy. `deploy_measure.py`: cache file written atomically via `tempfile.mkstemp` + `os.replace` with `os.lstat + S_ISLNK` rejection on BOTH `load_cache` and `save_cache` (security-review M2 — was missing on the load side); env override `SOUP_DEPLOY_AUTOPILOT_CACHE` rejects null bytes + control chars before any path resolution and confines the override to home / cwd / tempdir; cache file gets best-effort 0o600 perms on POSIX (matches v0.26.0 registry.db policy); 1 MB cache-file cap. `_DEPLOY_MEASURE_BEFORE_GEN` / `_AFTER_FACTORY` module-level callables are documented as a non-public escape hatch (deferred until v0.46.1 live model-loader). Test surface: 4 new test files (`test_v0531_82.py` / `test_v0531_109.py` / `test_v0531_139.py` / `test_v0531_142.py`) carrying 112 new tests covering happy paths + failure modes + every security guard (POSIX symlink rejection, per-scheme kwarg allowlist, TOCTOU defences, `_MAX_CANDIDATES` cap, MINOR-verdict band, mxfp4 word boundary, BNB-alias detection, render-table markup escape). Known limitations: (1) `_DEPLOY_MEASURE_BEFORE_GEN` / `_AFTER_FACTORY` are a stop-gap until v0.46.1 ships first-party transformers / vLLM generator factories. (2) `#70` GGUF and `#72` AWQ/GPTQ manual QA smokes remain pending — require CUDA + llama.cpp build; recipes scripted in `tests/qa/v053_qa.md`. (3) BNB-4bit merge + TorchAO PTQ live happy-path is mock-covered only — CPU-only CI cannot execute the real BNB / torchao kernels. (4) `_prepare_calibration_text` accepts JSONL with `text` / `prompt` / `content` aliases + raw text fallback; other formats (parquet / markdown) are out of scope. (5) Cache key truncates `base_sha` to 16 hex chars at the call site (collision probability ≈ 1-in-2³² across ~4 billion entries). (6) Pre-quantized detection is heuristic — name regex + local `config.json` probe; HF Hub repo IDs without local download fall back to name-only matching. (7) `enforce_under_cwd_and_no_symlink` checks only the leaf path; deeper traversal relies on the per-file leaf check at each site. (v0.53.1)
151
157
  - **v0.53.0 — Quant Menu II (UD GGUFs + KV cache + NVFP4 + LF parity + save formats)**: 6 schema-only Parts; live wiring deferred to v0.53.1. Every new validator follows the project's established hardening policy: closed allowlists (`UD_GGUF_FORMATS`, `IQ_GGUF_FORMATS`, `APPLE_ARM_GGUF_FORMATS`, `KV_CACHE_TYPES`, `MERGE_SAVE_FORMATS`, `TORCHAO_PTQ_SCHEMES`) as `frozenset` so registries cannot be mutated; `_GGUF_METADATA` / `_KV_CACHE_METADATA` / `_MERGE_METADATA` / `_TORCHAO_METADATA` wrapped in `MappingProxyType`; `_LOWER_INDEX` for GGUF lookup is also `MappingProxyType`-wrapped (replaces O(N) walk with O(1) lookup — code-review MEDIUM fix). All string validators reject non-string / bool / empty / null-byte / oversize with case-insensitive normalisation (matches v0.41.0 `validate_optimizer_name` / v0.51.0 `validate_hub_name` policy); `validate_torchao_scheme` is INTENTIONALLY case-sensitive (PyTorch class names — `torchao.quantize_` looks them up by exact name) with the asymmetry documented at both validators (security-review LOW fix). `validate_calibration_data_path` + `validate_quant_config_path` are shape-only at this release; their docstrings name the exact controls a v0.53.1 CLI dispatch contributor MUST add (`os.path.realpath` + `os.path.commonpath` cwd containment, `os.lstat` + `stat.S_ISLNK` symlink rejection before `open()`, existence check, `yaml.safe_load`-only for quant configs) — closes the security-review MEDIUM "documentation gap at trust boundary" finding. SoupConfig cross-validators: `_validate_fp8_attention_compat` (requires `quantization_aware='fp8'` BEFORE the MLX gate so the more actionable error fires first — code-review MEDIUM fix); `_validate_nvfp4_compat` (non-MLX + `modality='text'`; Blackwell SM ≥ 12.0 runtime check fires at trainer construction); `_validate_unsloth_bnb_4bit_compat` (requires `backend='unsloth'` + `quantization='4bit'`); `_validate_bnb_4bit_double_quant` (requires `quantization='4bit'` — rejects `none`/`8bit`/Quant-Menu); `_validate_llm_int8_alias` (asserts `quantization='8bit'`, deliberately disjoint from v0.41.0 `load_in_8bit` aliasing); `_validate_quantize_ref_reward` (extended ref-task allowlist `{dpo, ipo, simpo, orpo, bco, kto, preference, grpo, ppo}` per code-review HIGH fix — first-cut omitted grpo + kto + ppo which all have reference policies); `_validate_kv_cache_type_supported` (only `fp8` gated to non-MLX in v0.53.0; q8_0/bf16/f16 pass-through documented at validator site so v0.53.1 contributor sees the gate immediately). `requires_hopper` reads from `_KV_CACHE_METADATA` spec — single source of truth so adding a Hopper-only type means flipping the spec field only (code-review MEDIUM fix). All 7 new bool fields share `_validate_v053_bool_fields` `field_validator(mode='before')` that rejects bool-as-int with explicit `TypeError("v0.53.0 flag must be bool")` and passes `None` through to Pydantic's `default=False` rather than silently coercing it (python-review MEDIUM fix — `fp8_attention: null` in YAML now surfaces as a "valid boolean" ValidationError instead of masquerading as `False`). Known limitations: (1) Every live wiring is deferred to v0.53.1 — `export_advanced_gguf`, `apply_kv_cache_type`, `apply_fp8_attention`, `apply_nvfp4`, `merge_4bit`, `export_torchao` all raise `NotImplementedError` with explicit `v0.53.1` markers. (2) `validate_calibration_data_path` + `validate_quant_config_path` are shape-only this release; CLI dispatch in v0.53.1 MUST add cwd-containment + TOCTOU symlink rejection. (3) `kv_cache_type` MLX permissive policy: only `fp8` is rejected, the other three pass-through; v0.53.1 may narrow further. (4) Hopper SM-capability check is runtime-only — schema accepts `kv_cache_type='fp8'` + `fp8_attention=true` without GPU probe. (5) NVFP4 + Blackwell (SM ≥ 12.0) check is runtime-only. (6) `bnb_4bit_use_double_quant` only gated against `quantization`, not against `quantization_aware` — the latter combination is already rejected by v0.28.0 Quant-Menu + QAT cross-validator. (7) `llm_int8` is an assertion not an aliaser — diverges from v0.41.0 `load_in_8bit` design on purpose. (v0.53.0)
152
158
  - **v0.52.0 — Modality II (TTS + Distillation + BitNet + EBFT-GDPO + MoE quant + reasoning_effort)**: 7 schema-only Parts; live trainer / loss / export wiring deferred to v0.52.1. Every new validator follows the project's established hardening policy: closed allowlist (`SUPPORTED_TTS_FAMILIES`, `CLASSIFIER_TASKS`, `DIVERGENCES`, `BITNET_QUANT_FORMATS`, `BITNET_EXPORT_FORMATS`, `EBFT_VARIANTS`, `GDPO_VARIANTS`, `MOE_EXPERT_QUANT_FORMATS`, `REASONING_EFFORT_LEVELS`, per-family `_FAMILY_EMOTIONS`) wrapped in `frozenset` / `MappingProxyType` so registries cannot be mutated at runtime; `validate_*` helpers reject non-string / bool / empty / null-byte / oversize / unknown inputs with case-insensitive normalisation (matches v0.41.0 `validate_optimizer_name` / v0.50.0 `grpo_variant` / v0.51.0 `hub` policy); float validators (`validate_distill_temperature`, `validate_ebft_temperature`) gate on `math.isfinite` to reject NaN AND `±inf` (matches v0.32.0 `save_lr_finder_report` policy). `field_validator(mode="before")` on `num_labels` (security-review HIGH fix) rejects `bool` before Pydantic's `ge=1` coercion silently treats `True` as `1`. Field validator on `reasoning_effort` routes through the shared `validate_reasoning_effort` helper so the schema and runtime validator agree on what's accepted (security-review MEDIUM fix). SoupConfig cross-validators: `_validate_tts_compat` (requires `task='tts'` + `modality='audio_out'` + non-MLX backend; per-family emotion allowlist via `_FAMILY_EMOTIONS`), `_validate_classifier_compat` (with lazy-import early-return — code-review HIGH fix — so SFT hot path doesn't pay import cost; requires `num_labels` on classifier tasks; rejects classifier-only fields outside the task family with named offenders), `_validate_distill_compat` (requires `teacher_model` when `task='distill'`; rejects distill-only fields outside the task), `_validate_bitnet_compat` (gates to non-MLX + text-modality + task ∈ {sft, pretrain, dpo}), `_validate_ebft_compat` + `_validate_gdpo_compat` (task-family gates), `_validate_moe_expert_quant_compat` (requires `moe_lora=true` to prevent silent no-op), `_validate_reasoning_effort_task_gate` (code-review HIGH fix — rejects `reasoning_effort` + `train_on_eot` outside the SFT-family task set with named offenders; mirrors v0.50.0 GRPO stability task-gate policy). Public `DIVERGENCES` frozenset is derived from `_DIVERGENCE_ALIASES` so adding a new alias updates both the accepted-input set and the error message in lockstep (review fix LOW). `validate_bitnet_export` enforces a closed-allowlist canonical form for `soup export --format <bitnet|tq1_0>`, both of which are CLI-registered with a yellow advisory panel + `Exit(0)` stub (no artifact written until v0.52.1 — the format flag is accepted so existing scripts pinned to v0.52.0 will not break). 6 new YAML recipes appended (5 TTS + Falcon-E BitNet) — every entry is exercised by `tests/test_v0520.py` for `load_config_from_string` round-trip + `_no_null_or_whitespace` model-id check (mirrors v0.51.0 review-fix LOW). Known limitations: (1) Every live trainer / loss / export path is deferred to v0.52.1 — `build_tts_trainer`, `build_classifier_trainer`, `build_distill_trainer`, `build_bitnet_trainer`, `export_bitnet_gguf`, `apply_ebft_loss`, `apply_gdpo_loss`, `apply_moe_expert_quant` all raise `NotImplementedError` with explicit `v0.52.1` markers; schema accepts every new task / quant / variant + the CLI stub for `soup export --format bitnet/tq1_0` prints a deferred-advisory panel and exits 0. (2) `modality='audio_out'` accepted on non-TTS tasks — design choice this release so future audio-output tasks (ASR / V2A) can reuse it; today's runtime trainer dispatch must check `task == 'tts'` to avoid silent routing into the deferred TTS path. (3) Oute emotion allowlist is a tight 6-entry subset (neutral / happy / sad / angry / calm / excited); operators wanting custom emotions will need a v0.52.1 patch to extend `OUTE_EMOTIONS`. (4) `is_bitnet_model` is best-effort heuristic over name prefixes (`bitnet`, `falcon-e`, `1bitllm`, `onebit`); a BitNet checkpoint published under an org without any of those prefixes returns False. This is detection, not gating — the trainer wrapper (v0.52.1) loads the model regardless of the heuristic. (5) `quantization='bitnet_1.58'` gated to task ∈ {sft, pretrain, dpo} — extending to GRPO / PPO / RewardModel requires upstream onebitllms RL kernels not yet shipped. (v0.52.0)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "soup-cli"
7
- version = "0.53.1"
7
+ version = "0.53.3"
8
8
  description = "Fine-tune LLMs in one command. No SSH, no config hell."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,3 +1,3 @@
1
1
  """Soup CLI — Fine-tune LLMs in one command."""
2
2
 
3
- __version__ = "0.53.1"
3
+ __version__ = "0.53.3"
@@ -475,6 +475,19 @@ def train(
475
475
  if cfg.training.quantization_aware:
476
476
  quant_label += " + QAT"
477
477
 
478
+ # v0.53.2 review-fix: classifier-family tasks train a sequence-classification
479
+ # head, not a causal-LM LoRA — render "head" instead of LoRA r/alpha.
480
+ classifier_family = ("classifier", "reranker", "cross_encoder")
481
+ if cfg.task in classifier_family:
482
+ peft_line = (
483
+ f"Head: [bold]num_labels={cfg.training.num_labels}, "
484
+ f"kind={cfg.training.classifier_kind}[/]"
485
+ )
486
+ else:
487
+ peft_line = (
488
+ f"LoRA: [bold]r={cfg.training.lora.r}, "
489
+ f"alpha={cfg.training.lora.alpha}[/]"
490
+ )
478
491
  console.print(
479
492
  Panel(
480
493
  f"Device: [bold]{device_name}[/]\n"
@@ -482,7 +495,7 @@ def train(
482
495
  f"Model: [bold]{cfg.base}[/]\n"
483
496
  f"Task: [bold]{cfg.task}[/]\n"
484
497
  f"Backend: [bold]{backend_label}[/]\n"
485
- f"LoRA: [bold]r={cfg.training.lora.r}, alpha={cfg.training.lora.alpha}[/]\n"
498
+ f"{peft_line}\n"
486
499
  f"Quant: [bold]{quant_label}[/]",
487
500
  title="Training Setup",
488
501
  )
@@ -736,6 +749,16 @@ def train(
736
749
  from soup_cli.trainer.embedding import EmbeddingTrainerWrapper
737
750
 
738
751
  trainer_wrapper = EmbeddingTrainerWrapper(cfg, **trainer_kwargs)
752
+ elif cfg.task == "distill":
753
+ # v0.53.2 #133 — knowledge distillation (student + frozen teacher).
754
+ from soup_cli.trainer.distill import DistillTrainerWrapper
755
+
756
+ trainer_wrapper = DistillTrainerWrapper(cfg, **trainer_kwargs)
757
+ elif cfg.task in ("classifier", "reranker", "cross_encoder"):
758
+ # v0.53.2 #132 — sequence-classification head.
759
+ from soup_cli.trainer.classifier import ClassifierTrainerWrapper
760
+
761
+ trainer_wrapper = ClassifierTrainerWrapper(cfg, **trainer_kwargs)
739
762
  else:
740
763
  trainer_wrapper = SFTTrainerWrapper(cfg, **trainer_kwargs)
741
764
  trainer_wrapper.setup(dataset)
@@ -2512,6 +2512,7 @@ class SoupConfig(BaseModel):
2512
2512
  task=self.task,
2513
2513
  modality=self.modality,
2514
2514
  backend=self.backend,
2515
+ base=self.base, # v0.53.3 #129 — name-regex VLM probe
2515
2516
  )
2516
2517
  except ValueError as exc:
2517
2518
  raise ValueError(str(exc)) from exc
@@ -2557,6 +2558,35 @@ class SoupConfig(BaseModel):
2557
2558
  )
2558
2559
  return self
2559
2560
 
2561
+ @model_validator(mode="after")
2562
+ def _validate_grpo_fp16_amp_exclusive(self) -> "SoupConfig":
2563
+ """v0.53.3 #128 — ``grpo_fp16`` and ``auto_mixed_precision`` are
2564
+ mutually exclusive.
2565
+
2566
+ Both flags pick the mixed-precision dtype but go through different
2567
+ codepaths (``grpo_fp16`` forces ``fp16=True, bf16=False`` on
2568
+ GRPOConfig directly; ``auto_mixed_precision`` runs the v0.32.0
2569
+ per-model + per-GPU picker). Combining them is a footgun where the
2570
+ downstream behaviour depends on order-of-evaluation — fail fast at
2571
+ config-load with a friendly message naming both flags so the user
2572
+ picks one.
2573
+ """
2574
+ # Short-circuit when task is not 'grpo' so the v0.50.0 stability
2575
+ # task-gate error fires first (code-review HIGH fix — keeps a
2576
+ # consistent "wrong-task" diagnosis ahead of the mutual-exclusion
2577
+ # one, regardless of validator execution order).
2578
+ if self.task != "grpo":
2579
+ return self
2580
+ if self.training.grpo_fp16 and self.training.auto_mixed_precision:
2581
+ raise ValueError(
2582
+ "grpo_fp16=True and auto_mixed_precision=True are mutually "
2583
+ "exclusive — both pick the mixed-precision dtype but go "
2584
+ "through different codepaths. Pick one: grpo_fp16 forces "
2585
+ "FP16 (unsloth parity), auto_mixed_precision uses the "
2586
+ "v0.32.0 per-GPU picker."
2587
+ )
2588
+ return self
2589
+
2560
2590
  @model_validator(mode="after")
2561
2591
  def _validate_hub_supported(self) -> "SoupConfig":
2562
2592
  """v0.51.0 Part E — ``hub`` other than ``hf`` requires a non-mlx
@@ -110,6 +110,8 @@ def build_assistant_only_labels(
110
110
  messages: Sequence[dict],
111
111
  tokenizer: Any,
112
112
  max_length: int = 2048,
113
+ *,
114
+ include_eot: bool = False,
113
115
  ) -> dict[str, list[int]]:
114
116
  """Build labels where only assistant tokens contribute to loss.
115
117
 
@@ -117,6 +119,11 @@ def build_assistant_only_labels(
117
119
  messages: Chat messages list (``{"role": ..., "content": ...}``).
118
120
  tokenizer: HF tokenizer with a ``chat_template`` set.
119
121
  max_length: Truncate to this many tokens.
122
+ include_eot: When True (axolotl ``train_on_eot``), extend each
123
+ assistant span to include the immediately-following EOS / EOT
124
+ token in the unmasked region — so the model learns to predict
125
+ the turn terminator. Default False matches HF Trainer's standard
126
+ chat-template loss-mask behaviour. (v0.53.2 #137)
120
127
 
121
128
  Returns:
122
129
  ``{"input_ids": [...], "labels": [...], "attention_mask": [...]}``
@@ -125,13 +132,22 @@ def build_assistant_only_labels(
125
132
  Raises:
126
133
  ValueError: empty messages, non-positive max_length, or tokenizer
127
134
  lacking a chat_template.
135
+ TypeError: ``include_eot`` not bool.
128
136
  """
137
+ if not isinstance(include_eot, bool):
138
+ raise TypeError(
139
+ f"include_eot must be bool, got {type(include_eot).__name__}"
140
+ )
129
141
  _check_messages(messages)
130
142
  _validate_max_length(max_length)
131
143
 
144
+ eos_token_id = _resolve_eos_token_id(tokenizer) if include_eot else None
145
+
132
146
  preferred = _apply_template_with_mask(tokenizer, messages)
133
147
  if preferred is not None:
134
148
  input_ids, mask = preferred
149
+ if include_eot and eos_token_id is not None:
150
+ mask = _extend_mask_to_eot(input_ids, mask, eos_token_id)
135
151
  labels = [
136
152
  tok if flag else IGNORE_INDEX
137
153
  for tok, flag in zip(input_ids, mask)
@@ -150,10 +166,62 @@ def build_assistant_only_labels(
150
166
  if msg.get("role") == "assistant":
151
167
  end = min(new_len, len(full_ids))
152
168
  labels[prev_len:end] = full_ids[prev_len:end]
169
+ if include_eot and eos_token_id is not None:
170
+ # Extend through the immediately-following EOT/EOS run.
171
+ extra = end
172
+ while extra < len(full_ids) and full_ids[extra] == eos_token_id:
173
+ labels[extra] = full_ids[extra]
174
+ extra += 1
153
175
  prev_len = new_len
154
176
  return _truncate(full_ids, labels, max_length)
155
177
 
156
178
 
179
+ def _resolve_eos_token_id(tokenizer: Any) -> Optional[int]:
180
+ """Return an int EOS/EOT token id, or None if undetermined.
181
+
182
+ Handles tokenizers exposing ``eos_token_id`` as int (most), list[int]
183
+ (e.g. Llama 3 with the additional ``<|eot_id|>`` entry — we pick the
184
+ first int entry), or anything else (str/None/bool → None).
185
+ """
186
+ candidate = getattr(tokenizer, "eos_token_id", None)
187
+ if isinstance(candidate, bool):
188
+ return None
189
+ if isinstance(candidate, int):
190
+ return candidate
191
+ if isinstance(candidate, list):
192
+ for entry in candidate:
193
+ if isinstance(entry, int) and not isinstance(entry, bool):
194
+ return entry
195
+ return None
196
+
197
+
198
+ def _extend_mask_to_eot(
199
+ input_ids: Sequence[int], mask: Sequence[int], eos_token_id: int
200
+ ) -> list[int]:
201
+ """Mark EOT/EOS tokens immediately following an assistant span as kept.
202
+
203
+ Idempotent: a second pass over already-extended output produces the
204
+ same result (no extra EOT absorbed downstream of the original span).
205
+ """
206
+ result = list(mask)
207
+ n = len(input_ids)
208
+ i = 0
209
+ while i < n:
210
+ if result[i]:
211
+ # Walk to the end of this kept span, then absorb trailing EOS.
212
+ j = i
213
+ while j < n and result[j]:
214
+ j += 1
215
+ while j < n and input_ids[j] == eos_token_id:
216
+ result[j] = 1
217
+ j += 1
218
+ # j > i guaranteed: the truthy-span walk advanced j at least once.
219
+ i = j
220
+ else:
221
+ i += 1
222
+ return result
223
+
224
+
157
225
  def build_per_message_train_labels(
158
226
  messages: Sequence[dict],
159
227
  tokenizer: Any,